Last active
June 26, 2024 07:38
-
-
Save IanKeen/31fa4b36be507e649a027fda5afaf17a to your computer and use it in GitHub Desktop.
SwiftUI: Conditional views from optional bindings with required constraints
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
struct RootView: View { | |
@State private var state: AppState = .init() | |
var body: some View { | |
// if more or less than `1` if the `If`s below succeed then an error view will be shown instead | |
Require(1) { | |
// `If` only shows it's content if the provided Binding<T?> can be unwrapped to Binding<T> | |
If(binding: $state.signedOut) { binding in | |
SignedOutView(state: binding) | |
} | |
If(binding: $state.signedInt) { binding in | |
SignedInView(state: binding) | |
} | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public struct Require<Content: View>: View { | |
@StateObject private var registrar: ViewRegistrar | |
@ViewBuilder private var content: () -> Content | |
public init(_ value: Int, @ViewBuilder content: @escaping () -> Content) { | |
self._registrar = .init(wrappedValue: ViewRegistrar(limit: value)) | |
self.content = content | |
} | |
public var body: some View { | |
ZStack { | |
registrar.errorView() | |
content() | |
.opacity(registrar.isValid() ? 1 : 0) | |
} | |
.environmentObject(registrar) | |
} | |
} | |
public struct If<Content: View, T>: View { | |
@EnvironmentObject private var registrar: ViewRegistrar | |
@State private var id: AnyHashable | |
@Binding private var binding: T? | |
@ViewBuilder private var content: (Binding<T>) -> Content | |
public init(id: AnyHashable = UUID(), binding: Binding<T?>, @ViewBuilder content: @escaping (Binding<T>) -> Content) { | |
self._id = .init(wrappedValue: id) | |
self._binding = binding | |
self.content = content | |
} | |
public var body: some View { | |
Binding(_binding).map { content($0).onAppear { registrar.active(id) } } | |
} | |
} | |
private class ViewRegistrar: ObservableObject { | |
@Published private var active: [AnyHashable] = [] | |
private let limit: Int | |
init(limit: Int) { | |
self.limit = limit | |
} | |
func active(_ value: AnyHashable) { | |
active.append(value) | |
} | |
func isValid() -> Bool { | |
return active.count == limit | |
} | |
func errorView() -> some View { | |
VStack { | |
Text("Invalid State!") | |
.font(.title) | |
Text("\(limit) view(s) are required, but \(active.count) are active") | |
if !active.isEmpty { | |
Text("Active Views:") | |
.font(.title3) | |
.padding(.top, 16) | |
VStack(alignment: .leading) { | |
ForEach(active, id: \.self) { item in | |
Text(verbatim: "- \(item)") | |
} | |
} | |
} | |
} | |
.opacity(isValid() ? 0 : 1) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment