Post

Replies

Boosts

Views

Activity

Reply to iOS 18 Subviews and Environment values
Ok my bad this is intended. "Subviews are proxies to the resolved view they represent, meaning that modifiers applied to the original view will be applied before modifiers applied to the subview, and the view is resolved using the environment of its container, not the environment of the its subview proxy." from the documentation: https://developer.apple.com/documentation/swiftui/subview But this question remains true to be answered: "What I'm looking for is "insert"/"propagate" values to each subview during subview iteration"
Topic: UI Frameworks SubTopic: SwiftUI Tags:
4w
Reply to iOS 18 Subviews and Environment values
I guess this is one way to do it. TLDR; Create a resolver view to wrap your content views. Use an environment closure to pullback values from it. Re-distribute these values using the containerValue API to propagate value to subviews. Use .onChange(of: subview.containerValues.foo)API on subview to be notified of changes. Let's say we want to design an API like that: // MARK: Client Stepper { StepView { Text("Becoming spartan") } content: { FooView() } StepView { Text("Dying in glory") } content: { BarView() } } where FooView, BarView could access to a complete closure through environment to notify it finishes (same way as @Environment(\.dismiss)): struct FooView: View { @Environment(\.isComplete) private var isComplete var body: some View { VStack { Text("This is the part where you become a spartan") HStack(alignment: .bottom) { Rectangle() .fill(Color.blue.gradient) .frame(width: 50, height: 25) Rectangle() .fill(Color.cyan.gradient) .frame(width: 50, height: 10) Rectangle() .fill(Color.pink.gradient) .frame(width: 50, height: 30) } Button { isComplete() } label: { Text("This is Sparta") } } } } Client could add as many step as he wants adding more and more views to the Stepper // MARK: Library struct Stepper<Content: View>: View { @State private var steps = [StepState()] @ViewBuilder let content: Content var body: some View { VStack(spacing: 30) { Group(subviews: content) { subviews in ForEach(Array(zip(steps.indices, subviews)), id: \.0) { index, subview in subview .onChange(of: subview.containerValues.isComplete) { oldValue, newValue in $steps[index].wrappedValue.isComplete = newValue } } .onAppear { self.steps = Array(repeating: StepState(), count: subviews.count) } } } } } struct StepView<Title: View, Info: View>: View { @ViewBuilder var titleContent: Title @ViewBuilder var content: () -> Info @State private var isComplete = false @State private var isExpanded = false var body: some View { VStack { Button { withAnimation { isExpanded.toggle() } } label: { HStack { titleContent Spacer() if isComplete { Image(systemName: "checkmark.circle.fill") .resizable() .frame(width: 24, height: 24) .foregroundStyle(.green) } Image(systemName: "chevron.right") .resizable() .scaledToFit() .frame(width: 24, height: 24) .rotationEffect(isExpanded && !isComplete ? .degrees(-90) : .degrees(90)) } } .disabled(isComplete) if isExpanded, !isComplete { content() .environment(\.isComplete, { withAnimation { isComplete = true } }) .transition( .asymmetric( insertion: .opacity.animation(.linear.delay(0.33)), removal: .identity ) ) } } .padding() .background( RoundedRectangle(cornerRadius: 12, style: .continuous) .fill(.white) .shadow(radius: 5) ) .containerValue(\.isComplete, isComplete) } } struct StepState: Equatable { var isComplete = false } extension EnvironmentValues { @Entry var isComplete: () -> Void = { print("Hello World!") } } extension ContainerValues { @Entry var isComplete: Bool = false }
Topic: UI Frameworks SubTopic: SwiftUI Tags:
4w