问题描述
交互式弹出手势识别器应允许用户在滑过屏幕的一半以上(或这些行周围的某物)时返回导航堆栈中的上一个视图.在SwiftUI中,如果滑动距离不够远,手势不会被取消.
The interactive pop gesture recognizer should allow the user to go back the the previous view in navigation stack when they swipe further than half the screen (or something around those lines). In SwiftUI the gesture doesn't get canceled when the swipe wasn't far enough.
SwiftUI:: https://imgur.com/xxVnhY7
UIKit : https://imgur.com/f6WBUne
问题:
使用SwiftUI视图时能否获得UIKit行为?
Is it possible to get the UIKit behaviour while using SwiftUI views?
尝试
我试图将UIHostingController嵌入UINavigationController内,但其行为与NavigationView完全相同.
I tried to embed a UIHostingController inside a UINavigationController but that gives the exact same behaviour as NavigationView.
struct ContentView: View {
var body: some View {
UIKitNavigationView {
VStack {
NavigationLink(destination: Text("Detail")) {
Text("SwiftUI")
}
}.navigationBarTitle("SwiftUI", displayMode: .inline)
}.edgesIgnoringSafeArea(.top)
}
}
struct UIKitNavigationView<Content: View>: UIViewControllerRepresentable {
var content: () -> Content
init(@ViewBuilder content: @escaping () -> Content) {
self.content = content
}
func makeUIViewController(context: Context) -> UINavigationController {
let host = UIHostingController(rootView: content())
let nvc = UINavigationController(rootViewController: host)
return nvc
}
func updateUIViewController(_ uiViewController: UINavigationController, context: Context) {}
}
推荐答案
我最终重写了默认的NavigationView
和NavigationLink
以获得所需的行为.这看起来是如此简单,以至于我必须忽略默认SwiftUI视图所做的事情?
I ended up overriding the default NavigationView
and NavigationLink
to get the desired behaviour. This seems so simple that I must be overlooking something that the default SwiftUI views do?
我将UINavigationController
包装在超简单的UIViewControllerRepresentable
中,该UINavigationController
将UINavigationController
作为环境对象提供给SwiftUI内容视图.这意味着NavigationLink
以后可以抢到它,只要它在我们想要的同一个导航控制器中(显示的视图控制器不接收environmentObjects)即可.
I wrap a UINavigationController
in a super simple UIViewControllerRepresentable
that gives the UINavigationController
to the SwiftUI content view as an environmentObject. This means the NavigationLink
can later grab that as long as it's in the same navigation controller (presented view controllers don't receive the environmentObjects) which is exactly what we want.
注意::NavigationView需要.edgesIgnoringSafeArea(.top)
,我还不知道如何在结构本身中进行设置.如果您的nvc在顶部中断,请参见示例.
Note: The NavigationView needs .edgesIgnoringSafeArea(.top)
and I don't know how to set that in the struct itself yet. See example if your nvc cuts off at the top.
struct NavigationView<Content: View>: UIViewControllerRepresentable {
var content: () -> Content
init(@ViewBuilder content: @escaping () -> Content) {
self.content = content
}
func makeUIViewController(context: Context) -> UINavigationController {
let nvc = UINavigationController()
let host = UIHostingController(rootView: content().environmentObject(nvc))
nvc.viewControllers = [host]
return nvc
}
func updateUIViewController(_ uiViewController: UINavigationController, context: Context) {}
}
extension UINavigationController: ObservableObject {}
NavigationLink
我创建了一个自定义NavigationLink,用于访问环境UINavigationController来推送托管下一个视图的UIHostingController.
NavigationLink
I create a custom NavigationLink that accesses the environments UINavigationController to push a UIHostingController hosting the next view.
注意:我没有实现SwiftUI.NavigationLink的selection
和isActive
,因为我还不完全了解它们的作用.如果您想提供帮助,请评论/编辑.
Note: I didn't implement the selection
and isActive
that the SwiftUI.NavigationLink has because I don't fully understand what they do yet. If you want to help with that please comment/edit.
struct NavigationLink<Destination: View, Label:View>: View {
var destination: Destination
var label: () -> Label
public init(destination: Destination, @ViewBuilder label: @escaping () -> Label) {
self.destination = destination
self.label = label
}
/// If this crashes, make sure you wrapped the NavigationLink in a NavigationView
@EnvironmentObject var nvc: UINavigationController
var body: some View {
Button(action: {
let rootView = self.destination.environmentObject(self.nvc)
let hosted = UIHostingController(rootView: rootView)
self.nvc.pushViewController(hosted, animated: true)
}, label: label)
}
}
这解决了向后滑动无法在SwiftUI上正常工作的问题,并且因为我使用了NavigationView和NavigationLink这两个名称,所以我的整个项目都立即切换为这些.
This solves the back swipe not working correctly on SwiftUI and because I use the names NavigationView and NavigationLink my entire project switched to these immediately.
在示例中,我也显示了模态表示.
In the example I show modal presentation too.
struct ContentView: View {
@State var isPresented = false
var body: some View {
NavigationView {
VStack(alignment: .center, spacing: 30) {
NavigationLink(destination: Text("Detail"), label: {
Text("Show detail")
})
Button(action: {
self.isPresented.toggle()
}, label: {
Text("Show modal")
})
}
.navigationBarTitle("SwiftUI")
}
.edgesIgnoringSafeArea(.top)
.sheet(isPresented: $isPresented) {
Modal()
}
}
}
struct Modal: View {
@Environment(\.presentationMode) var presentationMode
var body: some View {
NavigationView {
VStack(alignment: .center, spacing: 30) {
NavigationLink(destination: Text("Detail"), label: {
Text("Show detail")
})
Button(action: {
self.presentationMode.wrappedValue.dismiss()
}, label: {
Text("Dismiss modal")
})
}
.navigationBarTitle("Modal")
}
}
}
我从这看起来太简单了,以至于我必须忽略某些东西"开始,我想我找到了它.这似乎没有将EnvironmentObjects转移到下一个视图.我不知道默认的NavigationLink是如何做到的,所以现在我手动将对象发送到需要它们的下一个视图.
I started off with "This seems so simple that I must be overlooking something" and I think I found it. This doesn't seem to transfer EnvironmentObjects to the next view. I don't know how the default NavigationLink does that so for now I manually send objects on to the next view where I need them.
NavigationLink(destination: Text("Detail").environmentObject(objectToSendOnToTheNextView)) {
Text("Show detail")
}
这通过执行@EnvironmentObject var nvc: UINavigationController
将导航控制器公开给NavigationView
内部的所有视图.解决此问题的方法是使用于管理导航的environmentObject成为fileprivate类.我在要点中对此进行了修复: https://gist.github.com/Amzd/67bfd4b8e41ec3f179486e13e9892eeb
This exposes the navigation controller to all views inside NavigationView
by doing @EnvironmentObject var nvc: UINavigationController
. The way to fix this is making the environmentObject we use to manage navigation a fileprivate class. I fixed this in the gist: https://gist.github.com/Amzd/67bfd4b8e41ec3f179486e13e9892eeb
这篇关于如何在SwiftUI中返回与UIKit(interactivePopGestureRecognizer)相同的行为的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!