手把手教你用SwiftUI写程序

学习SwifUI的最好方式就是,用SwiftUI编写一个程序

文章来源:Introducing SwiftUI: Building Your First App

注:视频中使用的部分API,已经在后续版本中废弃,本文代码根据最新版API进行了调整,确保 Demo工程 能够运行

一:编写第一个SwiftUI程序

  1. 创建SwiftUI工程

1.png

  • 左侧是代码区

  • 右侧是Canvas

    编写代码时,右侧的Canvas能够实时显示出代码的UI预览效果

2.png

  1. 编写UI布局代码

    • 通过拖拽增加UI控件

      1.gif

      • VStack:SwiftUI常用的一种布局元素,可以用来垂直地叠加视图
      • HStack:水平叠加视图
    • cmd+点击VStack,插入HStack
      3.png

      HStack {
                  VStack {
                      Text("Rooms")
                      Text("20 people")
                  }
      }
      复制代码
    • 在文字左侧增加一个图片

      HStack {
        					// `photo`是系统自带资源库中的图片
                Image(systemName: "photo")
                  
                  VStack {
                      Text("Rooms")
                      Text("20 people")
                  }
              }
      复制代码
    • 在Canvas中将VStack修改为左对齐

      2.gif

    • 设置Text的字号

      4.png

      HStack {
                  Image(systemName: "photo")
                  
                  VStack(alignment: .leading) {
                      Text("Rooms")
                      Text("20 people")
                          .font(.subheadline)
                  }
              }
      复制代码

      我们称.font(.subheadline)为修饰器(modifier),用来自定义视图的外观或行为

    • 设置Text的颜色为secondary

      HStack {
                  Image(systemName: "photo")
                  
                  VStack(alignment: .leading) {
                      Text("Rooms")
                      Text("20 people")
                          .font(.subheadline)
                          .foregroundColor(.secondary)
                  }
              }
      复制代码
    • 将HStack替换为List

      5.png

      6.png

  2. 设置数据源

    • 增加Room模型

      在SwiftUI中,需要让模型遵循Identifiable协议,实现id属性

    7.png

    • 在ContentView中使用Room数据来展示UI

      8.png

      当代码发生重大改变(比如增加属性),Xcode会暂停预览,直到我们做好重新更新的准备(点击Resum按钮)

  3. 丰富UI内容

    • 设置图片圆角

      • 通过拖拽Modifier库来实现

        3.gif

    • 设置Navigation、NavigationTitle以及给每个单元格设置跳转

      NavigationView {
                  List(rooms) { room in
                      NavigationLink(destination: Text(room.name)) {
                          Image(systemName: "photo")
                              .cornerRadius(8.0)
                      
                          
                          VStack(alignment: .leading) {
                              Text(room.name)
                              Text("\(room.capacity) people")
                                  .font(.subheadline)
                                  .foregroundColor(.secondary)
                          }
                      }
                  }
                  .navigationTitle(Text("Rooms"))
              }
      复制代码
  4. 进入实时模式,查看效果

    4.gif

  5. 将子视图提成一个单独的视图

    5.gif

  6. 创建新的页面:RoomDetail

    struct RoomDetail: View {
        let room: Room
        var body: some View {
            Image(room.imageName)
                .resizable()
                .aspectRatio(contentMode: .fit)
        }
    }
    
    struct RoomDetail_Previews: PreviewProvider {
        static var previews: some View {
            RoomDetail(room: testData[0])
        }
    }
    复制代码
    • 设置navigationBarTitle

      Image(room.imageName)
                  .resizable()
                  .aspectRatio(contentMode: .fit)
                  .navigationBarTitle(Text(room.name))
      复制代码

      此时页面不会更新,因为RoomDetail中缺乏NavigationView作为上下文

    • 在预览中添加NavigationView上下文

      struct RoomDetail_Previews: PreviewProvider {
          static var previews: some View {
              NavigationView {
                  RoomDetail(room: testData[0])
              }
          }
      }
      复制代码
    • 调整navigationBarTitle的展示模式

      9.png

  7. 将RoomCell的跳转修改为前往RoomDetail页面

    struct RoomCell: View {
        let room: Room
        
        var body: some View {
            NavigationLink(destination: RoomDetail(room: room)) {
                // ···
            }
        }
    }
    复制代码

    6.gif

二:Swift UI的工作方式

2.1 View

  • A View Defines a Piece of UI

    在SwiftUI中,视图是一种遵守View协议的结构,而不是继承自基础类的类

  • A View Defines its Dependencies

2.2 状态属性

  • @State

  • 当SwiftUI看到一个带@State状态变量的视图时,它会以视图的名义为那个变量分配存储空间。

    v1.png

    • 绿色部分是APP的内存
    • 紫色是SwiftUI所管理的内存

    SwiftUI可以观察到@State变量合时被读写,同时SwiftUI知道zoom是从body中读取的,SwiftUI会在@State变量发生更改时,使用新的状态值,刷新渲染。

例:实现在RoomDetail中,点击图片修改填充模式

struct RoomDetail: View {
    let room: Room
    @State private var zoomed = false
    
    var body: some View {
        Image(room.imageName)
            .resizable()
            .aspectRatio(contentMode: zoomed ? .fit : .fill) // 根据zoomed值修改填充模式
            .navigationBarTitle(Text(room.name), displayMode: .inline)
            .onTapGesture {
                self.zoomed.toggle() // 点击修改zoomed的值
            }
    }
}
复制代码

2.3 事实来源

