SwiftUI 6.0(iOS 18)自定义容器值(Container Values)让容器布局渐入佳境(下)

概述

我们在之前多篇博文中已经介绍过 SwiftUI 6.0(iOS 18)新增的自定义容器布局机制。现在,如何利用它们对容器内容进行"探囊取物"和"聚沙成塔",我们已然胸有成竹了。

然而,除了上述鬼工雷斧般的新技巧之外,SwiftUI 6.0 其实还提供了能更进一步增加容器布局自由度的新利器:自定义容器值(Container Values)。

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

  1. 自定义容器值如何让容器布局更加无拘无束?
  2. 水到渠成:在 Section 上应用自定义容器值

相信 SwiftUI 6.0 中全新的自定义容器值能够让容器布局更加"脱胎换骨"、灵动自由。

那还等什么呢?让我们继续前一篇的容器大冒险吧!Let's go!!!;)


4. 自定义容器值如何让容器布局更加无拘无束?

我们首先需要定义自己的 Container Values。正如上篇博文所说过的,这可以通过 @Entry 宏来轻松完成:

swift 复制代码
extension ContainerValues {
    @Entry var isNeedHighlightPrefix = false
}

为了方便起见,我们顺手在视图扩展中创建一个 highlightPrefix() 辅助方法:

swift 复制代码
extension View {
    func highlightPrefix(_ enable: Bool = true) -> some View {
        containerValue(\.isNeedHighlightPrefix, enable)
    }   
}

接着,我们率先在 NiceListContainer 容器 body 中对醒目显示做出支持:

swift 复制代码
struct NiceListContainer<Content: View>: View {
    @ViewBuilder var content: Content
    
    private var hightlight: some View {
        RoundedRectangle(cornerRadius: 8)
            .foregroundStyle(.red.gradient)
            .frame(width: 5.0)
            .padding(.vertical)
            .shadow(radius: 3.0)
    }
    
    var body: some View {
        List {
            ForEach(subviews: content) { subview in
                HStack {
                    if subview.containerValues.isNeedHighlightPrefix {
                        hightlight
                    }
                    subview
                }
            }
        }
    }
}

可以看到上面代码其实很简单:我们通过容器子视图的 containerValues 访问了它的 isNeedHighlightPrefix 自定义容器值,容易的简直不要不要的。

最后,我们只需在主视图中为所有必要的容器元素应用醒目显示效果即可:

swift 复制代码
struct ContentView: View {
    var body: some View {
        NiceListContainer {
            Group {
                Text("Hello")
                    .foregroundStyle(.green)
                    .highlightPrefix()
                
                Text("World")
                    .foregroundStyle(.red)
                    .highlightPrefix()
                
                Text("大熊猫侯佩")
                    .foregroundStyle(.brown)
                    .font(.system(size: 55, weight: .black))
                
                Image(systemName: "globe.europe.africa")
                    .resizable()
                    .aspectRatio(contentMode: .fit)
                    .foregroundStyle(.orange.gradient)
                
                HStack {
                    Text("战斗力")
                    Slider(value: .constant(10))
                        .padding()
                }
                .tint(.pink)
                .highlightPrefix()
            }
            .font(.title.weight(.heavy))
        }
    }
}

编译在预览中看一下成果吧:

正是我们想要的!棒棒哒!💯

5. 水到渠成:在 Section 上应用自定义容器值

除了在子视图上应用自定义容器值以外,我们还可以同样在容器的段(Section)上应用它们。

也就是说:除了在 SubView 以外,我们在 SectionConfiguration 中也可以通过 containerValues 属性访问自定义容器值。

假设在容器中有若干个 Section,我们需要将某些 Section 置顶显示,其余的段则顺其自然。和前面类似,我们需要让容器的使用者来决定哪些 Section 放在顶部(需要注意的是:置顶 Section 出现的位置不一定在 @ViewBuilder 闭包代码的前面)。

首先,新建段置顶功能对应的自定义容器值和视图扩展:

swift 复制代码
extension ContainerValues {
    @Entry var isOnTheTop = false
}

extension View {
    func onTheTop(_ enable: Bool = true) -> some View {
        containerValue(\.isOnTheTop, enable)
    }
}

接着,实现我们 NiceListContainer 容器的主体布局:

swift 复制代码
struct NiceListContainer<Content: View>: View {
    @ViewBuilder var content: Content
    
    private var hightlight: some View {
        RoundedRectangle(cornerRadius: 8)
            .foregroundStyle(.red.gradient)
            .frame(width: 5.0)
            .padding(.vertical)
            .shadow(radius: 3.0)
    }
    
