交互式弹出手势识别器应允许用户在滑过屏幕的一半以上(或这些行周围的某物)时返回导航堆栈中的上一个视图。在SwiftUI中,滑动距离不够时手势不会被取消。
SwiftUI: https://imgur.com/xxVnhY7
UIKit: https://imgur.com/f6WBUne
问题:
使用SwiftUI视图时能否获得UIKit行为?
尝试
我试图将UIHostingController嵌入UINavigationController内,但其行为与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) {}
}
答案 0 :(得分:1)
我最终覆盖了默认的NavigationView
和NavigationLink
来获得所需的行为。这看起来是如此简单,以至于我必须忽略默认SwiftUI视图所做的事情?
我将UINavigationController
包装在一个超级简单的UIViewControllerRepresentable
中,该UINavigationController
作为环境对象,将NavigationLink
赋予SwiftUI内容视图。这意味着.edgesIgnoringSafeArea(.top)
以后可以抢到它,只要它在我们想要的同一个导航控制器中(显示的视图控制器不接收environmentObjects)即可。
注意:NavigationView需要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 {}
,但我还不知道如何在结构本身中进行设置。如果您的nvc在顶部中断,请参见示例。
selection
我创建了一个自定义NavigationLink,用于访问环境UINavigationController来推送托管下一个视图的UIHostingController。
注意:我没有实现SwiftUI.NavigationLink拥有的isActive
和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)
}
}
,因为我还不完全了解它们的作用。如果您想提供帮助,请评论/编辑。
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()
}
}
}
这解决了后向滑动无法在SwiftUI上正常工作的问题,并且因为我使用了NavigationView和NavigationLink这两个名称,所以我的整个项目都立即切换为这些。
在示例中,我也显示了模态表示。
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")
}
}
}
NavigationLink(destination: Text("Detail").environmentObject(objectToSendOnToTheNextView)) {
Text("Show detail")
}
编辑:我从“这看起来很简单,我必须忽略某些东西”开始,我想我找到了它。这似乎没有将EnvironmentObjects转移到下一个视图。我不知道默认的NavigationLink是如何做到的,所以现在我手动将对象发送到需要它们的下一个视图。
NavigationView
编辑2:
这通过执行@EnvironmentObject var nvc: UINavigationController
将导航控制器暴露给wsgi.py
内部的所有视图。解决此问题的方法是使用于管理导航的environmentObject成为fileprivate类。我已将其修正为要点:https://gist.github.com/Amzd/67bfd4b8e41ec3f179486e13e9892eeb
答案 1 :(得分:0)
您可以通过进入UIKit并使用自己的UINavigationController来实现。
首先创建一个SwipeNavigationController
文件:
import UIKit
import SwiftUI
final class SwipeNavigationController: UINavigationController {
// MARK: - Lifecycle
override init(rootViewController: UIViewController) {
super.init(rootViewController: rootViewController)
}
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
delegate = self
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
delegate = self
}
override func viewDidLoad() {
super.viewDidLoad()
// This needs to be in here, not in init
interactivePopGestureRecognizer?.delegate = self
}
deinit {
delegate = nil
interactivePopGestureRecognizer?.delegate = nil
}
// MARK: - Overrides
override func pushViewController(_ viewController: UIViewController, animated: Bool) {
duringPushAnimation = true
super.pushViewController(viewController, animated: animated)
}
var duringPushAnimation = false
// MARK: - Custom Functions
func pushSwipeBackView<Content>(_ content: Content) where Content: View {
let hostingController = SwipeBackHostingController(rootView: content)
self.delegate = hostingController
self.pushViewController(hostingController, animated: true)
}
}
// MARK: - UINavigationControllerDelegate
extension SwipeNavigationController: UINavigationControllerDelegate {
func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool) {
guard let swipeNavigationController = navigationController as? SwipeNavigationController else { return }
swipeNavigationController.duringPushAnimation = false
}
}
// MARK: - UIGestureRecognizerDelegate
extension SwipeNavigationController: UIGestureRecognizerDelegate {
func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
guard gestureRecognizer == interactivePopGestureRecognizer else {
return true // default value
}
// Disable pop gesture in two situations:
// 1) when the pop animation is in progress
// 2) when user swipes quickly a couple of times and animations don't have time to be performed
let result = viewControllers.count > 1 && duringPushAnimation == false
return result
}
}
here与SwipeNavigationController
相同,并附加了pushSwipeBackView()
函数。
此功能需要一个SwipeBackHostingController
,我们将其定义为
import SwiftUI
class SwipeBackHostingController<Content: View>: UIHostingController<Content>, UINavigationControllerDelegate {
func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool) {
guard let swipeNavigationController = navigationController as? SwipeNavigationController else { return }
swipeNavigationController.duringPushAnimation = false
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
guard let swipeNavigationController = navigationController as? SwipeNavigationController else { return }
swipeNavigationController.delegate = nil
}
}
然后,我们将应用的SceneDelegate
设置为使用SwipeNavigationController
:
if let windowScene = scene as? UIWindowScene {
let window = UIWindow(windowScene: windowScene)
let hostingController = UIHostingController(rootView: ContentView())
window.rootViewController = SwipeNavigationController(rootViewController: hostingController)
self.window = window
window.makeKeyAndVisible()
}
最后在您的ContentView
中使用它:
struct ContentView: View {
func navController() -> SwipeNavigationController {
return UIApplication.shared.windows[0].rootViewController! as! SwipeNavigationController
}
var body: some View {
VStack {
Text("SwiftUI")
.onTapGesture {
self.navController().pushSwipeBackView(Text("Detail"))
}
}.onAppear {
self.navController().navigationBar.topItem?.title = "Swift UI"
}.edgesIgnoringSafeArea(.top)
}
}