项目作者: liangtongdev

项目描述 :
SwiftUI Tutorials
高级语言: Swift
项目地址: git://github.com/liangtongdev/SwiftUI-Tutorials.git
创建时间: 2019-10-15T07:29:04Z
项目社区:https://github.com/liangtongdev/SwiftUI-Tutorials

开源协议:MIT License

下载


SwiftUI-Tutorials

https://developer.apple.com/tutorials/swiftui

Overview

  • 1 Creating and Combining Views
  • 2 Building Lists and Navigation
  • 3 Handling User Input
  • 4 Drawing Paths and Shapes
  • 5 Animating Views and Transitions
  • 6 Composing Complex Interfaces
  • 7 Working with UI Controls
  • 8 Interfacing with UIKit

Screenshot

1 2 3
4 5 6
7 8 9
10 11 12

知识梳理

SwiftUI App 启动

  • AppDelegate UISceneSession Lifecycle
  • UISceneConfiguration Default Configuration
  • Info.plist: UIApplicationSceneManifest -> UISceneConfigurations -> SceneDelegate
  • scene: willConnectToSession: options: (iOS 启动流程)
  • UIHostingController:UIViewController (接受一个 SwiftUI 的 View 描述并将其用 UIKit 进行渲染)

SwiftUI页面结构

  • 遵从View协议

    1. public protocol View : _View {
    2. associatedtype Body : View
    3. var body: Self.Body { get }
    4. }

    这种带有 associatedtype 的协议不能作为类型来使用,而只能作为类型约束使用:

    1. struct ContentView: View {
    2. var body: some View {
    3. Text("Turtle Rock")
    4. }
    5. }

    some View 这种写法使用了 Swift 5.1 的 Opaque return types 特性。它向编译器作出保证,每次 body 得到的一定是某一个确定的,遵守 View 协议的类型。

  • 预览渲染

  1. struct ContentView_Preview: PreviewProvider {
  2. static var previews: some View {
  3. ContentView()
  4. }
  5. }

链式调用

  1. var body: some View {
  2. Image("turtlerock")
  3. .clipShape(Circle())
  4. .overlay(
  5. Circle().stroke(Color.white, lineWidth: 4))
  6. .shadow(radius: 10)
  7. }

ViewBuilder

HStackVStackZStack,这里拿HStack进行说明。结构体构造器定义如下

  1. public init(alignment: VerticalAlignment = .center, spacing: CGFloat? = nil, @ViewBuilder content: () -> Content)

包含3个参数,对其方式和spacing没啥说的。最后一个是被@ViewBuilder标记的闭包,作用是将闭包内的 Text 或其他 View 转换为一个 TupleView返回。

使用例子:

  1. HStack(alignment: .top) {
  2. Text(landmark.park)
  3. .font(.subheadline)
  4. Spacer()
  5. Text(landmark.state)
  6. .font(.subheadline)
  7. }

List

  1. var body: some View {
  2. List {
  3. LandmarkRow(landmark: landmarkData[0])
  4. LandmarkRow(landmark: landmarkData[1])
  5. }
  6. }

对于List来说,SwiftUI底层使用UITableView来进行绘制

属性修饰器

@State

通过使用 @State 修饰器我们可以关联出 View 的状态, 当 @State 装饰过的属性发生了变化,SwiftUI 会根据新的属性值重新创建视图

  1. @State var showingProfile = false
  2. var profileButton: some View {
  3. Button(action: { self.showingProfile.toggle() }) {
  4. Image(systemName: "person.crop.circle")
  5. .imageScale(.large)
  6. .accessibility(label: Text("User Profile"))
  7. .padding()
  8. }
  9. }
  10. var body: some View {
  11. NavigationView {
  12. // ...
  13. .navigationBarTitle(Text("Featured"))
  14. .navigationBarItems(trailing: profileButton)
  15. .sheet(isPresented: $showingProfile) {
  16. Text("User Profile")
  17. }
  18. }
  19. }
@Binding

Swift 中值的传递形式是值类型传递方式,通过 @Binding 修饰器修饰后,属性变成了一个引用类型,传递变成了引用传递。通常在视图属性传递时使用,例如PageViewController 和 PageControl之间 currentPage 属性的传递。

@Published
  1. final class UserData: ObservableObject{
  2. @Published var showFavoritesOnly = false
  3. @Published var landmarks = landmarkData
  4. }

对象遵从ObservableObject协议, 对于 Published 修饰的属性, 一旦发生了变换,SwiftUI 会更新相关联的 UI

@EnvironmentObject
  1. @EnvironmentObject var userData: UserData
  2. XXXX()
  3. .environmentObject(UserData())

这个修饰器是针对全局环境的。通过它,我们可以避免在初始 View 时创建 ObservableObject, 而是从环境中获取 ObservableObject

@Environment

