首先swiftUI是一个声明式UI框架,是不是最近对这个概念听的比较多了。没错React也是声明式的,flutter也是声明式的。
那么什么是声明式呢?我们简单说一下:
我们描述我们需要什么界面,并不是告诉计算机我们一步步要怎么做,例如:
“我需要一个界面,它是一个 VStack(垂直布局),里面有一个开关,开关的值与 switchValue 的布尔值绑定,VStack 里接下来是一个 Text,它的值当 switchValue 为 true 时是 foo,否则是 bar
声明式的优势在于:降低状态增加时界面维护的复杂度。
有关于声明式大家可以看一下这篇文章:从 SwiftUI 谈声明式 UI 与类型系统
进入正题:
将可变值作为状态进行管理
如果视图需要对于创建的数据进行实时的监控,也就是当值改变时,视图显示相应的改变,请使用State:
一个属性包装器类型,可以读取和写入由SwiftUI管理的值。
SwiftUI会管理你声明为状态的任何属性值,当状态值改变时,视图销毁重新以新值创建。同时为了保证从任何线程更改状态属性都是安全的,要将状态属性的声明方式设置为private。
要将状态属性给视图层次结构中的另一个视图,就需要带$前缀:
struct PlayerView: View {
var episode: Episode
@State private var isPlaying: Bool = false
var body: some View {
VStack {
Text(episode.title)
Text(episode.showTitle)
PlayButton(isPlaying: $isPlaying)
}
}
}
通过绑定共享对状态的访问
当视图要与子视图共享状态控制时,也就是两个body,使用Binding属性包装,
它会在两个视图之间创建双向链接,Binding将属性链接到一开始声明的视图位置,就像指针指向对数据存储的位置,自己不再直接存储数据。
用法:子视图使用@Binding包装属性,父视图使用State属性声明改属性:
///子视图创建Binding
struct PlayButton: View {
@Binding var isPlaying: Bool
var body: some View {
Button(action: {
self.isPlaying.toggle()
}) {
Image(systemName: isPlaying ? "pause.circle" : "play.circle")
}
}
}
///父视图使用State属性包装器包装改属性
struct PlayerView: View {
var episode: Episode
@State private var isPlaying: Bool = false
var body: some View {
VStack {
Text(episode.title)
Text(episode.showTitle)
PlayButton(isPlaying: $isPlaying)
}
}
}
动画状态转换
当视图状态改变时,SwiftUI会立即发生变化,如果你不想视图变化的那么突兀,要加入平的视觉过渡的话,可以添加控制动画:
struct ContentView: View {
@State private var isPlaying: Bool = false
var body: some View {
Button(action:{
///置由布尔值控制的更改的动画:withAnimation(_:_:)isPlaying
withAnimation(.easeInOut(duration:1)) {
self.isPlaying.toggle()
print("\(isPlaying)")
}
}){
///通过在animation函数的尾随闭包内部进行更改,您可以使SwiftUI对取决于包装值的任何事物进行动画处理,例如对按钮图像的缩放效果:isPlaying
Image(systemName: isPlaying ? "square.and.arrow.up" : "square.and.arrow.up.fill")
.scaleEffect(isPlaying ? 1 : 1.5)
}
}
}
在应用程序中管理模型数据
要使模型中的数据更改能被视图UI监控到,那么我们需要对模型类型使用协议。创建一个可观察对象的类:@ObservableObject,发布你的属性:@Published
class Book: ObservableObject {
@Published var title = "Great Expectations"
///当您不需要已发布的属性时,请避免它的开销。仅发布可以更改并且对用户界面很重要的属性。例如,Book该类可能具有identifier初始化后永不更改的属性:
let identifier = UUID() // A unique identifier that never changes.
}
监视可观察对象的变化
在使用中,要告诉SwiftUI监视可观察对象,请将属性添加到属性的声明中:
ObservedObject
struct BookView: View {
@ObservedObject var book: Book
var body: some View {
Text(book.title)
}
}
你可以将观察对象的各个属性传递给子视图,如上所示。当数据更改时(例如从磁盘加载新数据时),SwiftUI将更新所有受影响的视图。您还可以将整个可观察对象传递给子视图,并在视图层次结构的各个级别共享模型对象:
struct BookView: View {
@ObservedObject var book: Book
var body: some View {
BookEditView(book: book)
}
}
struct BookEditView: View {
@ObservedObject var book: Book
// ...
}
在整个应用程序中共享对象
如果要在整个应用程序中使用数据模型对象,但又不想将其传递给层次结构太多,则可以使用view修饰符将该对象放入环境中:environmentObject(_😃
@main
struct BookReader: App {
@StateObject var library = Library()
var body: some Scene {
WindowGroup {
LibraryView()
.environmentObject(library)
}
}
}
然后,通过声明具有属性的属性,可以向其应用修饰符的视图的任何后代视图访问数据模型实例:EnvironmentObject
struct LibraryView: View {
@EnvironmentObject var library: Library
// ...
}
如果使用环境对象,则可以将其添加到应用程序层次结构顶部的视图中,如上所示。或者,您可以将其添加到视图层次结构中子树的根视图中。无论哪种方式,请记住也将其添加到使用该对象的任何视图的预览提供者中,或者将其添加到使用该对象的后代的任何视图中:
struct LibraryView_Previews: PreviewProvider {
static var previews: some View {
LibraryView()
.environmentObject(Library())
}
}
使用绑定创建双向连接
当允许用户更改数据时,注意使用相应的绑定,这样可以确保更新的同步,通过在对象名称前加上美元符号($),可以绑定到观察到对象。例:
struct BookEditView: View {
@ObservedObject var book: Book
var body: some View {
TextField("Title", text: $book.title)
}
}