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

相关推荐
小弟调调9 小时前
Vidwall: 支持将 4K 视频设置为动态桌面壁纸,兼容 MP4 和 MOV 格式
macos·swiftui·桌面应用·macos app
杂雾无尘10 小时前
开发者必看,全面解析应用更新策略,让用户无法拒绝你的应用更新!
ios·xcode·swift
帅次13 小时前
【iOS设计模式】深入理解MVC架构 - 重构你的第一个App
ios·swiftui·objective-c·iphone·swift·safari·cocoapods
东坡肘子18 小时前
高温与奇怪的天象 | 肘子的 Swift 周报 #092
人工智能·swiftui·swift
Swift社区18 小时前
Swift 解 LeetCode 320:一行单词有多少种缩写可能?用回溯找全解
开发语言·leetcode·swift
杂雾无尘2 天前
开发者必看:如何在 iOS 应用中完美实现动态自定义字体!
ios·swift·apple
iOS阿玮2 天前
AppStore教你一招免备案的骚操作!
uni-app·app·apple
Daniel_Coder2 天前
Swift Package 教程:创建、发布与使用详解
ios·swift·package
点金石游戏出海4 天前
每周资讯 | Krafton斥资750亿日元收购日本动画公司ADK;《崩坏:星穹铁道》新版本首日登顶iOS畅销榜
游戏·ios·业界资讯·apple·崩坏星穹铁道
旷世奇才李先生4 天前
Swift 安装使用教程
开发语言·ios·swift