• SwiftUI的认识与使用


     
    SwiftUI简介
    SwiftUI是苹果推出的一个新的UI框架,它使用了声明的方式,通过视图,基础控件和布局控件来进行页面的开发。
    SwiftUI具有跨平台性,一份SwiftUI代码可以同时跑在iOS、macOS、tvOS、watchOS平台上。
    SwiftUI编写的页面代码更简洁,广泛使用链式调用。
    SwiftUI视图和UIKit视图可以互相转换,对于将旧的项目过度到新布局方式比较友好。
    SwiftUI的运行速度优于UIKit,他减少了界面的层次结构,因此可以减少绘制步骤,并且他完全绕过了CoreAnimation,直接进入Metal,可以有优秀的渲染性能。
     
    其实声明式页面布局前端已经出现了很久,像React, Vue都是使用的声明式布局,声明式布局与命令式布局相比有很多优势,
    如:单向数据流,双向数据绑定,只要数据状态改变使用了这些数据的视图就会自动更新等。
    声明式布局是UI布局方式的未来,这次苹果从命令式编程过度到声明式编程算是一个大的进步。
     
    设计模式
    采用Struct组成的树形结构组织页面。叶子节点是基本控件。
    这棵Struct树类似于React的抽象语法树,它会在编译阶段将这些描述信息翻译成真实的UIKit中的UI控件。
     
    视图结构
    APP根入口
    APP的根入口是一个Struct结构体,它遵守APP协议
    1
    2
    3
    4
    5
    6
    7
    8
    @main
    struct WorldLandMarkApp: App {
        var body: some Scene {
            WindowGroup {
                ContentView()
            }
        }
    }
    App协议
    1
    2
    3
    4
    5
    6
    7
    8
    public protocol App {
        associatedtype Body : Scene
      
        @SceneBuilder @MainActor var body: Self.Body { get }
      
        @MainActor init()
    }

     

    页面结构体
    some表示返回的是一个遵守了View协议的不透明类型,也就是var body: some View {} 这个计算属性中,只能return一种类型,不能出现if a {Text()} else {List{}} 这样的2种类型。
    1
    2
    3
    4
    5
    struct LandmarkList: View {
        var body: some View {
            Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/)
        }
    }
    View协议
    associatedtype Body : View 表示协议中定义了一个新类型Body,这个Body遵守View协议。
    Self.Body 表示协议中的Body类型。Self表示类型本身,self表示实例变量本身
    1
    2
    3
    4
    public protocol View {
        associatedtype Body : View
        @ViewBuilder @MainActor var body: Self.Body { get }
    }
    每个页面swift文件中都有2个结构体,一个表示要开发的页面,另一个是使用Canvas进行展示出来的视图,其中struct ContentView_Previews: PreviewProvider可以根据Debug需要在外层嵌套导航条,展示Group组。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    import SwiftUI
      
    struct ContentView: View {
        var body: some View {
            Text("Hello, world!")
                .padding()
        }
    }
      
    struct ContentView_Previews: PreviewProvider {
        static var previews: some View {
            ContentView()
        }
    }
    它们不一定保持一致,如:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    struct LandmarkList_Previews: PreviewProvider {
        struct DeviceType: Identifiable {
            var id = UUID()
            var name: String
        }
          
        static var previews: some View {
            //使用ForEach展示多个设备
            ForEach([DeviceType(name: "iPhone 12"),DeviceType(name: "iPhone 13")]){ deviceItem in
                LandmarkList().previewDevice(PreviewDevice(rawValue: deviceItem.name))
                    .previewDisplayName(deviceItem.name)
            }
              
        }
    }

     

    状态双向绑定
    @State单页面状态绑定
    通过@State修饰的变量是做了双向绑定的,如果这个变量数据发生了改变,所有使用这个变量的视图都会自动更新。但是@State的修饰范围是当前的一个视图,如果想一个状态修改,整个APP范围内使用这个变量的视图全部都更新,则需要使用全局环境变量的模式。
    1
    2
    3
    4
    5
    6
    7
    @State private var isOpen
    struct LandmarkList: View {
          
        @State private var isOpen: Bool = false
        //@ObservedObject: 全局环境变量绑定
        @ObservedObject var userData: UserData = UserData()
    }
    @ObservableObject+@Published全局状态变量
    要使用全局状态变量,则需要创建一个class,并遵守ObservableObject协议。 然后在这个类中定义一个@Published修饰的变量 @Published var userLandmarks, 当@Published修饰的变量更新时,那么使用了@Published修饰的变量的视图就会对应更新。
    定义一个UserData,遵守ObservableObject协议
    1
    2
    3
    4
    5
    6
    7
    import SwiftUI
    import Combine
      
    class UserData: ObservableObject {
        @Published var userLandmarks:[Landmark] = landmarks
          
    }
    使用时,也要在对应的视图中加上 @ObservedObject修饰符,然后更新这个变量self.userData.userLandmarks[self.userLandmarkIndex].isFeatured.toggle()
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    struct LandmarkDetail: View {
        var landmark: Landmark
        @ObservedObject var userData: UserData
          
        var userLandmarkIndex: Int {
            userData.userLandmarks.firstIndex(where: {$0.id == landmark.id})!
        }
      
        var body: some View {
            Button(action: {
                self.userData.userLandmarks[self.userLandmarkIndex]
                    .isFeatured.toggle()
            }){
                if landmark.isFeatured {
                    Image("icon_rcxinhua_selected")
                        .resizable().frame(width: 20, height: 20, alignment: .center)
                } else {
                    Image("icon_rcxinhua_defaultselected")
                        .resizable().frame(width: 20, height: 20, alignment: .center)
                }
            }
        }
    }

     

    单向数据流
    用户操作导致@State变量发生了改变,
    @State变量改变导致使用了@State变量的UI视图就会自动更新
    继续等待用户操作触发@State变量发生变化
     

     

    UIKit控件与SwiftUI中控件的转换
    UIKit转SwiftUI
    通过UIViewRepresentable协议将UIView包装成SwifUI的View来使用。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    struct MapView: UIViewRepresentable {
        let view: UIView = UIView()
        func makeUIView(context: Context) -> some UIView {
            return view
        }
        func updateUIView(_ uiView: UIViewType, context: Context) {
            view.backgroundColor = .red
        }
    }
    SwiftUI转UIKit
    通过UIHostingController将SwiftUI包装成UIView
    1
    UIHostingController(rootView: ContentView())

     

    Model模型定义
    List,ForEach等要求被循环的每个元素都要有一个唯一的标识
    这样数据变更时,可以迅速定位刷新对应的UI,提高性能
    所以,元素要遵循Identifiable协议(实现id协议)
    1
    2
    3
    4
    struct Landmark: Identifiable {
        var id = UUID()
        let name: String
    }

     

    Demo地址
    https://github.com/zhfei/SwiftBasicKnowledge
     
     
    参考文章
    https://www.jianshu.com/p/0f7215591b08
    https://zhuanlan.zhihu.com/p/436779033
     
     
  • 相关阅读:
    MSDC 4.3 接口规范(7)
    在服务器上部署MVC 6应用程序
    每日一题:leetcode 2594 修车的最少时间
    python django初步搭建(一)
    【sfu】rtc 入口
    [云原生] 二进制安装K8S(中)部署网络插件和DNS
    【Labivew】G语言
    通过Dynamo批量打印PDF图纸
    (一)RabbitMQ实战——rabbitmq的核心组件及其工作原理介绍
    解码Hadoop系列——NameNode启动流程
  • 原文地址:https://www.cnblogs.com/zhou--fei/p/17595278.html