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
-
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