    var body: some View {
        List {
            Group(sections: content) { sections in
                let onTheTops = sections.filter(\.containerValues.isOnTheTop)
                let others = sections.filter { !$0.containerValues.isOnTheTop }
                
                if !onTheTops.isEmpty {
                    ForEach(onTheTops) { section in
                        Section {
                            HStack {
                                Image(systemName: "star")
                                    .foregroundStyle(.yellow.gradient)
                                    .font(.largeTitle)
                                
                                ScrollView(.horizontal, showsIndicators: false) {
                                    LazyHStack {
                                        ForEach(subviews: section.content) { subview in
                                            HStack {
                                                if subview.containerValues.isNeedHighlightPrefix {
                                                    hightlight
                                                }
                                                subview
                                            }
                                        }
                                    }
                                }
                            }
                        } header: {
                            if let header = section.header.first {
                                HStack {
                                    Image(systemName: "teddybear.fill")
                                        .font(.largeTitle)
                                        .foregroundStyle(.cyan)
                                    header
                                }
                            }
                        }
                    }
                }
                
                if !others.isEmpty {
                    ForEach(others) { section in
                        Section {
                            ForEach(subviews: section.content) { subview in
                                HStack {
                                    if subview.containerValues.isNeedHighlightPrefix {
                                        hightlight
                                    }
                                    
                                    subview
                                }
                            }
                        } header: {
                            if let header = section.header.first {
                                header
                            }
                        }
                    }
                }
            }
        }
    }
}

在上面的代码中,我们通过检查容器中段的 containerValues.isOnTheTop 容器值,过滤出所有需要显示在顶部和所有其它的 Section。注意,在 NiceListContainer 容器中我们仍然同时支持子视图上的 isNeedHighlightPrefix 容器值。

最后,我们只需在想要置顶的 Section 上应用 onTheTop() 修改器即可:

swift 复制代码
struct ContentView: View {
    var body: some View {
        NiceListContainer {
            Group {
                Section("欢迎语") {
                    Text("Hello")
                        .foregroundStyle(.green)
                        .highlightPrefix()
                    
                    Text("World")
                        .foregroundStyle(.red)
                        .highlightPrefix()
                }
                
                Section("主人入住") {
                    Group {
                        Text("大熊猫侯佩")
                        
                        Text("🐼 Hopy")
                    }
                    .foregroundStyle(.brown)
                    .font(.system(size: 55, weight: .black))
                    .highlightPrefix()
                }
                .onTheTop()
                
                Section("图片") {
                    Image(systemName: "globe.europe.africa")
                        .resizable()
                        .aspectRatio(contentMode: .fit)
                        .foregroundStyle(.orange.gradient)
                }
                
                Section("其它") {
                    HStack {
                        Text("战斗力")
                        Slider(value: .constant(10))
                            .padding()
                    }
                    .tint(.pink)
                    .highlightPrefix()
                }
            }
            .font(.title.weight(.heavy))
        }
    }
}

如上代码所示:现在我们在需要顶部显示的 Section 上(可以不止一个)应用了 onTheTop() 置顶修改器,并在 Section 中的某些子视图上应用了之前的醒目显示效果。

运行代码,在预览中见证一下我们美美哒的成果吧:

至此,我们彻底掌握了 SwiftUI 6.0 中定制容器元素的解析与重组,并利用自定义容器值让容器布局更加随心所欲、逍遥自在!棒棒哒!💯

总结

在本篇博文中,我们补全了 SwiftUI 6.0(iOS 18)定制容器的最后一块拼图:自定义容器值(Container Values),并利用它们让容器布局自由度更进一步,小伙伴们值得拥有!

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

相关推荐
OliverQiu2 小时前
开源一个 macOS 小工具:集中管理 ChatGPT 的网络启动配置
macos·chatgpt·swiftui·mac·proxy·codex·dmg
用户38034165882975 小时前
Flutter 迁 SwiftUI:AI 把页面"搬"过来只用了两周,我们补结构花了两个月
ios·swift
茶底世界之下8 小时前
四角变换后的最小包围视口,为什么不能只把宽高取整?
ios·swift
茶底世界之下1 天前
预乘 Alpha 图层合成为什么会越叠越暗?
架构·swift
茶底世界之下3 天前
时间线效果层,为什么不能只复用一个处理器?
ios·swift
Cc.Y3 天前
Swift深度链接的极简之道
ios·cocoa·swift
传奇开心果编程4 天前
【SwiftUI提高练中学】第9课 安全与隐私
swiftui·移动开发·swift·编程语言理论·开发范式·编程框架·声明式ui
CocoaKier4 天前
苹果商店详情顶部头图已面向所有开发者开放!
ios·apple
茶底世界之下4 天前
动态切换 nearest / linear 时,为什么不能只替换 sampler
ios·swift
应用市场4 天前
把旧 Pixel 变成相册备份中转站(上):Mac 到安卓的照片传输工具设计——流式上传、sha256 校验、adb forward 与 Bonjour
android·macos·adb·kotlin·swift