Overview
This year’s WWDC 2024 movie is in full swing, and there are a few delicious, coveted apples on a floral, leafy apple tree from time to time.
As you would like, the interface layout “sharp tool” SwiftUI this vine also grows a lot of delightful fruit, among which the newly added container view modifier in iOS 18.0 must not be missed.
In this blog post, you will learn:
- Overview
- 1. Sachet: Get container subview
- 2. Sand Towers: Reorganizing Container Subviews
- Summary
The SwiftUI 6.0 New Container View modifier allows us to break down the original “black box” view of a “piece of iron” to open infinite possibilities.
Don’t wait, let’s start exploring now!
Let’s go!!! 😉
The video lesson for this article is here, and you are welcome to enjoy it.
SwiftUI 6.0 (iOS 18) New Container View Modifier
1. Sachet: Get container subview
Before SwiftUI 6.0 (iOS 18), if we wanted to let the custom container view handle the details of the layout, we needed to know the data set and build the corresponding container sub-view based on each individual element in the data set:
import SwiftUI
struct MyContainerView<Content: View>: View {
var items: [Int]
@ViewBuilder var itemView: (Int) -> Content
var body: some View {
List {
ForEach(items, id: \.self) { item in
itemView(item)
}
}
}
}
struct ContentView: View {
var body: some View {
MyContainerView(items: Array(1...10)) { item in
Text("Item \(item)")
.font(.title)
.padding()
}
}
}
As shown in the above code, we passed the child view closure itemView for the container data set items and individual data from the parent view into MyContainerView.
In some cases, however, we would like to create container child views more flexibly in the parent view, such as in a combination of static and dynamic:
struct ContentView: View {
var body: some View {
MyContainerView {
Text("Item Header")
Text("Item Subheader")
ForEach(1...10, id: \.self) { item in
Text("Item \(item)")
}
Text("Item Tail")
}
}
}
As the code above shows, we are trying to integrate three static and ten dynamically generated sub-views together into container view MyContainerView. But unfortunately, this was almost impossible before SwiftUI 6.0.
Of course, it’s not absolutely impossible if we use some of Swift Mirror’s “black magic” skillfully. To learn more about the Mirror Black Magic solution, let’s move on to the following links:
- SwiftUI Create a HStack (3) that contracts freely: “Magic Mirror Magic Mirror, I love you”
The reason for this is that we were unable to explore the internal structure of a view until SwiftUI 6.0, which is a completely “black box” view for us.
However, starting with SwiftUI 6.0 (iOS 18), Apple added several container view modifiers to ease our concerns. Where the ForEach subviewOf: modifier method is the “savior” we need here.
Using the ForEach subviewOf: method we can modify the MyContainerView container view to look like this:
struct MyContainerView<Content: View>: View {
@ViewBuilder var content: Content
var body: some View {
List {
ForEach(subviewOf: content) {subview in
subview
.padding()
.frame(maxWidth: .infinity)
.background(.green.gradient)
}
}
}
}
From the code modified above, we can see that now we only need to focus on the sub-view itself instead of the corresponding sub-element Item.
In addition to ForEach subviewOf:, SwiftUI 6.0 has added a similar ForEach sectionOf: modifier approach that partners would look for if they wanted their containers to support Section layouts.
2. Sand Towers: Reorganizing Container Subviews
After chatting up a whole black box view from the layout to separate sub-views, let’s take a look at another operation of the container view: Reassemble the container sub-views together.
If we want our container view to be able to lay out the interface further based on the number of sub-views or other specific criteria, then you must not miss the new subviewsOf: modifier:
The subviewsOf: content approach allows us to consider layout generalizations from all sub-views of the entire container instead of a single sub-view:
struct HyListView<Content: View>: View {
@ViewBuilder var content: Content
var body: some View {
VStack {
Group(subviewsOf: content) { subviews in
if subviews.isEmpty {
Text("🐼No\nContent")
.font(.system(size: 100))
.padding()
} else {
if let first = subviews.first {
first
.font(.largeTitle.weight(.heavy))
.background(
Circle()
.foregroundStyle(.blue.gradient)
.frame(width: 150, height: 150))
}
if subviews.count >= 3 {
HStack {
subviews[1]
subviews[2]
}
.font(.title)
.foregroundStyle(.white)
.padding()
.background(.green)
if subviews.count > 3 {
List {
subviews[3...]
.frame(maxWidth: .infinity)
.padding()
.background(.yellow.gradient)
}
.listStyle(.plain)
.font(.title3.weight(.black))
.foregroundStyle(.red)
.padding()
}
}
}
}
.transition(.slide)
}
}
}
struct ContentView: View {
@State var count = 10.0
var items: [Int] {
if count == 0 {
[]
} else {
Array(0...Int(count))
}
}
var body: some View {
VStack {
HyListView {
ForEach(items, id: \.self) { i in
Text("Item \(i)")
}
}
.animation(.bouncy, value: count)
Spacer()
HStack {
Text("\(Int(count))")
.fontWeight(.heavy)
Slider(value: $count, in: 0...20.0, label: {}, minimumValueLabel: {
Text("0")
}, maximumValueLabel: {
Text("20")
})
.foregroundStyle(.gray)
}
.padding()
}
}
}
In the above code, we created our own HyListView container views and used the subviewsOf: content modifier method to determine exactly how to combine them together “naturally” based on the number of actual sub-views.
As you can see from the actual running effect, we dynamically determine how to lay out the container itself according to the sub-view inside the container, and then add the animation to the whole effect, which is Nice:
Now, the new container view in the SwiftUI 6.0 is in the hands of the partners to modify the “sharp tools.” Is the development of the app more and more refreshing? Boutique!
Summary
In this blog post, we talked about the latest container view modifier in SwiftUI 6.0 (iOS 18) in WWDC24, and let the buddies blush with simple sample code!
Thank you for watching and meeting again! 😎
From
import SwiftUI
struct MyContainerView<Content: View>: View {
var items: [Int]
@ViewBuilder var itemView: (Int) -> Content
var body: some View {
List {
ForEach(items, id: \.self) { item in
itemView(item)
}
}
}
}
struct ContentView: View {
var body: some View {
MyContainerView(items: Array(1...10)) { item in
Text("Item \(item)")
.font(.title)
.padding()
}
}
}