初识 SwiftUI

什么是SwiftUI?

2019年 WWDC 大会上,苹果宣布了基于Swift语言构建的全新UI框架-SwiftUI。其界面布局彻底抛弃了Storyboard和Autolayout,采用了声明式的界面语言(DSL,即Domain Specific Language),加上 Canvas 的实时预览功能,开发体验有了很大的提高。git

自 iOS SDK 2.0 开始,UIKIt已经伴随开发者近十年,其思想继承了成熟的 AppKit 和 MVC, 为iOS开发提升了良好的学习曲线。UIKit提供的是一套符合直觉的、命令式的编程方式,可是因为UIKit的基本思想要求 View Controller 承担绝大部分职责,它须要协调 model,view 以及用户交互,使得在较大型的项目里 View Controller 很臃肿,若是状态管理复杂甚至致使后期代码没法维护。近年来随着编程思想、技术的进步,愈来愈多的开始使用声明式函数式的方式来进行界面开发,如今大热的 React 和 Flutter 即是采起了声明式编程。github

在这种状况下,今年发布的SwiftUI固然也采起了声明式编程。编程

什么是声明式编程

维基百科对命令式、声明式编程的描述以下:swift

声明式编程:是一种不使用控制流来表示计算逻辑的编程范式。

命令式编程:是一种经过语句来改变程序状态的编程范式。
复制代码

举个栗子,当咱们要设置一个Text的时候,命令式编程以下bash

override func viewDidLoad() {
        super.viewDidLoad()
        let label = UILabel(frame: CGRect(x: 100, y: 100, width: 100, height: 100))
        label.text = "世界和平"
        label.textColor = .purple
        view.addSubview(label)
    }
复制代码

使用SwiftUI声明式编程时,则:session

var body: some View {
     Text("世界和平")
    		.color(.purple) 
    }
复制代码

从上面能够看出,UIKit命令式编程是经过一句句的代码来指导“要怎么构建UI”,而声明式编程则是使用各自的DSL来描述“UI应该是什么样子”数据结构

SwiftUI是怎么进行布局

在SwiftUI里上面Text的声明只是纯数据结构的描述,并非实际显示出来的视图,SwiftUI 会直接读取 DSL 内部描述信息并收集起来,而后转换成基本的图形单元,最终交给底层 Metal 或 OpenGL 渲染出来。闭包

在 SwiftUI 中Text属性的设置在内部都会用一个虚拟的 View 来承载,而后在开始布局的时候再进行布局计算,这样作的目的主要是方便底层在设计渲染函数时更容易作到 monomorphic call,省去无用的分支判断,提升效率。app

官方教程小解

swiftUI控件

swiftUI控件图

备注:要使用 SwiftUI 的完整功能, 需使用 Xcode 11, 而且将 macOS 系统升级到最新的10.15框架

此次SwiftUI的官方教程能够说苹果很给力,每一步代码都有讲解说明及效果图片,强烈推荐跟着教程敲一遍!这里简单说明一下教程中的一些细节

APP 的启动

在示例中,app在AppDelegate中经过application(_:configurationForConnecting:options)返回一个UISceneConfiguration实例:

func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
        return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
    }
复制代码

这部份内容是 iOS 13 中新加入的经过 Scene 管理 app 生命周期的方式,以及多窗口支持部分所须要的代码。在 app 完成启动后,控制权被交接给 SceneDelegate,它的 scene(_:willConnectTo:options:)将会被调用,进行 UI 的配置:

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        let window = UIWindow(frame: UIScreen.main.bounds)
        window.rootViewController = UIHostingController(rootView: ContentView())
        self.window = window
        window.makeKeyAndVisible()
    }
复制代码

这段代码则跟以前iOS app启动很相似,惟一不一样的就是rootViewControllerUIHostingController类型的,UIHostingControllerUIViewController的子类,主要负责接受一个SwiftUI的View的描述并将其用UIKit进行渲染,因为这种继承关系,因此能够作到将SwiftUI构建的界面能够集成到已有的UIKit app中,不须要从头开始就是基于SwiftUI的构建

some View

struct ContentView: View {
    var body: some View {
        Text("世界和平")
            .color(.purple)
    }
}
复制代码

SceneDelegate进入到ContentView,会发现一个奇怪的some view,要解释这个咱们就须要先了解下View

View是SwiftUI的最核心的一个协议,表明了一个屏幕上元素的描述。这个协议中含有一个 associatedtype

public protocol View : _View {

    associatedtype Body : View

    var body: Self.Body { get }
}
复制代码

这种带有associatedtype的协议不能做为类型来使用,而只能做为类型约束使用,即不能写成var body: View不然会报Error,提示“Protocol 'View' can only be used as a generic constraint”

其实若是咱们指明 Body 的类型,也是能够的,例如

struct ContentView: View {
    var body: Text {
        Text("世界和平")
            .color(.purple)
    }
}
复制代码

可是有个问题就是若是每次修改Body的返回时,咱们都须要收到调整相应的类型,会很麻烦也无必要。 some View 这种写法使用了 Swift 5.1 的 Opaque return types 特性。它向编译器做出保证,每次 body 获得的必定是某一个肯定的,遵照 View 协议的类型,可是请编译器“网开一面”,不要再细究具体的类型。这一个编译期间的特性,在保证associatedtype protocol的功能的前提下,使用 some 能够抹消具体的类型。这个特性用在 SwiftUI 上简化了书写难度,让不一样 View 声明的语法上更加统一。

Modifier

VStack(alignment: .leading) {
    Text("世界和平")
        .font(.title)
}
复制代码

除了View 以外,Modifier是 SwiftUI 另外一个重要概念。在上面的代码中, .font(...).foregroundColor(...)都修饰了 Text() 视图的某些属性——字体和颜色。每个单独的 Modifier 并不会对 View 类型实例进行操做,而是一个返回 some View类型的闭包。所以 Modifier 的运行机制与咱们熟悉的 UIKit 中对视图属性进行修改的方式是相反的,咱们构建出一个视图时并不会先初始化出一个 View 实例再对其进行修饰,而是经过声明的各类Modifier 构建出View 实例

当容器内视图具备相同的属性,能够将其提取到容器外进行定义,从而减小大量的重复代码:

HStack(alignment: .top) {
    Text("世界和平")
        .font(.subheadline)
    Spacer()
    Text("地球无战事")
        .font(.subheadline)
    
}
.foregroundColor(.yellow)

复制代码

数据绑定

SwiftUI中一个很便捷的功能是视图与数据的绑定。在官方示例中,Toggle控件就绑定了showFavoritesOnly,并且绑定数据的方式很是的优雅,在初始化方法中经过 $ 声明某个属性即可以让视图自动绑定此变量

注:State是一个值或一组值,能够随时间发生变化并影响视图的行为、内容或布局。 👇使用具备 @State 声明的属性将状态添加到视图。

@State var showFavoritesOnly = true

var body: some View {
    
    Toggle(isOn: $showFavoritesOnly){
        Text("Favorites only")
    }
}
复制代码

ForEach

ForEach 能以与 List相同的方式对集合进行,也就是说能够在任何使用子视图的地方使用它,例如堆栈、列表、groups中,当数据元素为简单的值类型,能够直接将\.self当作标识符的关键路径

struct LandmarkList_Previews: PreviewProvider {
    static var previews: some View {
        ForEach(["iPhone SE", "iPhone XS Max"].identified(by: \.self)) { deviceName in
            LandmarkList()
                .previewDevice(PreviewDevice(rawValue: deviceName))
        }
    }
}
复制代码

Canvas使用

  • Canvas 具备实时预览功能,在 Xcode 中显示的快捷键 CMD + OPTION + ENTER,取消显示快捷键CMD + ENTER
  • 在 Xcode 中能够经过 Editor → Previews → Show View Bounds展现视图边界,方便 UI 校对
  • 点击Canvas左下角的针头能够固定当前的Canvas,以便点击其余SwiftUI界面时候不会随之变更Canvas
  • 选择LivePreview,可使静态画布变成实时的,咱们能够直接在Canvas上进行手势操做、触发事件查看效果

总结

今年WWDC发布的SwiftUI,是一个令iOS开发者兴奋的全新UI框架。虽然如今还有些遗憾,如仅支持iOS13及以上且其跨平台目前也仅仅只停留在苹果内部的生态圈里,可是因为Swift是开源的且Swift5的ABI也已经稳定下来,当2 - 3年后咱们能够大面积使用它的时候,SwiftUI应该会有很不错的反响。

参考资料

官方示例

SwiftUI 的一些初步探索 (一)

SwiftUI 的一些初步探索 (二)

SwiftUI 概览:十分钟构建简单应用

相关文章
相关标签/搜索