Skip to content

跟着教程,用 swiftUI 写了个简单应用,涉及到不少基础功能,在此归纳总结下。以下文章主要是个人在学习中总结,并引用网上的资料,只用于个人快速查阅

涉及到功能

  1. 什么是 swiftUI
  2. 一些常用的组件
    1. NavigationView
    2. NavigationLink
    3. List
    4. Text
    5. Image
    6. TextField
    7. Toggle
    8. DatePIcker
    9. Picker
    10. Button
    11. Spacer
    12. VStack, HStack
  3. 动画
    1. animation
    2. transtion
    3. withAnimation
  4. 控制器之间如何传值、通信
    1. 父传子
    2. 子通知父
    3. 父调用子
    4. 子调用父
  5. 如何共享全局数据
    1. ObservableObject
  6. 一些常用的操作符(装饰器?)
    1. @State
    2. @Binding
    3. @EnvironmentObject
    4. @Environment
    5. @Published
  7. 预览、调式
  8. 总结

什么是 swiftUI

swiftUI 是苹果公司在 2019 年新推出的一个 swift 语言的 UI 框架,它要搭配 XCode 一起开发。用 swiftUI 时,有点像写 React,在一个函数内输出 UI 内容,然后样式、点击事件都和对应的组件绑定在一起写。通过一些装饰器,比如 @Binding 或 $,能实现控制器和视图层的双向绑定。swiftUI 在 XCode 可以快速预览到代码编写的效果,就像写网页一样,不像我之前学 UIKit 的时候,写完,要编译一下,再看效果。在跟着教程写完后,感觉开发速度提升不少,而且也不难。

但有些地方 swiftUI 还是很不完善。比如在写代码的时候,有些地方写错了,它会在别的地方出现错误,而且错误信息对于错误代码没有任何意义;我看的教程是 2019 年,但在开发时,不少 api 都是废弃掉的,需要再在网上搜对应的解决方案;有些 UI 组件还不支持,比如地图、轮播,需要 UIViewRepresentable 进行改造,感觉挺麻烦的。目前,swiftUI 还不成熟,不足以支撑稍微大点的项目开发,而且 swiftUI 只支持 IOS13 以上的机器。

常用组件

设置一个导航的视图,如果你要设置页面标题,就要先用 NavigaitonView 套住。所以一个页面组件,通常要用到 NavigationView

导航作用,跳转到不同的控制器,声明 destination 指向的控制器,点击后会跳转

swift
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 的样式

swift
Picker("喜欢的季节", selection: $val) {
    ForEach(list, id: \.self) { i in
        Text(i.value).tag(i)
    }
}.pickerStyle(SegmentedPickerStyle())

Button

要在 swiftUI 实现点击效果,普通的组件,像 Text 和 Image,需要套一层 Button

swift
Button(action: {}) {
  Text("haha")
}

Spacer

间距

VStack, HStack

实现竖排列和横排列

动画

略...

控制器之间如何传值、通信

父传子属性

A 控制器传入 detail: item 给到 B 控制器

A.swift

swift
import swiftUI

struct List: View {
  var body: some View {
    ForEach(0 ..< 5, id: \.self) { item in
      Item(detail: item)
    }
  }
}

Item.swift

swift
import swiftUI

struct Item: View {
  let detail: Int
  var body: some View {
    Text(detail)
  }
}

父传子,子可修改属性,并回传

Item.swift 的 detail 是可以修改的,并且修改后会直接通知父组件进行更新

A.swift

swift
import swiftUI

struct List: View {
  var body: some View {
    ForEach(0 ..< 5, id: \.self) { item in
      Item(detail: $item)
    }
  }
}

Item.swift

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(声明要公用的数据)

swift
// 要先引入 Combine 模块
import Combine

// 声明该类是 ObservableObject
class Data: ObservableObject {
  // 用 @Published 
  // 我也不知道为啥这样...
  @Published var data = [...]
}

Detail.swift

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

而通过 $ 操作符,可以使它和组件双向绑定,自动更新

swift
@State var isShow = false

Toggle(isOn: $isShow) {
  Text("只展示收藏")
}

@Binding

上面说过了,组价内实现双向绑定

@EnvironmentObject

通过祖先视图连接不同的视图,读取一个可监测的对象。当该对象变化时,绑定了该对象的视图都会自动更新

如何使用,上面“组件间通信”有说

@Environment

从视图获取连接视图属性的可读值

比如使用 EditButton(),触发点击后,editMode 属性会改变

swift
struct Text: View {
  @Environment(\.editMode) var mode
  
  // 可以根据 mode?.wrappedValue == .active | .inactive 判断是否打开编辑
}

@Published

如何使用,上面“组件间通信”有说

预览、调式

swift 文件,下面会有个 PreviewProvider 的结构体,用于预览

swift
struct Profile_Previews: PreviewProvider {
    static var previews: some View {
        Text()
    }
}

预览多视图

swift
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())
        }
    }
}