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

相关推荐
iOS阿玮16 小时前
苹果审核被拒4.1-Copycats过审技巧实操
uni-app·app·apple
大熊猫侯佩18 小时前
SwiftUI 如何取得 @Environment 中 @Observable 对象的绑定?
swiftui·swift·apple
大熊猫侯佩19 小时前
SwiftUI 6.0(iOS 18)将 Sections 也考虑进自定义容器子视图布局(下)
swiftui·swift·apple
大熊猫侯佩20 小时前
SwiftUI 6.0(iOS 18)将 Sections 也考虑进自定义容器子视图布局(上)
swiftui·swift·apple
胖虎120 小时前
SwiftUI 数据绑定与视图更新(@State、@ObservedObject、@EnvironmentObject)
ios·swiftui·swift
大熊猫侯佩2 天前
用接地气的例子趣谈 WWDC 24 全新的 Swift Testing 入门(二)
单元测试·swift·apple
我现在不喜欢coding2 天前
swift中的self,Self,Class(struct).Type让你头大了嘛?
ios·swift