Skip to content

SwiftUI中一些名词或tips

警告
此文档参考了Chatgpt的解答

@Binding

在SwiftUI中,@Binding是一种属性包装器,用于将属性绑定到另一个属性的状态。它提供了一种双向数据绑定的方式,使得组件基于状态自动更新。@Binding用于独立的变量之间双向绑定,将子视图和父视图的状态同步。

例如,如果您想要创建一个可以在父View和子View之间传递值的视图,则可以使用@Binding。假设您在父View中定义了一个变量,该变量将传递给子View:

swift
struct ParentView: View {
    @State var value = 0  
    var body: some View {
        ChildView(value: $value)
    }
}
struct ParentView: View {
    @State var value = 0  
    var body: some View {
        ChildView(value: $value)
    }
}

在子View中,您可以使用@Binding将该变量进行绑定:

swift
struct ChildView: View {
    @Binding var value: Int
    var body: some View {
        // 使用绑定的值
    }
}
struct ChildView: View {
    @Binding var value: Int
    var body: some View {
        // 使用绑定的值
    }
}

这样,当父View的value值发生变化时,子View的value值也会自动更新。在子视图中更新该值会同步更新父视图中的值。

与@State的区别

虽然@Binding和@State都可以用于处理应用程序状态,但它们在用途和使用方式上有很大的不同。

@State被用于在单个视图中自动管理状态,它是一个属性包装器,用于在视图结构内部声明状态属性,当状态变化时自动触发视图更新。

而@Binding是用于连接不同的视图之间的双向数据绑定。它是一个传递数据的工具,其中数据可以在两个不同的视图之间传递。一般情况下,@Binding在父视图中传递一个值给子视图,并且允许子视图修改该值并将其更新回父视图

另外,@State只能用于一个视图中,而@Binding可以用于将值在不同视图之间传递。此外,@State只能由一个特定视图修改,而@Binding可以通过多个视图修改。

因此,尽管它们都可以处理状态,但@Binding和@State的用途和使用方式是不同的。只有当您需要将两个或多个视图之间的值保持同步时才应使用@Binding。而对于每个视图的本地状态,应该使用@State。

@EnvironmentObject

@ EnvironmentObject是一种属性包装器,用于将环境对象注入到视图层次结构中的任何地方,并使其可用于所有层次结构中的子视图。环境对象是一个可观察的对象,可以包含任意类型的数据,并且可以在整个应用程序中共享和使用,例如应用程序的设置或用户身份验证。@EnvironmentObject属性包装器的使用涉及以下步骤:

  1. 创建一个ObservableObject的自定义类并放入一些需要跨视图共享的属性。
  2. 在应用程序的顶层视图中使用@EnvironmentObject注册此自定义类。
  3. 在需要访问环境对象的任何视图中使用@EnvironmentObject属性包装器获取访问这个环境对象的能力。
    总之,@EnvironmentObject在 SwiftUI 的应用中为我们提供了一种方便的方式,以将环境对象注入到整个应用程序的视图层次结构中。

示例代码:

swift
import SwiftUI

class UserData: ObservableObject {
    @Published var name = "John"
}

struct ContentView: View {

    @EnvironmentObject var userData: UserData

    var body: some View {
        VStack {
            Text("Welcome, \(userData.name)!")
                .font(.title)
                .foregroundColor(.green)
            TextField("Enter your name", text: $userData.name)
                .textFieldStyle(RoundedBorderTextFieldStyle())
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView().environmentObject(UserData())
    }
}
import SwiftUI

class UserData: ObservableObject {
    @Published var name = "John"
}

struct ContentView: View {

    @EnvironmentObject var userData: UserData

