SwiftUI'de tokatlamak hareketi UIKit'teki davranışla nasıl geri verilir (interactivePopGestureRecognizer)


9

Etkileşimli pop jest tanıyıcı, kullanıcının ekranın yarısından fazlasını (veya bu satırların etrafındaki bir şeyi) kaydırdığında gezinme yığınında önceki görünüme geri dönmesine izin vermelidir. SwiftUI'de tokatlamak yeterli olmadığında jest iptal edilmiyor.

SwiftUI: https://imgur.com/xxVnhY7

UIKit: https://imgur.com/f6WBUne


Soru:

SwiftUI görünümlerini kullanırken UIKit davranışını elde etmek mümkün müdür?


Denemeler

Bir UINavigationController içine bir UIHostingController gömmeye çalıştı ama bu NavigationView ile aynı davranışı verir.

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

Yanıtlar:


4

Ben varsayılan geçersiz kılma NavigationViewve NavigationLinkistenen davranışı elde sona erdi . Bu kadar basit görünüyor ki varsayılan SwiftUI görünümlerinin yaptığı bir şeye bakmam gerekiyor mu?

NavigationView

SwiftUI içerik görünümünü bir ortamObject olarak veren UINavigationControllersüper basit bir sarın . Bu , daha sonra aynı gezinti denetleyicisinde (sunulan görünüm denetleyicileri, ortamObjects almazsa) tam olarak istediğimiz şey olduğu anlamına gelebilir.UIViewControllerRepresentableUINavigationControllerNavigationLink

Not: NavigationView ihtiyacı vardır .edgesIgnoringSafeArea(.top)ve bunu nasıl yapı kendisi ayarlamak için bilmiyorum. Nvc'niz en üstte kesilirse örneğe bakın.

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

Bir sonraki görünümü barındıran bir UIHostingController itmek için UINavigationController ortamlarına erişen özel bir NavigationLink oluşturun.

Not: SwiftUI.NavigationLink'in uygulanmadığını selectionve isActivehenüz ne yaptığını tam olarak anlamadığım için uyguladı. Bu konuda yardım etmek isterseniz lütfen yorum yapın / düzenleyin.

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

Bu SwiftUI üzerinde düzgün çalışma geri tokatlamak çözer ve tüm projem hemen bunlara geçti NavigationView ve NavigationLink isimlerini kullandığım için.

Misal

Örnekte ben de modal sunum gösterdim.

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

Düzenleme: Ben "Bu bir şey göz ardı gerekir o kadar basit görünüyor" ile başladı ve ben buldum düşünüyorum. Bu, EnvironmentObjects'i bir sonraki görünüme aktarmıyor gibi görünüyor. Varsayılan NavigationLink'in bunu nasıl yaptığını bilmiyorum, bu yüzden şimdilik el ile ihtiyacım olan bir sonraki görünüme nesneleri gönderiyorum.

NavigationLink(destination: Text("Detail").environmentObject(objectToSendOnToTheNextView)) {
    Text("Show detail")
}

Düzenleme 2:

Bu içindeki tüm görüşlere navigasyon kontrolörü ortaya NavigationViewyaparak @EnvironmentObject var nvc: UINavigationController. Bunu düzeltmenin yolu, navigasyonu bir dosyaözel sınıfı yönetmek için kullandığımız ortamObject'i yapmaktır. Bunu özde düzelttim: https://gist.github.com/Amzd/67bfd4b8e41ec3f179486e13e9892eeb


Bağımsız değişken türü 'UINavigationController' beklenen tür olan 'ObservableObject' ile uyumlu değil
stardust4891

@kejodion Ben stackoverflow yazıya eklemeyi unuttum ama özünde oldu:extension UINavigationController: ObservableObject {}
Casper Zandbergen

Yaşadığım bir geri kaydırma hatası düzeltildi, ancak maalesef istekleri ve varsayılan NavigationView'ın yaptığı gibi değişiklikleri almak için değişiklikleri kabul etmiyor gibi görünüyor.
stardust4891

@kejodion Ah bu çok kötü, bu çözümün environmentObjects ile ilgili sorunları olduğunu biliyorum. Hangi getirme isteğinin ne demek istediğinden emin değilim. Belki yeni bir soru açar.
Casper Zandbergen

Peki yönetilen nesne içeriğini kaydettiğimde otomatik olarak kullanıcı arayüzünde güncellenen birkaç getirme isteğim var. Nedense kodunuzu uyguladığımda çalışmıyorlar. GERÇEKTEN yaptıklarını diliyorum, çünkü bu günlerce düzeltmeye çalıştığım bir geri çekme sorunu düzeltildi.
stardust4891

1

Bunu UIKit'e inerek ve kendi UINavigationController'ınızı kullanarak yapabilirsiniz.

İlk önce bir SwipeNavigationControllerdosya oluşturun :

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

Bu, fonksiyonun eklenmesi ile buradaSwipeNavigationController sağlananla aynıdır .pushSwipeBackView()

Bu işlev, SwipeBackHostingControllertanımladığımız

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

Daha sonra uygulamayı SceneDelegatekullanmak için kurduk 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()
    }

Sonunda kullanın 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
Özel SwipeNavigationController aslında varsayılan UINavigationController davranışından bir şey değiştirmez. func navController()Vc kapmak ve sonra kendinizi aslında harika bir fikir vc itmek ve bana bu sorunu anlamaya yardımcı için! Daha SwiftUI dostu bir cevap vereceğim, ama yardımın için teşekkürler!
Casper Zandbergen
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.