स्विफ्टयूआई में वापस स्वाइप जेस्चर कैसे दें


9

इंटरएक्टिव पॉप जेस्चर पहचानकर्ता को उपयोगकर्ता को नेविगेशन स्टैक में पिछले दृश्य को वापस जाने की अनुमति देनी चाहिए जब वे आधे से अधिक स्क्रीन (या उन लाइनों के आसपास कुछ) को स्वाइप करते हैं। SwiftUI में जेस्चर तब तक रद्द नहीं होता जब स्वाइप पर्याप्त दूर नहीं था।

SwiftUI: https://imgur.com/xxVnhY7

UIKit: https://imgur.com/f6WBUne


सवाल:

क्या SwiftUI विचारों का उपयोग करते समय UIKit व्यवहार प्राप्त करना संभव है?


प्रयास

मैंने एक UINavigationController के अंदर एक UIHostingController एम्बेड करने की कोशिश की, लेकिन यह बिल्कुल नेविगेशनव्यू के समान व्यवहार देता है।

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) {}
}

जवाबों:


4

मैं डिफ़ॉल्ट अधिभावी समाप्त हो गया NavigationViewऔरNavigationLink वांछित व्यवहार प्राप्त करने के लिए । यह इतना सरल लगता है कि मुझे कुछ ऐसा करना चाहिए जो कि डिफ़ॉल्ट स्विफ्टयूआई के विचारों को दर्शाता हो?

NavigationView

मैं एक लपेटो UINavigationControllerएक सुपर सरल में UIViewControllerRepresentableहै कि देता है UINavigationControllerएक environmentObject रूप SwiftUI सामग्री को देखने के लिए। इसका मतलब यह है कि NavigationLinkबाद में जब तक यह एक ही नेविगेशन नियंत्रक (प्रस्तुत दृश्य नियंत्रकों को प्राप्त नहीं करता है, तब तक पर्यावरण प्राप्त नहीं कर सकता) जो वास्तव में हम चाहते हैं।

नोट: नेवीगेशन व्यू की जरूरत है .edgesIgnoringSafeArea(.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

मैं एक कस्टम नेविगेशनलिंक बनाता हूं जो अगले दृश्य को होस्ट करने वाले UIHostingController को पुश करने के लिए वातावरण UINavigationController तक पहुंचता है।

नोट: मैं लागू नहीं किया था selectionऔर isActiveहै कि SwiftUI.NavigationLink है क्योंकि मैं पूरी तरह से समझ में नहीं आता कि वे क्या अभी तक है। यदि आप उस टिप्पणी के साथ मदद करना चाहते हैं तो कृपया / संपादित करें।

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()
        }
    }
}
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")
}

2 संपादित करें:

यह अंदर सभी दृश्यों पर नेविगेशन नियंत्रक को उजागर करता है NavigationViewऐसा करके @EnvironmentObject var nvc: UINavigationController। इसे ठीक करने का तरीका पर्यावरण बना रहा है। हम एक फाइलपेयर क्लास के नेविगेशन को प्रबंधित करने के लिए उपयोग करते हैं। मैंने इसे जिस्ट में तय किया: https://gist.github.com/Amzd/67bfd4b8e41ec3f179486e13e9892eeb


तर्क प्रकार 'UINavigationController' अपेक्षित प्रकार 'ObservableObject' के अनुरूप नहीं है
stardust4891

@jjodion मैं स्टैकओवरफ्लो पोस्ट में इसे जोड़ना भूल गया, लेकिन यह जीस्ट में था:extension UINavigationController: ObservableObject {}
Casper Zandbergen

इसने एक बैक स्वाइप बग तय किया था जो मैं अनुभव कर रहा था लेकिन दुर्भाग्य से यह अनुरोधों को लाने के लिए परिवर्तनों को स्वीकार नहीं करता है और जिस तरह से डिफ़ॉल्ट नेवीगेशन व्यू करता है।
स्टारडस्ट 4891

@kejodion आह यह बहुत बुरा है, मुझे पता है कि इस समाधान में पर्यावरण के साथ मुद्दे हैं। निश्चित नहीं है कि आपको क्या अनुरोध करना है। शायद एक नया प्रश्न खोल दें।
कैस्पर ज़ैंडबर्गेन

वैसे मेरे पास कई सारे अनुरोध हैं जो प्रबंधित ऑब्जेक्ट संदर्भ को सहेजते समय स्वचालित रूप से UI में अपडेट हो जाते हैं। किसी कारण से जब मैं आपके कोड को लागू करता हूं तो वे काम नहीं करते हैं। मैं वास्तव में चाहता हूं कि उन्होंने किया, क्योंकि यह एक बैक स्वाइप मुद्दा था जिसे मैं दिनों से ठीक करने की कोशिश कर रहा था।
स्टारडस्ट 4891

1

आप इसे 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
    }
}

यह फ़ंक्शन के अतिरिक्त के साथ यहां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)
    }
}

1
आपका कस्टम SwipeNavigationController वास्तव में डिफ़ॉल्ट UINavigationController व्यवहार से कुछ भी नहीं बदलता है। func navController()कुलपति हड़पने के लिए और उसके बाद कुलपति खुद वास्तव में एक बहुत अच्छा विचार है धक्का और मदद की मुझे इस समस्या को यह पता लगाने! मैं अधिक SwiftUI अनुकूल उत्तर दूंगा, लेकिन आपकी मदद के लिए धन्यवाद!
कैस्पर जैंडरबर्ग
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.