    var body: some View {
        VStack {
            Text("Welcome, \(userData.name)!")
                .font(.title)
                .foregroundColor(.green)
            TextField("Enter your name", text: $userData.name)
                .textFieldStyle(RoundedBorderTextFieldStyle())
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView().environmentObject(UserData())
    }
}

在这个例子中,我们创建了一个名为UserData的ObservableObject。该对象具有一个@Published属性name,它将在数据更改时自动发布更改通知。然后,我们定义了一个名为ContentView的视图。该视图使用@EnvironmentObject注释将userData对象注入其上下文中,以便在视图本身和其子视图中可用。接下来,我们在视图中放置了一些简单的UI元素,例如标题和文本字段。文本字段使用$userData.name来读取和更新UserData的name属性。最后,我们还为ContentView定义了一个名为ContentView_Previews的视图预览,该预览使用.environmentObject将UserData注入ContentView以显示在预览中。

通过使用@EnvironmentObject将可观察对象注入SwiftUI视图树,我们可以使数据更改在整个应用程序中自动同步,并使数据管理变得简单。

@StateObject

@StateObject 是一个属性包装器,用于将对象作为 State 在视图中进行管理。SwiftUI中,每当属性的值更改时,所有依赖该值的视图都会重新计算和更新,@StateObject 通过将其包装的对象作为State来管理其状态,并确保只创建一个实例。当对象的状态改变时,SwiftUI会自动重新计算视图。

使用 @StateObject 的好处是可以将对象的生命周期委托给 SwiftUI 管理,也可以方便地在不同的视图之间共享相同的实例。这对于需要使用长时间存在的对象(例如网络请求、数据库连接或其他长时间运行的任务)的视图非常有用。

@StateObject只有iOS14以后版本才能用。iOS13之前,可以使用@ObservedObject

StateObject 和 ObservedObject 的区别:

@StateObject vs @ObservedObject: The differences explained - SwiftLee
Observed objects marked with the @StateObject property wrapper don’t get destroyed and re-instantiated at times their containing view struct redraws.

如果view有局部变量是用ObservedObject 修饰的,该view被重绘的时候,这个局部变量也会被重置。 但是如果我们用@StateObject修饰,就不会有这个问题。

也就是说,如果我们使用变量ObservedObject是从外部视图传参过来的,这样@StateObject没有区别

@State

@Published

SwiftUI 中的 @Published 属性包装器是用于标记特定属性的特殊属性包装器。被 @Published 标记的属性实际上是一个 ObservableObject 引用,并且当该属性的值被修改时,系统会自动发布一个对象更改的通知。订阅了该属性的视图会自动更新,并显示该属性的最新值。

举个例子:

swift
class DataModel: ObservableObject {
    @Published var itemName = ""
    @Published var itemCount = 0
    // ...
}
class DataModel: ObservableObject {
    @Published var itemName = ""
    @Published var itemCount = 0
    // ...
}

在上述示例中, itemName 和 itemCount 属性被标记为 @Published。这意味着每当它们的值发生变化时,他们会自动将更改推送到任何依赖于他们的视图。

@Published 是 SwiftUI 框架中一项非常强大的功能,它能够大幅减少代码量,并能够自动管理数据驱动的视图更新。

aspectRatio

SwiftUI中的aspectRatio可以帮助我们调整视图的宽高比。它通常用于调整图片或视频的大小并保持固定宽高比。
aspectRatio有两种用法:

  1. aspectRatio(_ aspectRatio: CGFloat, contentMode: ContentMode):此函数接受两个参数,第一个参数是宽高比,第二个参数是内容模式。例如,aspectRatio(16/9, contentMode: .fit)指定了视图的宽高比为16:9,且图像会缩放以适应视图。
  2. aspectRatio(_ aspectRatio: CGSize, contentMode: ContentMode):此函数接受CGSize类型的参数,表示宽高比。例如,aspectRatio(CGSize(width: 3, height: 4), contentMode: .fill)指定视图高度是宽度的4/3,且图像将填充整个视图。
    可以在视图的modifier中使用aspectRatio()来设置视图的宽高比。例如:
swift
Image("example_image")
    .resizable()
    .aspectRatio(16/9, contentMode: .fit)
Image("example_image")
    .resizable()
    .aspectRatio(16/9, contentMode: .fit)

这将显示一个16:9宽高比的可调整大小的图像,并将其缩放以适应视图。

LengthFormatter

Swift中的LengthFormatter是一个用于格式化长度(例如距离)的工具类。它可以将表示长度的数字转换为字符串,并根据所需的格式进行格式化。

LengthFormatter支持多种格式选项,包括以不同单位(如英尺、米、千米)表示长度,以及在文本中添加度量单位符号等。

例如,以下代码将一个Double值表示为“1.23 km”字符串:

swift
let lengthFormatter = LengthFormatter()
let distance = 1234.56
let formattedDistance = lengthFormatter.string(fromValue: distance, unit: .kilometer)
print(formattedDistance) // "1.23 km"
let lengthFormatter = LengthFormatter()
let distance = 1234.56
let formattedDistance = lengthFormatter.string(fromValue: distance, unit: .kilometer)
print(formattedDistance) // "1.23 km"

LengthFormatter还可以根据地区设置显示格式。例如,在德国,LengthFormatter会将公里表示为“km”,而在美国,它会将其表示为“mi”。

此外,LengthFormatter还支持在数字前面或后面添加文本,例如“距离:1.23 km”。

KeyPath

Swift5.2引入了 KeyPath<Root,Value>,这是个泛型类型。 参考: 探索Swift中KeyPath的使用 - 掘金

swift
// 用来表示从 Root 类型到某个 Value 属性的访问路径.既然它是一个类型,你就可以在变量中存储、传递、操作这个类型。
// 例如: 这里代表 Hike.Observation 这个类型如何访问到  Range<Double>
var path: KeyPath<Hike.Observation, Range<Double>>
// 用来表示从 Root 类型到某个 Value 属性的访问路径.既然它是一个类型,你就可以在变量中存储、传递、操作这个类型。
// 例如: 这里代表 Hike.Observation 这个类型如何访问到  Range<Double>
var path: KeyPath<Hike.Observation, Range<Double>>

根据keyPath访问:

model[keyPath: mypath]
model[keyPath: mypath]

数组操作中的 map和lazy.map

在Swift中,数组操作的map和lazy.map都是用于对数组中的元素进行转换操作的方法,但它们的执行方式有所不同。

