SwiftUI — Data Flow Through Swif
这一次我们来介绍一下 SwiftUI 里的属性装饰器@State, @Binding,@EnvironmentObject
@States
@frozen @propertyWrapper public struct State<Value> : DynamicProperty {
/// Initialize with the provided initial value.
public init(wrappedValue value: Value)
/// Initialize with the provided initial value.
public init(initialValue value: Value)
/// The current state value.
public var wrappedValue: Value { get nonmutating set }
/// Produces the binding referencing this state value
public var projectedValue: Binding<Value> { get }
}
@propertyWrapper 标注和之前提到的 的 @_functionBuilder类似 例如下面@State修饰的代码等同于 var showDetail = State(initialValue: false),会把使用过@State修饰器的属性存储到self上,但是这个属性和 View struct 是隔离的. 当@State装饰过的属性发生了变化,SwiftUI 会根据新的属性值更新视图
struct ContentView: View {
@State var showFavoritesOnly: Bool = false
//var showDetail = State(initialValue: false)
var body: some View {
VStack{
Button(action: {
self.showFavoritesOnly.toggle()
}) {
Text("Change")
}
if showFavoritesOnly {
Text("showFavoritesOnly")
}else{
Text("showAll)
}
}
}
}
@Binding
上面例子是showDetail这个属性 button 不会自动修改,但有些控件是可以直接修改,就可以把一个视图的属性传至控件中,例如下面DetailView里面的Toggle就可以,但是又不能直接的传递给控件,因为在 Swift 中值的传递形式是值类型传递方式,也就是传递出去的是一个拷贝过的值。但是通过 @Binding 修饰器修饰后,用$也等同于@Binding,属性变成了一个引用类型,传递变成了引用传递,这样父子视图的状态就能关联起来了。
struct ContentView: View {
@State var isFavorite: Bool
var body: some View {
VStack{
/// The binding value, as "unwrapped" by accessing `$foo` on a `@Binding` property.
DetailView(isFavorite: $isFavorite)
if isFavorite {
Text("isFavorite")
}else{
Text("NoFavorite")
}
}
}
}
struct DetailView: View {
@Binding var isFavorite: Bool
var body: some View {
Toggle(isOn: $isFavorite) {
Text("Change Favorite")
}
}
}
在 DetailView 视图里用 @Binding 修饰 isFavorite 属性, 在传递属性是使用 $ 来传递 isFavorite 属性的引用,这样 DetailView 视图就能读写父视图 ContentView 里的状态值了,并且值发生了修改 SwiftUI 会更新 ContentView 和 DetailView 视图
@EnvironmentObject
从名字上可以看出,这个修饰器是针对全局环境的。通过它,我们可以避免在初始 View 时创建 ObservableObject, 而是从环境中获取 ObservableObject
@Environment
SwiftUI 本身就有很多系统默认设定,我们可以使用@Environment 来获取到它们
@Environment(.editMode) var mode
@Environment(.calendar) var calendar: Calendar
@Environment(.locale) var locale: Locale
@Environment(.colorScheme) var colorScheme: ColorScheme
使用系统的还可以得到很多好处,例如你使用EditMode 就可以利用下面这些属性来完成一个编辑页面
public enum EditMode {
/// The view content cannot be edited.
case inactive
/// The view is in a temporary edit mode.
///
/// The definition of temporary might vary by platform or specific control.
/// As an example, temporary edit mode may be engaged over the duration of
/// a swipe gesture.
case transient
/// The view content can be edited.
case active
/// Indicates whether a view is being edited.
public var isEditing: Bool { get }
/// Returns a Boolean value indicating whether two values are equal.
///
/// Equality is the inverse of inequality. For any values `a` and `b`,
/// `a == b` implies that `a != b` is `false`.
///
/// - Parameters:
/// - lhs: A value to compare.
/// - rhs: Another value to compare.
}
你可以监听各种状态
import SwiftUI
struct ProfileHost: View {
@Environment(\.editMode) var mode
@State var draftProfile = Profile.default
@EnvironmentObject var userData: UserData
var body: some View {
VStack(alignment: .leading, spacing: 20) {
HStack {
if self.mode?.wrappedValue == .active {
Button("Cancel") {
self.draftProfile = self.userData.profile
self.mode?.animation().wrappedValue = .inactive
}
}
Spacer()
EditButton()
}
//
if self.mode?.wrappedValue == .inactive {
ProfileSummary(profile: draftProfile)
} else {
ProfileEditor(profile: $draftProfile)
.onAppear {
self.draftProfile = self.userData.profile
}
.onDisappear {
self.userData.profile = self.draftProfile
}
}
}
.padding()
}
}
struct ProfileHost_Previews: PreviewProvider {
static var previews: some View {
ProfileHost()
}
}