跟着教程,用 swiftUI 写了个简单应用,涉及到不少基础功能,在此归纳总结下。以下文章主要是个人在学习中总结,并引用网上的资料,只用于个人快速查阅
涉及到功能
- 什么是 swiftUI
- 一些常用的组件
- NavigationView
- NavigationLink
- List
- Text
- Image
- TextField
- Toggle
- DatePIcker
- Picker
- Button
- Spacer
- VStack, HStack
- 动画
- animation
- transtion
- withAnimation
- 控制器之间如何传值、通信
- 父传子
- 子通知父
- 父调用子
- 子调用父
- 如何共享全局数据
- ObservableObject
- 一些常用的操作符(装饰器?)
- @State
- @Binding
- @EnvironmentObject
- @Environment
- @Published
- 预览、调式
- 总结
什么是 swiftUI
swiftUI 是苹果公司在 2019 年新推出的一个 swift 语言的 UI 框架,它要搭配 XCode 一起开发。用 swiftUI 时,有点像写 React,在一个函数内输出 UI 内容,然后样式、点击事件都和对应的组件绑定在一起写。通过一些装饰器,比如 @Binding 或 $,能实现控制器和视图层的双向绑定。swiftUI 在 XCode 可以快速预览到代码编写的效果,就像写网页一样,不像我之前学 UIKit 的时候,写完,要编译一下,再看效果。在跟着教程写完后,感觉开发速度提升不少,而且也不难。
但有些地方 swiftUI 还是很不完善。比如在写代码的时候,有些地方写错了,它会在别的地方出现错误,而且错误信息对于错误代码没有任何意义;我看的教程是 2019 年,但在开发时,不少 api 都是废弃掉的,需要再在网上搜对应的解决方案;有些 UI 组件还不支持,比如地图、轮播,需要 UIViewRepresentable 进行改造,感觉挺麻烦的。目前,swiftUI 还不成熟,不足以支撑稍微大点的项目开发,而且 swiftUI 只支持 IOS13 以上的机器。
常用组件
NavigationView
设置一个导航的视图,如果你要设置页面标题,就要先用 NavigaitonView 套住。所以一个页面组件,通常要用到 NavigationView
NavigationLink
导航作用,跳转到不同的控制器,声明 destination 指向的控制器,点击后会跳转
NavigationLink(destination: Text("GO")) {
Text("点击")
}
List
一个行容器组件,每个组件之间都有对应的间距和间隔线
Text
普通的文本组件
Image
图片组件
ios 自带的图标,可通过 systemName: "star"
声明
要修改图片的宽高,要先调用 .resizeable()
通过 .scaleToFill()
和 .scaleToFit
控制图片缩放形式
通过 .frame
可控制图片的宽高
实现圆形效果:.clipShape(Circle())
描绘边框:.overlay(Circle().stroke(Color.white, lineWidth: 4))
TextField
文本输入组件
Toggle
Toggle 表单控件
DatePIcker
日期选择器
Picker
可通过 pickerStyle
变更 Picker 的样式
Picker("喜欢的季节", selection: $val) {
ForEach(list, id: \.self) { i in
Text(i.value).tag(i)
}
}.pickerStyle(SegmentedPickerStyle())
Button
要在 swiftUI 实现点击效果,普通的组件,像 Text 和 Image,需要套一层 Button
Button(action: {}) {
Text("haha")
}
Spacer
间距
VStack, HStack
实现竖排列和横排列
动画
略...
控制器之间如何传值、通信
父传子属性
A 控制器传入 detail: item 给到 B 控制器
A.swift
import swiftUI
struct List: View {
var body: some View {
ForEach(0 ..< 5, id: \.self) { item in
Item(detail: item)
}
}
}
Item.swift
import swiftUI
struct Item: View {
let detail: Int
var body: some View {
Text(detail)
}
}
父传子,子可修改属性,并回传
Item.swift 的 detail 是可以修改的,并且修改后会直接通知父组件进行更新
A.swift
import swiftUI
struct List: View {
var body: some View {
ForEach(0 ..< 5, id: \.self) { item in
Item(detail: $item)
}
}
}
Item.swift
import swiftUI
struct Item: View {
@Binding var detail: Int
var body: some View {
Text(detail)
}
}
视图间更新
比如 a, b, c 视图都要显示同一块数据,当某个视图对该数据进行更新的时候,如何让其他绑定了这数据的视图也相应更新?
使用 @EnvironmentObject,Combine,ObserverableObject 和 @Published 解决(tip: 真麻烦,要引入一堆东西)
Data.swift(声明要公用的数据)
// 要先引入 Combine 模块
import Combine
// 声明该类是 ObservableObject
class Data: ObservableObject {
// 用 @Published
// 我也不知道为啥这样...
@Published var data = [...]
}
Detail.swift
import swiftUI
struct Detail: View {
// 声明引入环境对象 data
@EnvironmentObject var data: Data
var body: some View {
Button(action: {
// 当对象更新后,所有绑定了该对象的,都会相应更新
self.data += 1
}) {
Text("add")
}
}
}
// 每个要用该 data 的视图,都要记住只能用同一个,不能多次声明 Data()
Detail().environmentObject(Data())
// 另外的视图使用,这个 data 就是上面 Data() 返回的实例
Other().environmentObject(data: data)
一些常用的操作符(装饰器?)
使用 swiftUI 时,struct 里面声明的变量都是不可变的
@State
要使变量可变化,要用 @State
而通过 $ 操作符,可以使它和组件双向绑定,自动更新
@State var isShow = false
Toggle(isOn: $isShow) {
Text("只展示收藏")
}
@Binding
上面说过了,组价内实现双向绑定
@EnvironmentObject
通过祖先视图连接不同的视图,读取一个可监测的对象。当该对象变化时,绑定了该对象的视图都会自动更新
如何使用,上面“组件间通信”有说
@Environment
从视图获取连接视图属性的可读值
比如使用 EditButton(),触发点击后,editMode 属性会改变
struct Text: View {
@Environment(\.editMode) var mode
// 可以根据 mode?.wrappedValue == .active | .inactive 判断是否打开编辑
}
@Published
如何使用,上面“组件间通信”有说
预览、调式
swift 文件,下面会有个 PreviewProvider 的结构体,用于预览
struct Profile_Previews: PreviewProvider {
static var previews: some View {
Text()
}
}
预览多视图
struct LandmarkList_Previews: PreviewProvider {
static var previews: some View {
ForEach(["iPhone XS", "iPhone 8"], id: \.self) {deviceName in
Test()
.previewDevice(PreviewDevice(rawValue: deviceName))
.previewDisplayName(deviceName).environmentObject(Data())
}
}
}