  1. map方法会直接生成一个新的数组,并将转换后的元素存放在其中,需要等待所有的转换操作都执行完后,才会返回一个新的数组。这种方式产生了临时的数组实例,当原数组比较大时,可能会占用较多的内存空间。
  2. lazy.map方法则是使用惰性计算的方式,先创建一个新的序列对象,然后在遍历这个序列的同时,对每个元素进行转换操作,并返回一个新的序列对象。只有在需要这些元素时,才会对转换操作进行实际的计算,避免了一次性分配大量的内存空间。
    因此,当需要对一个大数组进行转换操作时,如果使用map方法,会产生大量的临时数组,占用大量的内存;而使用lazy.map方法,可以避免这种情况,减少内存的占用。但是,由于使用了惰性计算,所以在对结果进行遍历时,可能会出现一定的性能影响。

日期格式化

swift
Text("Goal Date: ") + Text(profile.goalDate, style: .date)
Text("Goal Date: ") + Text(profile.goalDate, style: .date)

cornerRadius(.infinity)

在 SwiftUI 中,cornerRadius() 是一个用于设置视图的圆角的修饰符。而 cornerRadius(.infinity) 表示将视图的圆角设置为无限大,使得视图的四个角完全变为圆形,没有任何角度。

这种设置在创建按钮、卡片、标签等圆形或圆角视图时很有用。通常情况下,我们可以设置一个固定的半径来产生具有一定角度的圆角效果,但是当我们想要完全没有角度时,就需要将半径设置为无限大,即 cornerRadius(.infinity)。这样的设置可以使一个矩形视图变为一个完全圆形的视图,或使视图的四个角完全变为圆形,没有任何锐角。

ios16 中的NavigationStack 和 NavigationPath

ios - NavigationView and NavigationLink on button click in SwiftUI? - Stack Overflow

In iOS 16 we can access the NavigationStack and NavigationPath.

ios - SwiftUI - how to avoid navigation hardcoded into the view? - Stack Overflow

@Environment

@Environment是SwiftUI的一种特殊属性,可以获取当前应用程序的环境变量,并让环境变量的值可用于视图中。

@Environment可以获取当前应用程序的环境变量,例如:

  • .colorScheme:获取当前使用的外观色彩方案(明/暗)。
  • .presentationMode:获取当前包含视图的PresentationMode(例如,是否以全屏模式呈现该视图)。
  • .locale:获取当前语言环境。
    通过获取@Environment对象,您可以轻松地在您的视图中使用这些环境变量的值,并根据需要调整视图的外观或行为。

另外,在SwiftUI中,可以使用@EnvironmentObject,通过设置属性观察器来自动监控环境对象的变化。这使得您可以轻松地自动更新您的视图,而无需编写繁琐的手动代码。

resizable

在 SwiftUI 中,resizable 是用于调整视图大小的修饰符。它可以与任何可调整大小的视图元素(如 Image 和 Shape)一起使用。
使用 resizable 时,可以将其与可选参数一起使用,即指定所需大小的宽度和高度。接下来,SwiftUI 将按比例缩放视图元素,以适应指定的大小。
例如,可以在 SwiftUI 中使用如下代码调整图像的大小:

swift
Image("example-image")
    .resizable()
    .frame(width: 200, height: 200)
Image("example-image")
    .resizable()
    .frame(width: 200, height: 200)

以上代码指定了该图像元素的宽度和高度均为 200 点。在这个例子中,使用 resizable() 修饰符告诉 SwiftUI 图像可以缩放大小,以使其适应到确定的大小。

另外,如果不指定宽度和高度参数,那么 SwiftUI 将自动调整视图元素的大小,以使其适应所在的容器大小。