SwiftUISwiftUI

SwiftUI-数据流

2020-02-04  本文已影响0人  YungFan

SwiftUI中的界面是严格数据驱动的:运行时界面的修改,只能通过修改数据来间接完成,而不是直接对界面进行修改操作。

数据处理的基本原则

五个数据流工具

可以通过它们建立数据和视图的依赖关系

注意:后面四种使用 Swift 5.1 的新特性 Property Wrapper来实现的一种属性装饰语法糖(修饰器/装饰器)

Property

import SwiftUI

struct Model {
    var title: String
    var info: String
}

struct ContentView : View {
    
    let model = Model(title: "WWDC 2019", info: "SwiftUI是一个全新的UI框架")
    var body: some View {
        
        VStack {
            // 内部使用前面定义的let和var
            Text(model.title).font(.title)
            Text(model.info)
        }
    }
}

@State

@Binding

struct ContentView: View {
    // 用@State修饰需要改变的变量
    @State private var count: Int = 0
    
    var body: some View {
        VStack {
            Text("\(count)").foregroundColor(.orange).font(.largeTitle).padding()
            // $访问传递给另外一个UI
            CountButton(count: $count)
        }
    }
}

struct CountButton : View {
    // 用@State修饰,绑定count的值
    @Binding var count: Int
    
    var body: some View {
        Button(action: {
            // 此处修改数据会同步到上面的UI
            self.count = self.count + 1
            
        }) { Text("改变Count")
        }
    }
}

@State VS @Binding

@State只能在当前修饰的属性改变时会触发UI刷新,所以很适合值类型,因为对值类型里面属性的更新,也会触发整个值类型的重新设置。不过值类型在传递时会发生复制操作,所以给传递后的值类型即使属性更新了也不会触发最初的传过来的值类型的重新赋值,所以界面并不会刷新,此时需要用@Binding,因为它可以将值类型转为引用类型,这样在传递时,其实是一个引用,任何一方修改属性都会触发值类型的重新设置,UI界面也随之更新。

ObservableObject

class User: ObservableObject {
    @Published var name = ""  // @Published修饰需要监听的属性,一旦变化就会发出通知,它是发布者
    @Published var address = ""
}


struct ContentView: View {
    @ObservedObject var User = User()  // @ObservedObject修饰ObservableObject
    var body: some View {
    }
}
class UserSettings: ObservableObject {
    // 有可能会有多个视图使用,所以属性未声明为私有
    @Published var score = 123
}

struct ContentView: View {
    @ObservedObject var settings = UserSettings()

    var body: some View {
        VStack {
            Text("人气值: \(settings.score)").font(.title).padding()
            Button(action: {
                self.settings.score += 1
            }) {
                Text("增加人气")
            }
        }
    }
}
class UserSettings: ObservableObject {
    
    // 1.添加发布者,实现一个属性,名字不能乱写,否则没有效果
    let objectWillChange = ObservableObjectPublisher()
    
    // 2.只要name发生更改,属性观察器就会调用,告诉objectWillChange发布者发布有关我们的数据已更改的消息,以便所有订阅的视图都可以刷新的消息
    var name = "" {
        willSet {
            
            // 3.使用发布者
            objectWillChange.send()
        }
    }
}


struct ContentView: View {
    @ObservedObject var settings = UserSettings()
    
    var body: some View {
        VStack {
            TextField("姓名", text: $settings.name)
                .textFieldStyle(RoundedBorderTextFieldStyle()).padding()
            
            Text("你的姓名: \(settings.name)")
        }
    }
}

@EnvironmentObject

// 和@ObservableObject一样
class User: ObservableObject {
    @Published var name = ""
    @Published var address = ""
}


struct ContentView: View {
    @EnvironmentObject var User   // 注意这里不需要初始化
    var body: some View {
    }
}
class UserSettings: ObservableObject {
    @Published var score = 123
}

struct ContentView: View {
    
    @EnvironmentObject var settings: UserSettings
    
    var body: some View {        
        NavigationView{            
            VStack {
                // 显示score
                Text("人气值: \(settings.score)").font(.title).padding()
                // 改变score
                Button(action: {
                    self.settings.score += 1
                }) {
                    Text("增加人气")
                }
                // 跳转下一个界面
                NavigationLink(destination: DetailView()) {
                    Text("下一个界面")
                }
            }
        }
    }
}

struct DetailView: View {
    
    @EnvironmentObject var settings: UserSettings
    
    var body: some View {
        VStack {
            Text("人气值: \(settings.score)").font(.title).padding()
            Button(action: {
                self.settings.score += 1
            }) {
                Text("增加人气")
            }
        }
    }
}

// 需要注意此时需要修改SceneDelegate,传入environmentObject
window.rootViewController = UIHostingController(rootView: ContentView().environmentObject(UserSettings()))

总结

SwiftUI中视图不再是一系列操作事件而是数据的函数式表现。通过这种编程思想的改变,SwiftUI 帮助你管理各种复杂的界面和数据的处理,开发者只需要关注数据的业务逻辑即可,但是要想管理好业务数据,还得要遵循数据的流转规范才可以,官方为我们提供了一个数据流图。

数据流图

从上图可以看出SwiftUI 的数据流转过程:

注意

  1. 在 SwiftUI 中,开发者只需要构建一个视图可依赖的数据源,保持数据的单向有序流转即可,其他数据和视图的状态同步问题 SwiftUI 帮你管理,所以 ViewController 在这里也就不需要了,再也不存在臃肿瘦身的问题了。
  2. SwiftUI 的界面不再像 UIKit 那样,用 ViewController 承载各种 UIVew控件,而是一切皆 View,所以可以把 View 切分成各种细粒度的组件,然后通过组合的方式拼装成最终的界面,这种视图的拼装方式大大提高了界面开发的灵活性和复用性,视图组件化并任意组合的方式是 SwiftUI 官方非常鼓励的做法。
  3. Property、 @State、 @Binding 一般修饰的都是 View 内部的数据。
  4. @ObservedObject、 @EnvironmentObject 一般修饰的都是 View 外部的数据:
    • 系统级的消息
    • 网络或本地存储的数据
    • 界面之间互相传递的数据
上一篇 下一篇

猜你喜欢

热点阅读