SwiftUI 6.0(iOS 18)新容器视图修改器漫谈

概览

本届 WWDC 2024 观影正如火如荼的进行中,一片鸟语花香、枝繁叶茂的苹果树上不时结出几颗令人垂涎欲滴的美味苹果让秃头码农们欲罢不能。

如您所愿,在界面布局"利器" SwiftUI 这根蔓藤也长出不少喜人的果实,其中在 iOS 18.0 中新添加的容器视图修改器大家一定不能错过。

在本篇博文中,您将学到如下内容:

  1. 探囊取物:获取容器子视图
  2. 聚沙成塔:重新组织容器子视图

SwiftUI 6.0 新容器视图修改器让我们得以分解原本"铁板一块"的"黑箱"视图,打开无限可能。

无需等待,让我们马上开始探寻之旅吧!

Let's go!!!;)


1. 探囊取物:获取容器子视图

在 SwiftUI 6.0(iOS 18)之前,如果我们希望让自定义容器视图处理布局排版的细节则需要知道数据集,并根据数据集中每个单独元素构建对应的容器子视图:

swift 复制代码
import SwiftUI

struct MyContainerView<Content: View>: View {
    var items: [Int]
    @ViewBuilder var itemView: (Int) -> Content
    
    var body: some View {
        List {
            ForEach(items, id: \.self) { item in
                itemView(item)
            }
        }
    }
}

struct ContentView: View {
    var body: some View {
        MyContainerView(items: Array(1...10)) { item in
            Text("Item \(item)")
                .font(.title)
                .padding()
        }
    }
}

如上代码所示,我们将容器数据集合 items 和单个数据对应的子视图闭包 itemView() 从父视图传入到了 MyContainerView 中。

不过在某些情况下,我们希望在父视图中更灵活的创建容器子视图,比如以静态与动态相结合的方式:

swift 复制代码
struct ContentView: View {
    var body: some View {
        MyContainerView {
            Text("Item Header")
            
            Text("Item Subheader")
            
            ForEach(1...10, id: \.self) { item in
                Text("Item \(item)")
            }
            
            Text("Item Tail")
        }
    }
}

如上代码所示,我们试图将 3 个静态和 10 个动态产生的子视图和睦融洽的一起融入到容器视图 MyContainerView 中。但不幸的是,这在 SwiftUI 6.0 之前几乎是不可能完成的任务。


当然,如果我们巧妙运用一些 Swift Mirror "黑魔法"的话也不是绝对不可能。想要进一步了解 Mirror 黑魔法解决之道的小伙伴们,请移步如下链接观赏精彩的内容:


究其原因则是因为:在 SwiftUI 6.0 之前我们无法探查一个 View 的内部结构,它对我们来说就是一个彻头彻尾的"黑盒"视图。

不过从 SwiftUI 6.0(iOS 18)开始情况有了改观,苹果新增了若干容器视图修改器为我们排愁解忧。其中 ForEach(subviewOf:) 修改器方法就是这里我们所需要的那个"救世主"。

利用 ForEach(subviewOf:) 方法我们可以将 MyContainerView 容器视图修改为如下模样:

swift 复制代码
struct MyContainerView<Content: View>: View {
    @ViewBuilder var content: Content
    
    var body: some View {
        List {
            ForEach(subviewOf: content) {subview in
                                
                subview
                    .padding()
                    .frame(maxWidth: .infinity)
                    .background(.green.gradient)
            }
        }
    }
}

从上面修改后的代码中可以看到:现在我们只需专注于子视图本身,而无需再操心对应的子元素 Item 了。

除了 ForEach(subviewOf:) 以外,SwiftUI 6.0 还新增了一个类似的 ForEach(sectionOf: ) 修改器方法,如果小伙伴们希望自己的容器支持 Section 布局则会寻求它的鼎力相助哦。

2. 聚沙成塔:重新组织容器子视图

在聊完了将一整块黑盒视图从布局上分解为各个独立的子视图后,我们再来看看容器视图的另一种操作:将容器子视图重新"恣意"组合在一起。

假如我们希望自己的容器视图能够进一步根据子视图的数量或其它特定条件来布局界面,那么新的 Group(subviewsOf:) 修改器你绝对不能错过:

Group(subviewsOf: content) 方法让我们可以从整个容器的所有子视图而不是单个子视图来考虑布局大计:

swift 复制代码
struct HyListView<Content: View>: View {
    @ViewBuilder var content: Content
    
    var body: some View {
        VStack {
            Group(subviewsOf: content) { subviews in
                                
                if subviews.isEmpty {
                    Text("🐼No\nContent")
                        .font(.system(size: 100))
                        .padding()
                } else {
                    if let first = subviews.first {
                        first
                            .font(.largeTitle.weight(.heavy))
                            .background(
                                Circle()
                                    .foregroundStyle(.blue.gradient)
                                    .frame(width: 150, height: 150))
                    }
                    
                    if subviews.count >= 3 {
                       HStack {
                           subviews[1]
                           subviews[2]
                       }
                       .font(.title)
                       .foregroundStyle(.white)
                       .padding()
                       .background(.green)
                       
                        
                       if subviews.count > 3 {
                           
                           List {
                               subviews[3...]
                                   .frame(maxWidth: .infinity)
                                   .padding()
                                   .background(.yellow.gradient)
                           }
                           .listStyle(.plain)
                           .font(.title3.weight(.black))
                           .foregroundStyle(.red)
                           .padding()
                           
                       }
                        
                   }
                }
            }
            .transition(.slide)
        }
    }
}

struct ContentView: View {
    
    @State var count = 10.0
    
    var items: [Int] {
        if count == 0 {
            []
        } else {
            Array(0...Int(count))
        }
    }
    
    var body: some View {
        VStack {
            HyListView {
                ForEach(items, id: \.self) { i in
                    Text("Item \(i)")
                }
            }
            .animation(.bouncy, value: count)
            
            Spacer()
            
            HStack {
                Text("\(Int(count))")
                    .fontWeight(.heavy)
                Slider(value: $count, in: 0...20.0, label: {}, minimumValueLabel: {
                    Text("0")
                }, maximumValueLabel: {
                    Text("20")
                })
                .foregroundStyle(.gray)
            }
            .padding()
        }
    }
}

在上面代码中,我们创建了自己的 HyListView 容器视图,并且根据实际子视图的数量利用 Group(subviewsOf: content) 修改器方法来决定到底如何将它们"浑然天成"的组合在一起。

从实际运行效果可以看到,我们动态的根据容器内部的子视图来决定到底如何布局容器本身,再辅以动画整个效果简直 Nice 的不要不要的:

现在小伙伴们手握 SwiftUI 6.0 中新的容器视图修改"利器",在 App 的开发中是不是愈发感觉神采奕奕、容光焕发了呢?棒棒哒!

总结

在本篇博文中,我们讨论了 WWDC24 里 SwiftUI 6.0(iOS 18)中最新的容器视图修改器,并用简单的示例代码让小伙伴们豁然开朗!

感谢观赏,再会!8-)

相关推荐
tangweiguo0305198737 分钟前
SwiftUI布局完全指南:从入门到精通
ios·swift
T1an-15 小时前
最右IOS岗一面
ios
坏小虎7 小时前
Expo 快速创建 Android/iOS 应用开发指南
android·ios·rn·expo
光影少年8 小时前
Android和iOS原生开发的基础知识对RN开发的重要性,RN打包发布时原生端需要做哪些配置?
android·前端·react native·react.js·ios
北京自在科技8 小时前
Find My 修复定位 BUG,AirTag 安全再升级
ios·findmy·airtag
Digitally9 小时前
如何不用 USB 线将 iPhone 照片传到电脑?
ios·电脑·iphone
Sim148021 小时前
iPhone将内置本地大模型,手机端AI实现0 token成本时代来临?
人工智能·ios·智能手机·iphone
Digitally1 天前
如何将 iPad 上的照片传输到 U 盘(4 种解决方案)
ios·ipad
报错小能手1 天前
ios开发方向——swift并发进阶核心 @MainActor 与 DispatchQueue.main 解析
开发语言·ios·swift
LcGero1 天前
Cocos Creator 业务与原生通信详解
android·ios·cocos creator·游戏开发·jsb