在SwiftUI中,UI可能因不同的数据,处于不同的状态,我们将这些用来绘制UI的数据称为“事实来源”,“事实来源“由状态变量模型共同组成。

  • 属性可以简单地分为:事实来源(Source of Truth)和衍生值(Derived Value)

    v2.png

    zoomed变量是一个事实来源,contentMode衍生自它,当系统观察到zoomed变量发生变化时,SwiftUI框架会请求新的body,刷新渲染,重新生成一个新的宽高比视图,接下来覆盖contentMode

    像RoomDetail中的,room属性,也是一个衍生值。

  • 数据流原语(Data Flow Primitives)

    v3.png

SwiftUI是数据驱动,而不是事件驱动

三:完善Rooms APP

  1. 增加动画

    .onTapGesture {
                    withAnimation {
                        self.zoomed.toggle()
                    }   
                }
    复制代码
  2. 添加一个ZStack

            ZStack(alignment: .topLeading) {
                Image(room.imageName)
                    .resizable()
                    .aspectRatio(contentMode: zoomed ? .fit : .fill)
                    .navigationBarTitle(Text(room.name), displayMode: .inline)
                    .onTapGesture {
                        withAnimation {
                            self.zoomed.toggle()
                        }
                }
                
                Image(systemName: "video.fill")
                    .font(.title)
                    .padding(.all)
            }
    复制代码

    7.gif

  3. 固定图标的位置

    Image(room.imageName)
                    .frame(minWidth:0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
    复制代码

    8.gif

    更多关于SwiftUI布局相关的内容 – > Buiding Custom Views with SwiftUI

  4. 同时预览多个View

    struct RoomDetail_Previews: PreviewProvider {
        static var previews: some View {
            
            Group {
                NavigationView {
                    RoomDetail(room: testData[0])
                }
                NavigationView {
                    RoomDetail(room: testData[1])
                }
            }
            
        }
    }
    复制代码
  5. 增加动效

    • 给视频图标增加过渡动效
    if room.hasVideo && !zoomed {
                    Image(systemName: "video.fill")
                        .font(.title)
                        .padding(.all)
                        .transition(.move(edge: .leading))
                }
    复制代码

    9.gif

    • 给图片的动效延长时间

      .onTapGesture {
                          withAnimation(.easeInOut(duration: 2)) {
                              self.zoomed.toggle()
                          }
                      }
      复制代码
  6. 支持动态增加

    监测数据模型的改变,实时更新UI

    • 创建RoomStore储存Room模型

      import SwiftUI
      
      class RoomStore {
          var rooms: [Room]
          
          init(rooms: [Room] = []) {
              self.rooms = rooms
          }
      }
      复制代码
    • 遵守ObservableObject协议

      class RoomStore: ObservableObject {
        @Published var rooms: [Room]
      	// ···
      }
      复制代码
    • 声明EnvironmentObject类型变量

      @EnvironmentObject var store: RoomStore
      复制代码
    • 传入EnvironmentObject类型变量

      struct ContentView_Previews: PreviewProvider {
          static var previews: some View {
              ContentView()
                  .environmentObject(RoomStore(rooms: testData))
          }
      }
      复制代码
    • 列表中增加一个按钮

      List {
                      Button(action:{
                          
                      }) {
                          Text("Add Room")
                      }
        							// ForEach为它的每个集合项都创建一个视图
                      ForEach(store.rooms)  { room in
                          RoomCell(room: room)
                      }
                  }
      复制代码

      10.png

      Button(action:addRoom) {
                          Text("Add Room")
                      }
      
      func addRoom() {
              store.rooms.append(Room(name: "Hall 2", capacity: 2000))
          }
      复制代码

      10.gif

  7. 修改List的样式

    • 修改listStyle

      NavigationView {
                  List {
                		// ···    
                  }
                  .navigationBarTitle(Text("Rooms"))
                  .listStyle(GroupedListStyle())
              }
      复制代码
    • 设置分组

                  List {
                      Section {
                          Button(action:addRoom) {
                              Text("Add Room")
                          }
                      }
                      
                      Section {
                          ForEach(store.rooms)  { room in
                              RoomCell(room: room)
                          }
                      }
                      
                  }
      复制代码
  8. 支持动态删除

    func delete(at offsets: IndexSet) {
            store.rooms.remove(atOffsets: offsets)
    复制代码
    ForEach(store.rooms)  { room in
                            RoomCell(room: room)
                        }
                        .onDelete(perform: delete)
    复制代码

    11.gif

  9. 设置NavigationBarItem

            NavigationView {
                List {
     
                    
                }
                .navigationBarItems(trailing: EditButton())
            }
    复制代码
  10. 支持列表重新排序

        func move(from source: IndexSet, to destination: Int) {
            store.rooms.move(fromOffsets: source, toOffset: destination)
        }
    复制代码
    ForEach(store.rooms)  { room in
                            RoomCell(room: room)
                        }
                        .onDelete(perform: delete)
                        .onMove(perform: move)
    复制代码

    12.gif

  11. 设置预览环境

            Group {
                ContentView()
                    .environmentObject(RoomStore(rooms: testData))
                // 大字号环境
                ContentView()
                    .environmentObject(RoomStore(rooms: testData))
                    .environment(\.sizeCategory, .extraExtraLarge)
                // 深色模式
                ContentView()
                    .environmentObject(RoomStore(rooms: testData))
                    .environment(\.colorScheme, .dark)
                // 布局方向
                ContentView()
                    .environmentObject(RoomStore(rooms: testData))
                    .environment(\.layoutDirection, .rightToLeft)
                    .environment(\.locale, Locale(identifier: "ar"))
            }
    复制代码

总结

  • SwiftUI四个主要设计原则:

    • Declarative
    • Compositional
    • Automatic
    • Consistent
  • SwiftUI使用陈述性语法

  • 在SwiftUI中,Xcode预览可以让我们浏览、编辑和调试APP,我们甚至不需要运行项目工程

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享