SwiftUI is a modern way to declare user interfaces for any Apple platform. Create beautiful, dynamic apps faster than ever before.react
隨着整個軟件開發領域的發展,近年來涌現的 React Native ,Weex,Flutter技術,逐漸證實了一個事實:在 UI 開發領域,描述性語言是最佳方式。 SwiftUI使用易於閱讀和編寫的聲明式 Swift 語法,建立了一個很是接近HTML 描述語言的 DSL(domain-specific language)。 蘋果一直在嘗試所見即所得的理念,早期 xib 到 stroyboard,可是發展的都不大順暢。ios
簡單對比一下其餘語法:git
SwiftUIgithub
VStack { Text("a1") Text("a2") Text("a3") } 複製代碼
Flutterweb
Stack( children: <Widget>[ Text("a1"), Text("a2"), Text("a3"), ], ) 複製代碼
HTMLspring
<div>
<label>a1</label>
<label>a2</label>
<label>a3</label>
</div>
複製代碼
拖放: 只需拖動畫布上的控件便可在用戶界面中排列組件。單擊打開檢查器能夠拖具體控件,也能夠拖控件的字體、顏色、圓角、對齊方式等屬性。代碼中能夠任意拖,可是畫布只能拖控件,不能拖屬性。畫布相關修改後,代碼中自動生成。編程
cmd+鼠標左鍵,呼出快速操做,不只在代碼中支持,預覽畫布中也支持,Inspect功能很強大。canvas
預覽: 右上角:Editor and Canvas,快捷鍵:shift + cmd + enter。 如今你能夠爲任何 SwiftUI 視圖建立一個或多個預覽,以獲取示例數據並配置幾乎全部用戶可能看到的內容,諸如大字體、本地化或黑暗模式。預覽還能夠顯示你的 UI 在任何設備和任何方向上的效果。 預覽 是 Apple 用來對標 RN 或者 Flutter 的 Hot Reloading 的開發工具。 Xcode會自動刷新,也能夠手動刷新,快捷鍵:option + cmd + Pswift
簡單排列Label、Button、Image:bash
struct ViewsTest: View { var body: some View { VStack { Text("Label") .foregroundColor(Color.orange) .font(Font.system(size: 17)) Button(action: { print("click") }) { Text("Button") } Image("image_write") .onTapGesture { print("image click") } } } } struct ViewsTest_Previews: PreviewProvider { static var previews: some View { ViewsTest() } } 複製代碼
View協議的定義:
public protocol View { /// The type of view representing the body of this view. /// /// When you create a custom view, Swift infers this type from your /// implementation of the required `body` property. associatedtype Body : View /// Declares the content and behavior of this view. var body: Self.Body { get } } 複製代碼
PreviewProvider協議定義:
public protocol PreviewProvider : _PreviewProvider { /// The type of the previews variable. associatedtype Previews : View /// Generates a collection of previews. static var previews: Self.Previews { get } /// Returns which platform to run the provider on. /// /// When `nil`, Xcode infers the platform based on the file the /// `PreviewProvider` is defined in. This should only be provided when the /// file is in targets that support multiple platforms. static var platform: PreviewPlatform? { get } } 複製代碼
_PreviewProvider協議的定義就看不到源碼了。
some View 這種寫法使用了 Swift 5.1 的 Opaque return types 特性。它向編譯器做出保證,每次 body 獲得的必定是某一個肯定的,遵照 View 協議的類型,可是請編譯器「網開一面」,不要再細究具體的類型。相似於OC id。
UIKit與SwiftUI相同功能組件對比:
View Controllers
先上代碼:
VStack(alignment: .center, spacing: 10) { Text("a") Text("b") HStack(alignment: .top, spacing: 20) { Text("c") Text("d") } } 複製代碼
a、b、(c、d)垂直排列,c、d水平排列。
HStack:用於將子視圖在水平線上依次排列 VStack:用於將子視圖在垂直線上依次排列 ZStack:用於將子視圖在垂直於屏幕線上依次排列,能夠實現覆蓋子視圖。相似在 UIKit中向一個 View添加不一樣的 SubView。 支持各類Stack的嵌套。
看下VStack的定義:
public struct VStack<Content> : View where Content : View { /// Creates an instance with the given `spacing` and Y axis `alignment`. /// /// - Parameters: /// - alignment: the guide that will have the same horizontal screen /// coordinate for all children. /// - spacing: the distance between adjacent children, or nil if the /// stack should choose a default distance for each pair of children. @inlinable public init(alignment: HorizontalAlignment = .center, spacing: CGFloat? = nil, @ViewBuilder content: () -> Content) } 複製代碼
咱們發現返回值是Content協議類型,並使用@ViewBuilder進行標記。這裏使用了 Swift 5.1 的另外一個新特性:Funtion builders。
這個新特性的大概預覽:
// Original source code: @TupleBuilder func build() -> (Int, Int, Int) { 1 2 3 } // This code is interpreted exactly as if it were this code: func build() -> (Int, Int, Int) { let _a = 1 let _b = 2 let _c = 3 return TupleBuilder.buildBlock(_a, _b, _c) } 複製代碼
ViewBuilder的定義爲:
@_functionBuilder public struct ViewBuilder { /// Builds an empty view from an block containing no statements, `{ }`. public static func buildBlock() -> EmptyView /// Passes a single view written as a child view (e..g, `{ Text("Hello") }`) through /// unmodified. public static func buildBlock<Content>(_ content: Content) -> Content where Content : View } 複製代碼
使用 @_functionBuilder 進行標記的類型 (這裏的 ViewBuilder),能夠被用來對其餘內容進行標記。
HStack調用等效僞代碼,不能實際編譯:
HStack(alignment: .center,spacing: 20) { viewBuilder -> Content in let text1 = Text("c") let text2 = Text("d") return viewBuilder.buildBlock(text1, text2) } 複製代碼
Stack相似OC UIStackView,沒用過? 看這個:
若是兩個控件須要設置哪一個優先展現,使用佈局優先級 .layoutpriority(1) ,數字越大,優先級越高
HStack(alignment: .lastTextBaseline, spacing: 10) { // alignment對齊方式,spacing控件間距 Text("Hello").layoutPriority(100) Text("哈哈哈").layoutPriority(20).alignmentGuide(.lastTextBaseline) { d in d[.bottom] * 0.5 } Text("World").layoutPriority(10) } 複製代碼
系統提供Combine 庫,類比 reactive-cocoa,rxSwift 庫。
@State @Binding @EnvironmentObject @ObservedObject 等一系列的Combine提供的功能,支持實現數據綁定,view自動更新數據。
Combine 的鏈式調用和 SwiftUI 組件裏的 ViewModifier 雖然形似但內核是不同的,Combine 自己就是數據流處理的模式;而 SwiftUI 裏鏈式調用只是爲了形式上返回單一對象。
定義一個響應式狀態,它的變化會致使依賴它的視圖自動更新(單向)
示例:點擊play按鈕,label自動計數
struct DataFlowTest: View { @State var number : Int = 0 var body: some View { VStack { Text("play number is : \(self.number)") Button(action: { self.number += 1 }) { Text("Play") } } } } 複製代碼
視圖和數據的雙向綁定。 傳遞參數使用'$'符號
struct DataFlowTest: View { @State var number : Int = 0 var body: some View { VStack { Text("play number is : \(self.number)") PlayButton(count: $number) } } } struct PlayButton : View { @Binding var count : Int var body: some View { Button(action: { self.count += 1 }) { Text("Play") } } } 複製代碼
自定義綁定類型不能使用@Binding,須要使用@ObservedObject(以前使用@ObjectBinding) 做用等價於 @Binding,可是支持使用一個外部對象。
Xcode 11 beta 5 : BindableObject protocol is become ObservableObject and willChange become objectWillChange.
class UserData: ObservableObject { let objectWillChange = PassthroughSubject<Void, Never>() // 注意:變量名必須是這個objectWillChange var userName = "no name" { willSet { objectWillChange.send() } } } 複製代碼
struct DataFlowTest: View { @State var userData = UserData() var body: some View { VStack { Text("user name is : \(self.userData.userName)") PlayButton(userModel: userData) } } } struct PlayButton : View { @ObservedObject var userModel: UserData var body: some View { Button(action: { self.userModel.userName = "Tom" }) { Text("Play") } } } 複製代碼
用於實現單個model綁定多個view
struct ContentView: View { let userModel = UserData() var body: some View { NavigationView { List { Section() { NavigationLink(destination: DataFlowModelTest().environmentObject(userModel)) { Text("DataFlow model") } NavigationLink(destination: DataFlowModelTest2().environmentObject(userModel)) { Text("DataFlow model 2") } } } } } } 複製代碼
這時候修改某個view中的UserData對象,其餘view的值自動變換
struct DataFlowModelTest: View { @EnvironmentObject var userModel : UserData var body: some View { VStack { Text("change name :\(userModel.userName)") Button(action: { self.userModel.userName = "tom" }) { Text("change name") } } } } 複製代碼
在 SwiftUI 中,作動畫變的十分簡單。
@inlinable public func animation(_ animation: Animation?) -> some View
複製代碼
@State var showDetail = false var body: some View { Button(action: { self.showDetail.toggle() }) { Image("image_write") .imageScale(.large) .rotationEffect(.degrees(showDetail ? 90 : 0)) .animation(.linear(duration: 10)) .animation(nil) .scaleEffect(showDetail ? 3 : 1) .padding() .animation(.spring()) } } 複製代碼
SwiftUI 作的事情等效因而把以前的全部 modifier 檢查一遍,而後找出全部知足 Animatable 協議的 view 上的數值變化,好比角度、位置、尺寸等,而後將這些變化打個包,建立一個事務 (Transaction) 並提交給底層渲染去作動畫。
withAnimation() 接受一個指定所需動畫類型的參數。 好比,按鈕點擊後,字體顏色2s時間變化爲黃色:
@State var bgColor = Color.green var body: some View { Button(action: { withAnimation(.easeInOut(duration: 2)) { self.bgColor = Color.orange } }) { Text("change color") .background(bgColor) } } 複製代碼
這個方法至關於把一個 animation 設置到 View 數值變化的 Transaction 上,並提交給底層渲染去作動畫。
@inlinable public func transition(_ t: AnyTransition) -> some View
複製代碼
view支持直接調用.transition,便可實現轉場動畫。系統提供offset、scale、opacity、slide、move等。
ShowDetailView()
.transition(.slide)
複製代碼
自定義轉場動畫:
extension AnyTransition { static var customTransition: AnyTransition { let insertion = AnyTransition.move(edge: .trailing) .combined(with: .opacity) let removal = AnyTransition.scale .combined(with: .opacity) return .asymmetric(insertion: insertion, removal: removal) // insertion:入場動畫,removal:出場動畫 } } // 調用,跟系統的同樣 ShowDetailView() .transition(.customTransition) 複製代碼
Use the UIHostingController to bridge SwiftUI views into a UIKit view and view controller hierarchy.
UIHostingController 是一個 UIViewController 子類,它將負責接受一個 SwiftUI 的 View 描述並將其用 UIKit 進行渲染 (在 iOS 下的狀況)。UIHostingController 就是一個普通的 UIViewController,所以徹底能夠作到將 SwiftUI 建立的界面一點點集成到已有的 UIKit app 中,而並不須要從頭開始就是基於 SwiftUI 的構建。
eg:設置rootViewController爲swiftUI的view,須要使用UIHostingController構建
window.rootViewController = UIHostingController(rootView: ContentView())
複製代碼
Use the UIViewRepresentable protocol to bridge UIKit views into SwiftUI, not view controllers.
好比SwiftUI中須要加載WebView,SwiftUI是沒有WebView控件的,而WebKit的WKWebView是繼承自UIView,因此須要使用UIViewRepresentable中轉。
struct WebViewTestRepresentable : UIViewRepresentable { typealias UIViewType = WKWebView func makeUIView(context: UIViewRepresentableContext<WebViewTestRepresentable>) -> WebViewTest.UIViewType { return WKWebView() } func updateUIView(_ uiView: WebViewTest.UIViewType, context: UIViewRepresentableContext<WebViewTestRepresentable>) { let req = URLRequest(url: URL(string: "https://www.apple.com")!) uiView.load(req) } } struct WebViewTestView_Previews : PreviewProvider { static var previews: some View { WebViewTestRepresentable() } } 複製代碼
Create a structure that conforms to UIViewControllerRepresentable and implement the protocol requirements to include a UIViewController in your SwiftUI view hierarchy.
具體方法: 新建一個結構體,繼承自UIViewControllerRepresentable,實現Protocol:建立、更新UIViewController
struct UIKitVCRepresentable : UIViewControllerRepresentable { func makeUIViewController(context: UIViewControllerRepresentableContext<UIKitVCRepresentable>) -> UIViewController { return UIKitViewController() // 返回新構建的UIViewController子類 } // vc展現的時候回調,相似oc裏viewWillAppear/viewDidAppear func updateUIViewController(_ uiViewController: UIViewController, context: UIViewControllerRepresentableContext<UIKitVCRepresentable>) { } } 複製代碼
注意: 這種方式實現的是嵌套在SwiftUI的view中的UIViewController,相似OC中addController
目前只支持蘋果生態內的全部os系統,but 最近出了個支持web開發的測試項目:SwiftWebUI Swift 是開源的,跨平臺運行也不是問題,之後可能會支持更多的平臺。
developer.apple.com/documentati… developer.apple.com/documentati… developer.apple.com/videos/play… developer.apple.com/tutorials/s… fuckingswiftui.com/ onevcat.com/2019/06/swi… swift.gg/2019/09/12/…