修饰符:
修饰符的工作原理:每个修饰符都会创建一个应用了该修饰符的新结构体,而不是在视图上设置属性。
Button("Hello World") {
print(type(of: self.body))
}
.background(Color.red)
.frame(width: 200, height: 200)
Swift的type(of:)
方法会打印特定值的确切类型,在这种情况下,它将打印以下内容:ModifiedContent<ModifiedContent<Button<Text>, _BackgroundModifier<Color>>, _FrameLayout>
使用ModifiedContent
类型堆叠——每个视图都需要一个视图进行转换以及要进行的实际更改,而不是直接修改视图。
SwiftUI使用ModifiedContent
构建数据。
SwiftUI使用通用的ModifiedContent
包装器创建新视图。
修饰符会返回新对象,而不是修改现有对象,因此我们可以创建一个将视图嵌入堆栈并添加另一个视图的对象。
在 SwiftUI 中,修饰符的功能类似于 CSS,用来在应用布局中定位和配置视图,如修改视图的大小、背景、添加动画、添加手势等等。View 协议通过扩展提供了大量的修饰符,它们以协议方法的形式给出,同时提供了默认实现。
修饰符的效果具有传递性,也就是说,父视图上使用的修饰符也会影响到其所有子视图,除非子视图显式的调用修饰符来覆盖这种效果。
之所以能以链式的方式调用修饰符,是因为每个修饰符方法的返回值是 some View
(如frame()
的声明),仍然是一个视图,所以可以在新的视图的基础上继续调用修饰符。
需要注意的是,在链式调用的过程中,修饰符的顺序会对实际效果产生影响。相同的两个修饰符以不同的顺序调用,呈现的结果可能是不一样的。
通过po type(of: text)
来查看 text
的类型。
SwiftUI 还提供了方法让我们可以自定义修饰符。
条件修饰符:
环境修饰符:许多修饰符可以应用于容器,这就同时将同一个修饰符应用于多个视图。视图的修饰符优先于环境修饰符。
自定义修饰符:需要创建一个自定义的ViewModifier
结构体,该结构体可以实现我们想要的功能
在SwiftUI中,在View上大多数调用的方法都称为Modifier,一种是为原地Modifier,
另外一种为封装类Modifier。原地Modifier是返回同样类型的View,
封装类Modifier则可以返回不同类型的View,在开发中我们经常需要自定义
ViewModifier来对View进行特定的变换操作。
原生的修饰符不考虑顺序,自定的修饰符有顺序要求,顺序不一样结果可能不一样。
https://www.jianshu.com/p/9934aa924043
struct Title: ViewModifier {
func body(content: Content) -> some View {
content
.font(.largeTitle)
.foregroundColor(.white)
.padding()
.background(Color.blue)
.clipShape(RoundedRectangle(cornerRadius: 10))
}
}
使用自定义的修饰符:
Text("Hello World")
.modifier(Title())
属性包装器:
@Binding 可以向子视图传值, @Binding修饰的变量不需要默认值。我们会把一个视图的属性传至子节点中,但是又不能直接的传递给子节点,因为在 Swift 中值的传递形式是
值类型
传递方式,也就是传递给子节点的是一个拷贝过的值。但是通过 @Binding 修饰器修饰后,属性变成了一个引用类型
,传递变成了引用传递,这样父子视图的状态就能关联起来了。给子视图的属性和它父视图的属性建立关联,我们必须做3件事情。在子视图的属性前面写关键词@Binding
, 父视图的属性前面得用@State
修饰,当把父视图的@State
属性传到子视图的时候,在属性名前面加上$
。这样我们就建立了关联。无论什么时候我们在子视图里面改变该属性,父视图里面的属性也会被修改。@Binding 包含了@State的功能。
@Binding
,它使我们可以将一个视图的@State
属性连接到一些基础模型数据。@Binding和@State搭配使用的,父view用@State,子view用@Binding,以实现父view和子view的数据绑定。当数据是从外部传递的,并且需要和外部保持绑定时,就用@Binding修饰该变量。
@State @State修饰的数据只用于当前view内部使用,所以@State常与Private搭配使用。
@State
变量需要一个默认值, 用来管理当前页面的状态,当被@State
修饰的属性值改变的时候,它所在的View就会重新渲染。在任何一个值发生更改时都会更新UI。在幕后,实际发生的情况是,每当我们的结构体中的值更改整个结构体时,它就像每次我们为新用户的名字或姓氏输入值时一样(创建了一个新的结构体)。每次我们修改该结构体的属性时,Swift实际上是在创建该结构的新实例。@State
能够发现这个变化,并自动重新加载我们的视图。修改属性的结构体方法使用
mutating
关键字吗?这是因为,如果我们将结构体的属性创建为变量,但结构体本身是常量,则无法更改属性——Swift需要能够在属性更改时销毁和重新创建整个结构体,而常量结构则不可能这样做。类不需要mutating
关键字,因为即使类实例标记为常量Swift,仍然可以修改变量属性。
@Environment SwiftUI让我们的每个视图都可以访问一个共享的信息池,称为环境(environment)。 通过 @Environment 修饰的属性,我们开一个监听系统级别信息的变换,系统的某些属性 发生了变换,我们定义的 view就会刷新,例如 @Environment(\.presentationMode) var presentationMode
@EnvironmentObject 整个应用程序中视图共享数据,把属性放进环境变量里,@EnvironmentObject
会在环境中自动查找一个实例,并且把找到的结果放进 当前修饰的属性中(例如@EnvironmentObject var user: User)。注意:如果环境中找不到 User
实例,你的应用会崩溃。所以请千万小心。环境对象必须由祖先视图提供。如果对象存活于整个app生命周期,并且大量view需要共享此对象,那么用@EnvironmentObject修饰。
@ObservedObject 它告诉SwiftUI监视类中的任何的更改公告。 针对跨越 View 层级的状态共享。如果你用 @ObservedObject 来修饰一个对象,那么那个对象必须要实现 ObservableObject 协议(
@ObservedObject
属性包装器只能用于符合ObservableObject
协议的类型),然后用 @Published 修饰对象里属性,表示这个属性是需要被 SwiftUI 监听的。和@State类似,但@ObservedObject用于修饰复杂的数据类型,而且只能修饰引用类型的数据。所以,如果你想要修饰一个复杂的struct数据,首先需要用class来包裹这个struct。可以用于组件之间的 数据传递。
import SwiftUI
class User: ObservableObject {
@Published var name = "Taylor Swift"
}
import SwiftUI
struct EditView: View {
@EnvironmentObject var user: User
var body: some View {
TextField("Name", text: $user.name)
}
}
let user = User()
var body: some View{
VStack {
EditView().environmentObject(user)
}
}
@ObservableObject 和@ObservedObject类似,针对跨越 View 层级的状态共享,需要在数据变化时通过某种手段向外发送通知 (比如手动调用 objectWillChange.send() 或者使用 @Published),来触发界面刷新。
@Published @Published
属性观察器, 修饰对象里属性,表示这个属性是需要被 SwiftUI 监听的。这个要和ObservableObject配合使用。每当类中的任何一个属性更改时,它都应该向任何正在关注它们的SwiftUI视图发送公告,让他们知道类发生了更改,以便可以重新加载它们。
import SwiftUI
/*
结构的body属性返回一个或多个场景,这些场景又提供了要显示的内容。该@main属性标识应用程序的入口点。
*/
@main
struct DriverHomeSwiftUIApp: App {
WindowGroup {
ContentView().environmentObject(User())//这里User添加到环境变量中,注意一定要添加到祖先的环境变量,
}
}
}
import SwiftUI
class User: ObservableObject {
@Published var name = "Taylor Swift"
}
//使用:
@EnvironmentObject var user: User
var body: some View{
VStack {
Text(user.name)
}
}
符合ObservableObject
协议的类可以使用SwiftUI的@Published
属性包装器自动声明对属性的更改,以便使用该对象的所有视图都可以重新调用其body
属性,并与数据保持同步
每个符合ObservableObject
的类都会自动获得一个名为objectWillChange
的属性。这是一个发布者,这意味着它执行与@Published
属性包装器相同的工作:它通知正在观察该对象的所有视图一些重要的更改。顾名思义,此发布者应在我们进行更改之前立即触发,这使SwiftUI可以检查UI的状态并为动画更改做准备。
class DelayedUpdater: ObservableObject {
@Published var value = 0
init() {
for i in 1...10 {
DispatchQueue.main.asyncAfter(deadline: .now() + Double(i)) {
self.value += 1
}
}
}
}
使用:
@ObservedObject var updater = DelayedUpdater()
var body: some View {
Text("Value is: \(updater.value)")
}
如果删除@Published属性包装,您将看到UI不再更改。在后台,所有的asyncAfter()工作仍在进行,但不会导致UI刷新,因为没有发出更改通知。
使用objectWillChange实现以上的功能(不使用
@Published
属性包装器)
class DelayedUpdater: ObservableObject {
var value = 0 {
willSet {
objectWillChange.send()
}
}
init() {
for i in 1...10 {
DispatchQueue.main.asyncAfter(deadline: .now() + Double(i)) {
self.value += 1
}
}
}
}
使用:
@ObservedObject var updater = DelayedUpdater()
var body: some View {
Text("Value is: \(updater.value)")
}
@escaping: 用来修饰闭包,表示此闭包可在此方法运行的范围之外使用,请保留其内存,知道闭包运行完成。
@Published是SwiftUI最有用的包装之一,允许我们创建出能够被自动观察的对象属性,SwiftUI会自动监视这个属性,一旦发生了改变,会自动修改与该属性绑定的界面。