系统级别的设定,我们开一个通过 @Environment 来获取到它们

  1. @Environment(\.editMode) var mode
  2. @Environment(\.calendar) var calendar: Calendar
  3. @Environment(\.locale) var locale: Locale
  4. @Environment(\.colorScheme) var colorScheme: ColorScheme

动画

  • 直接在view上使用.animation()来修改
  • 使用withAnimation{}来控制某个状态,触发动画
  1. Button(action: {
  2. self.showDetail.toggle()
  3. }) {
  4. Image(systemName: "chevron.right.circle")
  5. .imageScale(.large)
  6. .rotationEffect(.degrees(showDetail ? 90 : 0))
  7. .animation(nil) //去除动画
  8. .scaleEffect(showDetail ? 1.5 : 1)
  9. .padding()
  10. .animation(.spring())
  11. }

SwiftUI 的 modifier 是有顺序的。在我们调用 animation(_:) 时,SwiftUI 做的事情等效于是把之前的所有 modifier 检查一遍,然后找出所有满足 Animatable 协议的 view 上的数值变化,比如角度、位置、尺寸等,然后将这些变化打个包,创建一个事物 (Transaction) 并提交给底层渲染去做动画。

  1. Button(action: {
  2. withAnimation {
  3. self.showDetail.toggle()
  4. }
  5. }) {
  6. Image(systemName: "chevron.right.circle")
  7. .imageScale(.large)
  8. .rotationEffect(.degrees(showDetail ? 90 : 0))
  9. .scaleEffect(showDetail ? 1.5 : 1)
  10. .padding()
  11. }

withAnimation 是统一控制单个的 Transaction,而针对不同 View 的 animation(_:) 调用则可能对应多个不同的 Transaction。

View的生命周期

  1. ProfileEditor(profile: $draftProfile)
  2. .onAppear {
  3. self.draftProfile = self.userData.profile
  4. }
  5. .onDisappear {
  6. self.userData.profile = self.draftProfile
  7. }

NavigationView

  1. struct LandmarkList: View {
  2. var body: some View {
  3. NavigationView {
  4. List(landmarkData) { landmark in
  5. NavigationLink(destination: LandmarkDetail(landmark: landmark)) {
  6. LandmarkRow(landmark: landmark)
  7. }
  8. }
  9. .navigationBarTitle(Text("Landmarks"))
  10. }
  11. }
  12. }
  • NavigationView ,页面跳转
  • NavigationLink(destination:) , 跳转页面

UIKit 接口

UIView
  • 遵从 UIViewRepresentable 协议,重写 makeUIView(context:)updateUIView(_:context:)方法
UIViewController
  • 遵从 UIViewControllerRepresentable 协议,重写 makeUIViewController(context:)updateUIViewController(_:context:)方法
Coordinator

UIView或UIViewController会有自己的dataSource 或 delegate,通常的做法是在结构体内部定义Coordinator类,由Coordinator类来实现这些特点协议(或提供特点的方法)。然后在makeUIViewxxx的内部设置对应的delegate。例如:

  1. struct PageControl: UIViewRepresentable {
  2. var numberOfPages: Int
  3. @Binding var currentPage: Int
  4. func makeCoordinator() -> Coordinator {
  5. return Coordinator(self)
  6. }
  7. func makeUIView(context: Context) -> UIPageControl {
  8. let control = UIPageControl()
  9. control.numberOfPages = numberOfPages
  10. control.pageIndicatorTintColor = .gray;
  11. control.currentPageIndicatorTintColor = .white;
  12. control.addTarget(
  13. context.coordinator,
  14. action: #selector(Coordinator.updateCurrentPage(sender:)),
  15. for: .valueChanged)
  16. return control
  17. }
  18. func updateUIView(_ uiView: UIPageControl, context: Context) {
  19. uiView.currentPage = currentPage
  20. }
  21. class Coordinator: NSObject {
  22. var control: PageControl
  23. init(_ control: PageControl) {
  24. self.control = control
  25. }
  26. @objc func updateCurrentPage(sender: UIPageControl) {
  27. control.currentPage = sender.currentPage
  28. }
  29. }
  30. }
  1. struct PageViewController: UIViewControllerRepresentable {
  2. var controllers: [UIViewController]
  3. @Binding var currentPage: Int
  4. func makeCoordinator() -> Coordinator {
  5. Coordinator(self)
  6. }
  7. func makeUIViewController(context: Context) -> UIPageViewController {
  8. let pageViewController = UIPageViewController(
  9. transitionStyle: .scroll,
  10. navigationOrientation: .horizontal)
  11. pageViewController.dataSource = context.coordinator
  12. pageViewController.delegate = context.coordinator
  13. return pageViewController
  14. }
  15. func updateUIViewController(_ pageViewController: UIPageViewController, context: Context) {
  16. pageViewController.setViewControllers(
  17. [controllers[currentPage]], direction: .forward, animated: true)
  18. }
  19. class Coordinator: NSObject, UIPageViewControllerDataSource,UIPageViewControllerDelegate {
  20. //...
  21. }
  22. }