• WWDC 23 之后的 SwiftUI 有哪些新功能


    在这里插入图片描述

    前言

    WWDC 23 已经到来,SwiftUI 框架中有很多改变和新增的功能。在本文中将主要介绍 SwiftUI 中数据流动画ScrollView搜索新手势等功能的新变化。

    数据流

    Swift 5.9 引入了宏功能,成为 SwiftUI 数据流的核心。SwiftUI 不再使用 Combine,而是使用新的 Observation 框架。Observation 框架为我们提供了 Observable 协议,必须使用它来允许 SwiftUI 订阅更改并更新视图。

    @Observable
    final class Store {
        var products: [String] = []
        var favorites: [String] = []
        
        func fetch() async {
            try? await Task.sleep(nanoseconds: 1_000_000_000)
            // load products
            products = [
                "Product 1",
                "Product 2"
            ]
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    不需要在代码中遵循 Observable 协议。相反,可以使用 @Observable 宏来标记你的类型,它会自动为符合 Observable 协议。也不再需要 @Published 属性包装器,因为 SwiftUI 视图会自动跟踪任何可观察类型的可用属性的更改。

    struct ProductsView: View {
        @State private var store = Store()
        
        var body: some View {
            List(store.products, id: \.self) { product in
                Text(verbatim: product)
            }
            .task {
                if store.products.isEmpty {
                    await store.fetch()
                }
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    以前,有一系列的属性包装器,如 StateStateObjectObservedObjectEnvironmentObject,你应该了解何时以及为何使用它们。

    现在,状态管理变得更加简单。对于值类型(如字符串和整数)和符合 Observable 协议的引用类型,只需使用 State 属性包装器。

    struct FavoriteProductsView: View {
        let store: Store
        
        var body: some View {
            List(store.favorites, id: \.self) { product in
                Text(verbatim: product)
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    在上面的示例中,有一个接受 Store 类型的视图。在之前的 SwiftUI 框架版本中,应该使用 @ObservedObject 属性包装器来订阅更改。现在不需要了,因为 SwiftUI 视图会自动跟踪符合 Observable 协议的类型的更改。

    struct EnvironmentViewExample: View {
        @Environment(Store.self) private var store
        
        var body: some View {
            Button("Fetch") {
                Task {
                    await store.fetch()
                }
            }
        }
    }
    
    struct ProductsView: View {
        @State private var store = Store()
        
        var body: some View {
            List(store.products, id: \.self) { product in
                Text(verbatim: product)
            }
            .task {
                if store.products.isEmpty {
                    await store.fetch()
                }
            }
            .toolbar {
                NavigationLink {
                    EnvironmentViewExample()
                } label: {
                    Text(verbatim: "Environment")
                }
            }
            .environment(store)
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    还可以使用 Environment 属性包装器与 environment 视图修饰符配对,将可观察类型放入 SwiftUI 环境中。不需要使用 @EnvironmentObject 属性包装器或 environmentObject 视图修饰符。同样的 Environment 属性包装器现在适用于可观察类型。

    struct BindanbleViewExample: View {
        @Bindable var store: Store
        
        var body: some View {
            List($store.products, id: \.self) { $product in
                TextField(text: $product) {
                    Text(verbatim: product)
                }
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    每当需要从可观察类型中提取绑定时,可以使用新的 Bindable 属性包装器。

    动画

    动画始终是 SwiftUI 框架中最重要的部分。在 SwiftUI 中轻松实现任何动画,但之前的框架版本缺少一些现在具有的功能。

    struct AnimationExample: View {
        @State private var value = false
        
        var body: some View {
            Text(verbatim: "Hello")
                .scaleEffect(value ? 2 : 1)
                .onTapGesture {
                    withAnimation {
                        value.toggle()
                    } completion: {
                        print("Animation have finished")
                    }
                }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    如上例所示,我们有了新版本的 withAnimation 函数,允许提供动画完成处理程序。这是一个很好的补充,现在您可以构建阶段性动画。

    enum Phase: CaseIterable {
        case start
        case loading
        case finish
        
        var offset: CGFloat {
            // Calculate offset for the particular phase
            switch self {
            case start: 100.0
            case loading: 0.0
            case finish: 50.0
            }
        }
    }
    
    struct PhasedAnimationExample: View {
        @State private var value = false
        
        var body: some View {
            PhaseAnimator(Phase.allCases, trigger: value) { phase in
                LoadingView()
                    .offset(x: phase.offset)
            } animation: { phase in
                switch phase {
                case .start: .easeIn(duration: 0.3)
                case .loading: .easeInOut(duration: 0.5)
                case .finish: .easeOut(duration: 0.1)
                }
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    SwiftUI 框架引入了新的 PhaseAnimator 视图,它遍历阶段序列,允许为每个阶段提供不同的动画,并在阶段更改时更新内容。还有 KeyframeAnimator 视图,可以使用关键帧来实现动画。

    ScrollView

    今年 ScrollView 有了很多优秀的新增功能。首先,可以使用 scrollPosition 视图修饰符来观察内容偏移量。

    struct ContentView: View {
        @State private var scrollPosition: Int? = 0
        
        var body: some View {
            ScrollView {
                Button("Scroll") {
                    scrollPosition = 80
                }
                
                ForEach(1..<100, id: \.self) { number in
                    Text(verbatim: number.formatted())
                }
                .scrollTargetLayout()
            }
            .scrollPosition(id: $scrollPosition)
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    如上例所示,使用 scrollPosition 视图修饰符将内容偏移量绑定到一个状态属性上。每当用户滚动视图时,它会通过设置第一个可见视图的标识来更新绑定。还可以通过编程方式滚动到任何视图,但是,应该使用 scrollTargetLayout 视图修饰符来告诉 SwiftUI 框架在哪里查找标识以更新绑定。

    struct ContentView: View {
        var body: some View {
            ScrollView {
                ForEach(1..<100, id: \.self) { number in
                    Text(verbatim: number.formatted())
                }
                .scrollTargetLayout()
            }
            .scrollTargetBehavior(.paging)
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    可以通过使用 scrollTargetBehavior 视图修饰符来更改滚动行为。它允许在滚动视图中启用分页。

    搜索

    与搜索相关的视图修饰符也有一些很好的新增功能。例如,可以通过编程方式聚焦到搜索字段。

    struct ProductsView: View {
        @State private var store = Store()
        @State private var query = ""
        @State private var scope: Scope = .default
        
        var body: some View {
            List(store.products, id: \.self) { product in
                Text(verbatim: product)
            }
            .task {
                if store.products.isEmpty {
                    await store.fetch()
                }
            }
            .searchable(text: $query, isPresented: .constant(true), prompt: "Query")
            .searchScopes($scope, activation: .onTextEntry) {
                Text(verbatim: scope.rawValue)
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    如上例所示,可以使用可搜索视图修饰符的 isPresented 参数来显示/隐藏搜索字段。还可以使用 searchScopes 视图修饰符的 activation 参数来定义范围的可见性逻辑。

    新手势

    新增的 RotateGestureMagnifyGesture 使我们能够跟踪视图的旋转和放大。

    struct RotateGestureView: View {
        @State private var angle = Angle(degrees: 0.0)
    
        var rotation: some Gesture {
            RotateGesture()
                .onChanged { value in
                    angle = value.rotation
                }
        }
    
        var body: some View {
            Rectangle()
                .frame(width: 200, height: 200, alignment: .center)
                .rotationEffect(angle)
                .gesture(rotation)
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    新增的小功能

    增加了全新的 ContentUnavailableView 类型,当需要显示空视图时可以使用它。示例如下:

    struct ProductsView: View {
        @State private var store = Store()
        
        var body: some View {
            List(store.products, id: \.self) { product in
                Text(verbatim: product)
            }
            .background {
                if store.products.isEmpty {
                    ContentUnavailableView("Products list is empty", systemImage: "list.dash")
                }
            }
            .task {
                if store.products.isEmpty {
                    await store.fetch()
                }
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    还有新增了新的视图修饰符,允许调整列表中的间距。可以使用 listRowSpacinglistSectionSpacing 视图修饰符来设置列表中所需的间距。EnvironmentValues 结构体包含了一系列与最新平台更新相关的新属性,例如 isActivityFullscreenshowsWidgetContainerBackground。Swift Charts 也具有可滚动和可动画的功能。

    #Preview {
        ContentView()
    }
    
    • 1
    • 2
    • 3

    还有一个新的 Preview 宏,可以让我们轻松地为 UIKit 和 SwiftUI 构建预览,只需几行代码。

    总结

    SwiftUI 框架中有许多小的新增功能,我们将会继续分享。希望能帮到你。

    特别感谢 Swift社区 编辑部的每一位编辑,感谢大家的辛苦付出,为 Swift社区 提供优质内容,为 Swift 语言的发展贡献自己的力量。

  • 相关阅读:
    阿里云k8s环境下,因slb限额导致的发布事故
    【Head First Java 笔记】参数传递
    1.1 redis介绍
    判断语音识别结果好坏的指标——python实现
    直播交友app开发_1对1视频直播聊天APP定制_语音直播交友软件源码
    大数据ClickHouse进阶(十二):ClickHouse的explain查询执行计划
    java.lang.ClassNotFoundException:如何解决
    猿创征文 |【STM32】I2C协议完成温湿度检测
    广泰转债,雅创转债上市价格预测
    聚焦关键要素|打造智慧园区综合运营决策平台解决方案
  • 原文地址:https://blog.csdn.net/qq_36478920/article/details/131127123