SwiftUI入门

与大家一起学习一下SwiftUI

历史背景回顾

  • WWDC2014 推出Swift,现最新版本 Swift 5
  • WWDC2016 苹果公司为iPad推出Swift Playgrounds App,旨在让学生和初学者更容易地掌握 Swift 学习编程的技能。
  • WWDC2019于 今年6月份举行,发布了SwiftUI

SwiftUI介绍

  • 实时模式 及 新的直观的设计工具(Preview 需要xcode11、macOS 10.15)
    1. 设计画布中的操作与编辑器中代码保持完全同步
    2. Swift 编译器和运行时已全面嵌入到 Xcode 中,您可以随时构建和运行您的 app
  • 声明式语法
  • 原生支持所有Apple平台(iOS、OSX、iPadOS、tvOS、watchOS…)

为什么推出SwiftUI呢?

  1. 为苹果各生态提供统一的UI框架,降低其平台间开发门槛(假如去开发macOS,不需要再学一次 AppKit)
  2. 大幅提高UI开发效率
  3. 声明式语法,结合新推出的响应式框架(Combine),iOS生态下终于可以像Flutter、React 那样写代码

2014-2019年,开发者可以无视Swift,继续用Objective-C,但是2019 年开始,一切都不一样了

声明式编程的几个优点:

  1. 代码是描述程序做什么,阅读性较高
  2. 组件状态更容易维护在内部,减少副作用
  3. 组件间边界更明显,从而让组件复用更灵活

近年来,函数式或申明式开发UI方式逐渐成为主流,如Flutter、React,这些开发框架一般的开发步骤如下:

  1. 使用各自的DSL来描述【UI应该是什么样子】,而不是用代码命令式的执行【要怎么构建UI】
  2. 框架内部读取这些view的申明,负责将其以合适的方式绘制渲染
  3. 如果view需要根据某个状态(state)来改变,则将状态存储在变量中,并在声明view时使用它
  4. 状态改变时,框架重新调用声明部分的代码,计算出新的view申明,并和原来的view进行diff,之后由框架负责将变更的部分进行高效重绘

SwiftUI文档

| SDKS | 适用版本 | | - | - | | SDKs | iOS13.0+ | macOS10.15+ | Mac Catalyst13.0+ | tvOS13.0+ | watchOS 6.0+

因为SwiftUI需要的iOS系统支持最低13.0,所以在实际项目中运用的话,可能需要等一两年

申明每个平台上的你的应用的用户界面和行为

概览

SwiftUI提供了用于声明应用程序用户界面的视图(views)、控件(controls)、布局结构。该框架为分发点击、手势和其他类型的输入提供事件处理程序,并提供工具来管理从应用的models到用户将看到并与之交互的views和controls间的数据流。

创建遵守View协议的自定义视图,然后将它们与SwiftUI的视图组合在一起,以使用stacks, lists等来显示文本、图片、自定义shapes。将强大的修改器应用于内置视图和自定义视图,以自定义其渲染和交互性。多个平台的应用之间可以共享代码,这些视图和控件可以适应多个平台的上下文和表现形式。

可以将SwiftUI视图与UIKit,AppKit,WatchKit框架中对象集成在一起,以进一步利用特定平台的功能。SwiftUI支持自定义辅助功能(accessibility),并针对不同的语言,国家、文化区域做本地化用户界面。

###话题 ***

要点

学习使用SwiftUI制作应用 遵循一些列指导教程,学习使用SwiftUI和Xcode制作应用程序 ***

用户界面

视图与控件 | 在屏幕上显示内容并处理用户交互|

视图和控件是应用中用户界面的视觉组成部分。视图包括text、images、shapes、自定义绘图及所有的这些组合组成。控件是用户能够与适应其平台和上下文一致的api进行交互。

视图布局与表现 | 将视图组合在栈中,动态生成视图组和列表,并定义视图表现和层次结构 |

使用stacks和lists来布局用户界面的视图。可以将视图与从数据集合中动态生成的视图集合起来。所有容器视图都会根据内容或界面尺寸来更新和调整其子集的位置

绘画和动画 | 使用颜色、形状、阴影增强视图,并自定义视图状态之间的动画过度 |

框架整合 | 将SwiftUI视图集成到现有的应用程序中,并将AppKit、UIKit、WatchKit视图和控制器嵌入到SwiftUI视图层次结构中 |


数据与事件

状态与数据流 | 控制和响应应用程序模型中的数据流和编程 |

States 和 bindings将视图连接到应用的基础数据模型。当声明一个state,SwiftUI会为你存储该状态并管理该状态与视图的连接。当状态更新时,视图将使其外观无效并自动更新。还可以将动画连接到状态,以视图的动画形式来描述状态的变更。

从状态的成员创建绑定以连接到各个视图。绑定提供双向连接,所以屏幕上的控件可以改变状态。绑定也具有在视图间传递值的事务。

| 手势 | 定义从点击、点击、滑动到细粒度手势的交互 | ***

Xcode中的预览

预览 | 生成自定义视图的动态交互预览 | ***

