Custom Container View with ForEach(subviews:content:)

Hi, I’m trying to implement a custom horiztonal Picker using the newly introduced API on ForEach like the following:

struct ScopeBar<SelectionValue: Hashable, Content: View>: View {
// MARK: Initializers
@Binding
private var selection: SelectionValue
@ViewBuilder
private var content: () -> Content
public init(selection: Binding<SelectionValue>, @ViewBuilder content: @escaping () -> Content) {
_selection = selection
self.content = content
}
// MARK: Content
var body: some View {
ScrollView(.horizontal) {
LazyHStack {
ForEach(subviews: content()) { subview in
Button {
// selection = subview.id as! SelectionValue
} label: {
subview
}
.tag(subview.id)
}
}
}
}
}

The following implementation is what I’m trying to achieve for the custom container usage:

struct MyOtherView: View {
enum SomeScopes: String, CaseIterable, Hashable {
case first
case second
case third
case fourth
case fifth
}
@State
private var selection: SomeScopes = .first
var body: some View {
ScopeBar(selection: $selection) {
ForEach(SomeScopes.allCases, id: \.rawValue) { scope in
Text(scope.rawValue)
}
}
}
}

I’m having some trouble figuring out two things:

  • How can I make my SelectionValue equal to the Subview.ID so that my selection behaves internally like a Picker would?
  • Say I wanted to add an Image(…) in addition to the Text(scope.rawValue), how would I do it in order for the Button { … } label: { … } to use both views, and not each separately…?

Have you tried creating a custom PickerStyle instead? this way you're able to customize the appearance and interaction without having to re-implement its functionality ?

You should review Creating custom container views sample code and Demystify SwiftUI containers WWDC session to learn more about container views. The learning from those resources should answer your questions and get you going.

Thank you for your reply! From what I read in the PickerStyle documentation, there is no public API to create a custom one… 🤔

Anyway, revisiting the mentioned sessions allowed me to derive the ID property from the containerValues rather than the Subview.ID, like so:

if let selection = subview.containerValues.tag(for: SelectionValue.self) {
}

All I have to do then is tag my custom view in place like so:

ForEach(SomeEnum.allCases, id: \.self) { enumCase in
Text(enumCase.localizedString)
.tag(enumCase)
}

I’m unsure wether this is the best way to go, but at least it technically solves my initial problem and works quite well.

Custom Container View with ForEach(subviews:content:)
 
 
Q