Replies: 1 comment
-
Tried to get cute and pull a publisher off the binding to see if that would fire, but it did not: private extension Binding {
func withPublisher() -> (Binding<Value>, AnyPublisher<Value, Never>) {
let subject = CurrentValueSubject<Value, Never>(self.wrappedValue)
let binding = Binding<Value>(
get: {
subject.send(wrappedValue)
return wrappedValue
},
set: { value in
self.wrappedValue = value
}
)
return (binding, subject.eraseToAnyPublisher())
}
}
private struct UINavigationControllerKey: EnvironmentKey {
static let defaultValue: UINavigationController? = nil
}
extension EnvironmentValues {
var navigationController: UINavigationController? {
get { self[UINavigationControllerKey.self] }
set { self[UINavigationControllerKey.self] = newValue }
}
}
extension View {
func uiNavigationDestination<Item>(
item: Binding<Item?>,
@ViewBuilder destination: @escaping (Item) -> some View
) -> some View {
modifier(
UIKitNavigationDestinationModifier(item: item, destination: destination)
)
}
}
private var pushedControllers: [UUID: UIViewController] = [:]
private struct UIKitNavigationDestinationModifier<Destination, Item>: ViewModifier where Destination: View {
internal init(item: Binding<Item?>, destination: @escaping (Item) -> Destination) {
let (myBinding, publisher) = item.withPublisher()
self._item = myBinding
self.itemPublisher = publisher
self.destination = destination
}
@Environment(\.navigationController) private var navigationController
@Binding var item: Item?
var itemPublisher: AnyPublisher<Item?, Never>
let destination: (Item) -> Destination
private let id = UUID()
private var pushedViewController: UIViewController? {
pushedControllers[id]
}
func body(content: Content) -> some View {
content
.onReceive( itemPublisher) { newItem in
guard let navigationController else {
fatalError("""
You must use .environment() to attach a UINavigationController to the root SwiftUI view pushed \
into a UIKit navigation flow.
""")
}
if let newItem, pushedControllers[id] == nil {
let controller = OnDisappearHostingViewController(content: self.destination(newItem)) {
pushedControllers[id] = nil
item = nil
}
pushedControllers[id] = controller
navigationController.pushViewController(controller, animated: !navigationController.viewControllers.isEmpty)
} else if newItem == nil, pushedControllers[id] != nil {
pushedViewController?.dismiss(animated: true)
pushedControllers[id] = nil
}
}
}
}
private final class OnDisappearHostingViewController<Content: View>: SwiftUIViewController<Content> {
private let onDisappear: () -> Void
init(content: Content, onDisappear: @escaping () -> Void) {
self.onDisappear = onDisappear
super.init(content: content)
}
override func willMove(toParent parent: UIViewController?) {
super.willMove(toParent: parent)
guard parent == nil else { return }
onDisappear()
}
@available(*, unavailable)
required init?(coder _: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
} |
Beta Was this translation helpful? Give feedback.
0 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
-
On the project we're building we're stuck with UIKit based navigation for the moment, but since the
navigationDestination
API doesn't play nice with a UINavigationController, we're still using deprecatedNavigationLink
s. So I'm wanting to build an operator that mimics thenavigationDestination
behavior, but uses UIKit under the hood. @jshier had the same need, and gave me a good head start with most of the code below. The problem is thatonChange
seems to only be fired when the body re-renders.I could get this API working by making the API require a Store instead of a Binding, and use the state keypath to pull off a publisher that I observe. I'm going to play with that, but ideally it would be the same exact API as
navigationDestination
.Beta Was this translation helpful? Give feedback.
All reactions