学习使用SwiftUI制作应用

  1. 初始化工程里的小知识点
    • swift工程里是没有main.m文件的,程序启动入口 可以理解为由 @UIApplicationMain自动生成需要的main函数
    • xcode11新建工程会多一个SceneDelegate,是为了支持iPadOS多窗口的原因,如果不需要支持多窗口,可以手动删除info.plist文件中的Application Scene Manifest的配置数据、Scene等相关代码。纯代码实现时,需要手动在AppDelegate里添加window属性
    • UIHostingController是UIViewController的子类,负责接收一个SwiftUI的描述并将其用UIKit进行渲染
    • associatedtype关键字声明一个类型的占位符作为协议定义的一部分,协议中实现范性功能的语法;class,struct,enums都可以使用参数化类型来表达泛型的
  2. SwiftUI知识点 ***
    • 自定义SwiftUI视图时,可以调用称为modifiers的方法。Modifiers包装视图以更改其显示或其他属性。每个modifier都会返回一个新的视图,因此可以进行链式调用
    • 创建SwiftUI视图时,可以在body属性中描述其内容、布局和行为。但body属性只能返回单个视图,多个视图的组合可以使用Stacks。(HStack 子view排列在一条水平线上的视图 VStack 垂直排列 ZStack 从后向前)
    • 在swiftUI文件中需要引用UIKit时,需要遵守UIViewRepresentable协议。其协议需要实现两个方法:

    1.创建view的方法 func makeUIView(context: Self.Context) -> Self.UIViewType

    2.配置视图并响应所有更改的方法func updateUIView(_ uiView: Self.UIViewType, context: Self.Context)

  • 当”预览”是在静态模式时,仅仅渲染SwiftUI的视图。当视图时UIView的子类时,需要把”预览”切换到实时预览才能看到效果 ***
  • Image方法resizable()可以根据不同模式缩放图片
  • preview可以通过修改视图的初始化参数直接更新预览,可以通过previewLayout方法修改合适的预览尺寸,比如设置为列表中一行的大小;可以通过Group容器添加多个预览,Xcode将卒中的子视图呈现为画布中的单独预览;
  • 视图的子集继承视图的上下文设置
  • 列表的使用需要identifiable的数据;使数据identifiable由两种方式,

    • 通过将数据传递给唯一标示每个元素的属性的键路径
    • 使数据类型符合 identifiable协议
  • 常规的属性传值

  • 默认预览设备类型由scheme中选择的模拟器类型决定的;PreviewDevice初始化参数字符串可以在view的扩展的注释里找到所有支持的设备名称,搜一下”iPhone SE”;预览中多个设备同时显示,对比界面视图时比较方便,不过实时连接的设备只能有一个,类似于模拟器可以开很多,但是调试时只有一个是连接上的;

  • 遗留问题点
    1. navigationBarTitle 设置 导航条标题 是view的方法,而不是NavigationView的方法,试了一个NavigationView的子集由两个view都设置 导航条方法时,只显示了第一次设置的,待研究
    2. NavigationLink的定义为A button that triggers a navigation presentation when pressed. 不知道有没有 不跳转的方法,只是单纯的点击事件
    3. 例子中的cell样式是系统的,不知道怎么自定义,比如右边的箭头去掉,还有就是行高怎么控制

  • if 语句可以根据条件来包含视图
  • 状态是一个值或一组值,可随时间变化,并影响视图的行为、内容、布局(@State 属性)
  • 要将静态视图和动态视图合并到列表中,或者将两个或多个不同的动态视图组合并,需要使用ForEach类型,而不要将数据集合传递给List
  • 使用$前缀来访问对状态变量或状态变量的某个属性的绑定;绑定充当对可变状态的引用;控件可以使用绑定来相应的更新视图的状态
  • observable对象是一个自定义数据对象,可以将其绑定到SwiftUI环境中的存储视图;SwiftUI监听可能影响视图的observable对象的任何变更,并在更改后显示视图的正确版本;
  • SwiftUI订阅一个自定义的observable对象,并在数据更改时更新需要刷新的所有视图。ObservableObject协议需要导入框架Combine;
  • 为了让一个observable对象的订阅者可以获取更改,需要对象发布其对数据的变更(在需要的数据属性前加上 @Published)

不同场景中,SwiftUI 提供了不同的关键词,其实现原理上如上文所示:

@State - 视图和数据存在依赖,数据变化要同步到视图;

@Binding - 父子视图直接有数据的依赖,数据变化要同步到父子视图;

@BindableObject - 外部数据结构与 SwiftUI 建立数据存在依赖;

@EnvironmentObject - 跨组件快速访问全局数据源




应用程序设计和布局

  • Dictionary(grouping: , by:)方法将数组转成Dictionary
  • listRowInsets(EdgeInsets()) 扩展内容到显示的边缘
  • 传给NavigationLink的Text默认使用环境的强调色呈现,Image会呈现为模版图像,需要修改合适的设计。

使用UI控件

  • .frame(width: 300, height: 300).scaleEffect(1.0 / 3.0) .frame(width: 100, height: 100) 相当于view的等比例缩放

  • .hueRotation(Angle(degrees:90)) 对view进行hue 颜色旋转效果

  • Divider() 分割线


与UIKit的框架整合

  • 为了在SwiftUI中表示UIKit中的view和viewController,需要创建遵守UIViewRepresentable和UIViewControllerRepresentable协议的类型。当创建自定义类型并配置相应的UIKit类型的表现形式,而SwiftUI管理其生命周期并在需要时更新更新。
  • 当装备显示视图时,SwiftUI会调用一次makeUIViewController方法,然后管理视图控制器的生命周期
  • 表示UIKit中控制器的那个SwiftUI视图可以定义为Coordinator类型,次类型由SwiftUI管理并作为表示视图上下文的一部分提供。
  • SwiftUI管理UIViewControllerRepresentable类型的协调器,并在调用上面创建的方法时将其作为上下文的一部分提供。
  • SwiftUI在调用makeUIViewController之前调用makeCoordinator方法,以便在配置视图控制时可以访问协调器
  • 可以使用协调器来实现常见的Cocoa模式,比如代理、数据源、通过target-action响应用户事件

开预览 可能对机器性能要求较高,学习官网教程过程中,查看api时电脑卡死2次,切到其他程序时卡死1次。

引用源

Written on December 17, 2019