- swiftui 是 apple 新出面向未來、跨多端解決方案、聲明式編程
swiftui 最新版本 2.0 但是需要 ios 14 支持,多數現在還用的是 ios 13 所以很多不完善的東西都用 swiftuix 以及各種庫代替,bug 也是層出不窮
2.下面是鄙人對@state@published@observedobject 理解,如有不對,還請指出
1.@ state 居間
因為 swiftui view 採用的是結構體,當創建想要更改屬性的結構體方法時,我們需要添加 mutating 關鍵字,例如:
mutating func doSomeWork()
然而,Swift 不允许我们创建可变计算属性,这意味着我们不能编写mutating var body: some View——这是不允许的。
@state 允許我們繞過結構體的限制:我們知道不能更改它們的屬性,因為結構是固定的,但是@state 允許 swiftui 將該值單獨存儲在可以修改的地方。
是的,這感覺有點像作弊,你可能想知道為什麼我們不使用類-它們可以自由修改。但是相信我,這是值得的:隨著你的進步,你會了解到 swiftui 經常破壞和重新創建你的結構體,所以保持它們的小而簡單的結構對性能很重要。
提示:在 swiftui 中存儲程式狀態有幾種方法,您將學習所有這些方法。@ state 是專門為存儲在一個視圖中的簡單屬性而設計的。因此,蘋果建議我們向這些屬性添加私有訪問控制,比如:@state private var tapcount = 0。
2.@ published +@observedobject 居間
@published 是 swiftui 最有用的包裝之一,允許我們創建出能夠被自動觀察的對象屬性,swiftui 會自動監視這個屬性,一旦發生了改變,會自動修改與該屬性綁定的界面。
比如我們定義的數據結構 model,前提是@published 要在 observableobject 下使用 然後用@observedobject 來引用這個對象,當然@state 不會報錯,但是無法更新
class BaseModel: ObservableObject{
@Published var name:String = ""
}
struct ContentView: View{
@ObservedObject var baseModel:BaseModel = BaseModel()
var body: some View{
Text("用户名\(baseModel.name)")
Button(action: {
baseModel.name = "Renew"
}, label: {
Text("更新视图")
})
}
}
3.最重要的部分 (代碼注釋部分最為主要,務必看完)
雖然上面案例運行中什麼都正常展示加載,但是到了實際項目中,卻一堆 bug,這是如何導致的,如果對 這三種狀態跟 view 綁定的關係不了解,很可能給自己留下隱患
先來看組案例
//// MASK - 先定义两个Model 继承 ObservableObject
class WorkModel: ObservableObject {
@Published var name = "name"
@Published var count = 1
}
class UserModel: ObservableObject {
@Published var nickname = "nickname"
@Published var header = "http://www.baidu.com"
}
//// MASK - View显示层
struct ContentView: View {
@ObservedObject var workModel:WorkModel = WorkModel()
@ObservedObject var userModel:UserModel = UserModel()
var body: some View {
VStack{
Text("work.count \(workModel.count)")
Text("work.name \(workModel.name)")
Text("user.nickname \(userModel.nickname)")
Text("user.header \(userModel.header)")
Button(action: {
userModel.nickname = "Renew"
userModel.header = "http://..."
workModel.name = "work name"
workModel.count += 1
}, label: {
Text("更新数据")
})
}
}
}
不出意外上面代碼點擊按鈕就會更新數據,但是如果我們有個包裝類呢
class WrapperModel: ObservableObject{
@ObservedObject var workModel:WorkModel = WorkModel()
@ObservedObject var userModel:UserModel = UserModel()
}
struct ContentView: View {
@ObservedObject var wrapperModel:WrapperModel = WrapperModel()
var body: some View {
VStack{
Text("work.count \(wrapperModel.workModel.count)")
Text("work.name \(wrapperModel.workModel.name)")
Text("user.nickname \(wrapperModel.userModel.nickname)")
Text("work.header \(wrapperModel.userModel.header)")
Button(action: {
wrapperModel.userModel.nickname = "Renew"
wrapperModel.userModel.header = "http://..."
wrapperModel.workModel.name = "work name"
wrapperModel.workModel.count += 1
}, label: {
Text("更新数据")
})
}
}
}
這時候點擊按鈕還會更新數據嗎,答案是否定的,那這個是為啥呀???
因为SwiftUI更新数据的前提是触发
第一层 绑定的对象 wrapperModel下的属性(字段)发生更新才会调用视图层更新数据
但是 第一次下绑定的对象还绑定了 @ObservedObject 或者其他类型的对象呢?
还会触发第一次对象属性更新吗,答案是不能的
你可以在 didSet 事件里面捕捉,是捕捉不到的,所以视图是不会更新的,那这还有其他解决方案吗
有:
調用對象 wrappermodel.objectwillchange.send() 方法告訴 view 層 我更新 但是這個就是絕對的了嗎?:不是 如果層次再深一點的 model 還是有 bug,觸發不了
4.總結以及解決方案
/// 既然我们知道View 跟 状态绑定的关系
/// 是以第一继承ObservableObject 类 下的属性(字段)更新来更新视图的
/// 那我们可以给 ObservableObject 加一个 无关紧要的字段,然后编写一个方法,来通知更新
class BaseobservableObject: ObservableObject {
///
/// 注意
/// 接收 子类model 时候要用 @ObservedObject 不能用 @Published
/// 因为SwiftUI 更新机制是当前对象有 @Published 字段更新 就会调用View视图进行更新
/// 在BaseModel里面实现 notifyUpdate 更新当前对象 _lastUpdateTime 字段,实现自身全部字段更新
@Published private var _lastUpdateTime: Date = Date()
///
/// 通知更新
public func notifyUpdate() {
_lastUpdateTime = Date()
}
}
/// 那当我们 包装类下的对象更新的时候
/// 可以直接 调用包装类 notifyUpdate() 方法更新当前对象属性,来达到更新View 的效果
/// 顾忌:如果多次调用 notifyUpdate() View会刷新两边吗
/// 答案是否定的,再一次函数栈里面 多次调用 notifyUpdate() View也只更新一次
/// 当子类继承了 BaseobservableObject 对象
/// 那么该对象下面属性其实可以不需要在写 @ObservedObject 或者 @Published 了
/// 因为更新属性之后调用了 notifyUpdate() 达到了更新整个对象的效果,所以可以省略了
5.其他知識
/// MASK - 实现一个基础Model类,其他Model继承该类
class BaseModel: ObservableObject {
@Published var isLoading = false
}
class SonModel: BaseModel {
@Published var name = "name"
@Published var count = 1
}
struct ContentView: View {
@ObservedObject var sonModel:SonModel = SonModel()
var body: some View {
VStack{
Text("name \(sonModel.name)")
Button(action: {
sonModel.name = "Renew"
}, label: {
Text("加载")
})
}
}
}
/// 问题来了,现在我View 层我直接引用
/// 照说这时候应该 Text 提示信息是 name Renew 但是点击没反应
/// 啥原因,问题其实还是跟上面的问题有点相似
/// SonModel 不是直接继承于 ObservableObject 类的
/// 所以,直接继承 ObservableObject 下的属性(字段)没更新,就不会更新View
/// 最简单的解决办法就是 更新直接继承 ObservableObject(父对象) 里面的随便一个属性