SwiftUI 6.0(iOS 18)将 Sections 也考虑进自定义容器子视图布局(下)

概述

在 WWDC 24 新推出的 SwiftUI 6.0 中,苹果对于容器内部子视图的布局有了更深入的支持。为了能够未雨绸缪满足实际 App 中所有可能的情况,我们还可以再接再厉,将 Sections 的支持也考虑进去。

SwiftUI 6.0 对容器子视图布局的增强支持可以认为是一个小巧的容器自定义布局(Custome Layout)简化器。

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

    1. 再进一步:支持段(Sections)的容器子视图布局
    • 5.1 更小的容器组织单位:Section
    • 5.2 支持 Sections 的 ForEach
    • 5.3 支持 Sections 的 Group

有了全新容器子视图布局机制的加持,现在对于任何需要适配自定义容器行为的情况我们都可以游刃有余、从容应对了!

那还等什么呢?Let's go!!!


5. 再进一步:支持段(Sections)的容器子视图布局

5.1 更小的容器组织单位:Section

在实际 App 布局的"摆放"中,我们往往会进一步用段(Sections)来划分容器中的海量内容,这在 List 或 Form 等 SwiftUI 内置容器里是司空见惯的事情:

swift 复制代码
struct ContentView: View {
    var body: some View {
        Form {
            Section("概述") {
                Color.yellow
                    .frame(height: 100)
                Text("Hello World")
            }
            
            Section("高级") {
                Color.red
                    .frame(height: 100)
                Toggle("成为黑悟空", isOn: .constant(true))
                    .tint(.pink)
                    .padding(.horizontal)
            }
            
            Section("其它") {
                Color.green
                    .frame(height: 100)
            }
        }
        .font(.largeTitle.bold())
    }
}

在上面的代码中,我们将之前容器中所有子视图都划分到 3 个独立的 Section 中去了:

那么问题来了:我们的自定义容器能否识别传入内容中的 Sections?

答案是肯定的!

可能聪明的小伙伴们都已经猜到了,之前我们讨论过的 SwiftUI 6.0 中 ForEach 和 Group 的新构造器都有支持 Sections 的重载形式:

下面我们就看看如何利用它们让自定义容器也支持 Sections 吧。

5.2 支持 Sections 的 ForEach

说出来大家可能不信,对之前 Carousel 容器的实现稍加修改,我们即可从容让它支持 Sections:

swift 复制代码
struct Carousel<Content: View>: View {
    @ViewBuilder var content: Content
    
    var body: some View {
        ScrollView {
            LazyVStack {
                ForEach(sections: content) { section in
                    VStack {
                        section.header
                        
                        ScrollView(.horizontal) {
                            LazyHStack {
                                section.content
                                    .containerRelativeFrame(.horizontal)
                                    .frame(minHeight: 100)
                            }
                            .scrollTargetLayout()
                        }
                        .scrollIndicators(.hidden)
                        .scrollTargetBehavior(.viewAligned)
                        .contentMargins(16)
                        
                        section.footer
                    }
                }
            }
        }
    }
}

从上面代码中我们可以看到,ForEach(sections:) 闭包传入的实参是一个 SectionConfiguration 类型。如果我们希望的话,还可以使用 SectionConfiguration 的 content 属性进一步分离段中的每一个子视图:

下面的代码演示了这种用法:

swift 复制代码
ForEach(sections: content) { section in
    VStack {
        section.header
        
        ScrollView(.horizontal) {
            LazyHStack {
                ForEach(section.content) { subview in
                    subview
                        .containerRelativeFrame(.horizontal)
                        .frame(minHeight: 100)
                }
            }
            .scrollTargetLayout()
        }
        .scrollIndicators(.hidden)
        .scrollTargetBehavior(.viewAligned)
        .contentMargins(16)
        
        section.footer
    }
}

现在,对于容器中包含若干 Sections 的情况,我们的 Carousel 也可以"应付自如"了:

