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-)

相关推荐
season_zhu6 小时前
iOS开发:关于日志框架
ios·架构·swift
iOS阿玮8 小时前
苹果2024透明报告看似更加严格的背后是利好!
uni-app·app·apple
大熊猫侯佩10 小时前
SwiftUI 中如何花样玩转 SF Symbols 符号动画和过渡特效
swiftui·swift·apple
大熊猫侯佩11 小时前
SwiftData 共享数据库在 App 中的改变无法被 Widgets 感知的原因和解决
swiftui·swift·apple
大熊猫侯佩11 小时前
使用令牌(Token)进一步优化 SwiftData 2.0 中历史记录追踪(History Trace)的使用
数据库·swift·apple
大熊猫侯佩11 小时前
SwiftUI 在 iOS 18 中的 ForEach 点击手势逻辑发生改变的解决
swiftui·swift·apple
iOS阿玮1 天前
苹果审核被拒4.1-Copycats过审技巧实操
uni-app·app·apple
大熊猫侯佩1 天前
SwiftUI 如何取得 @Environment 中 @Observable 对象的绑定?
swiftui·swift·apple