swift 复制代码
struct ContentView: View {
    var body: some View {
        Carousel {
            Section("概述") {
                Color.yellow
                    .frame(height: 100)
                Text("Hello World")
            }
            
            Section("高级") {
                Color.red
                    .frame(height: 100)
                Toggle("成为黑悟空", isOn: .constant(true))
                    .tint(.pink)
                    .padding(.horizontal)
            }
            
            Section("其它") {
                Color.green
                    .frame(height: 100)
            }
        }
        .font(.largeTitle.bold())
    }
}

编译并在预览中看一看效果吧:

5.3 支持 Sections 的 Group

类似的,Group 同样对 Sections 提供了完善的支持。

如您所愿,我们也只需在 Magazine 容器的原有实现上"略施小计",即可大功告成:

swift 复制代码
struct Magazine<Content: View>: View {
    @ViewBuilder var content: Content
    
    var body: some View {
        ScrollView {
            LazyVStack {
                Group(sections: content) { sections in
                    if !sections.isEmpty {
                        GroupBox(label: sections[0].header) {
                            ZStack {
                                sections[0].content.frame(minHeight: 100)
                            }
                        }
                    }
                    
                    if sections.count > 1 {
                        ForEach(sections[1...]) { section in
                            section.header
                            
                            ScrollView(.horizontal) {
                                LazyHStack {
                                    section.content
                                        .containerRelativeFrame(.horizontal)
                                        .frame(minHeight: 100)
                                }
                            }
                            .contentMargins(16)
                            
                            section.footer
                        }
                    }
                }
            }
        }
    }
}

从上面的代码可以看到,我们对容器中的第一个段做了特殊对待。

还是那个 ContentView,只不过里面的容器现在是 Magazine "当家做主"了:

swift 复制代码
struct ContentView: View {
    var body: some View {
        Magazine {
            Section("概述") {
                Color.yellow
                    .frame(height: 100)
                Text("Hello World")
            }
            
            Section("高级") {
                Color.red
                    .frame(height: 100)
                Toggle("成为黑悟空", isOn: .constant(true))
                    .tint(.pink)
                    .padding(.horizontal)
            }
            
            Section("其它") {
                Color.green
                    .frame(height: 100)
            }
        }
        .font(.largeTitle.bold())
    }
} 

运行代码,看看我们支持 Sections 崭新的 Magazine 吧:

至此,我们自定义容器已然完全支持 Sections 啦!小伙伴们还不赶紧给自己一个大大的赞!棒棒哒!💯

总结

在本篇博文中,我们介绍了 SwiftUI 6.0(iOS 18)如何让自定义容器支持 Sections 布局,超简单的哦!

感谢观赏,再会吧!8-)

相关推荐
用户79457223954131 天前
【AFNetworking】OC 时代网络请求事实标准,Alamofire 的前身
objective-c·swift
报错小能手1 天前
SwiftUI 框架 认识 SwiftUI 视图结构 + 布局
ui·ios·swift
东坡肘子1 天前
被 Vibe 摧毁的版权壁垒,与开发者的新护城河 -- 肘子的 Swift 周报 #131
人工智能·swiftui·swift
报错小能手2 天前
ios开发方向——swift错误处理:do/try/catch、Result、throws
开发语言·学习·ios·swift
小夏子_riotous2 天前
openstack的使用——5. Swift服务的基本使用
linux·运维·开发语言·分布式·云计算·openstack·swift
mCell2 天前
MacOS 下实现 AI 操控电脑(Computer Use)的思考
macos·agent·swift
用户79457223954132 天前
【DGCharts】iOS 图表渲染事实标准——8 种图表类型、高度可定制,3 行代码画出一条折线
swiftui·swift
chaoguo12342 天前
Any metadata 的内存布局
swift·metadata·value witness table
tangweiguo030519874 天前
SwiftUI布局完全指南:从入门到精通
ios·swift
用户79457223954134 天前
【RxSwift】Swift 版 ReactiveX,响应式编程优雅处理异步事件流
swift·rxswift