我有一些下面的代码,它创建了一个定制的导航堆栈,使全屏滑动可以导航到远离视图的地方.我想用这个定制的导航堆栈支持iOS 17,但一些更改修饰符只适用于iOS 17.
我如何修改fileprivate struct FullSwipeModifier: ViewModifier {
中的.onChange(of: isEnabled, initial: true) { oldValue, newValue in
,使其与iOS16一起工作,并具有相同的功能?我试着用onReceive
修改器代替,但我不能让它工作.
此外,当视图被划go 时,执行功能的最佳位置是哪里.我不希望函数在滑动过程中执行,而是在滑动结束时,当视图从导航堆栈中弹出时执行.我当然可以用onDisappear
,但我宁愿不为我的具体情况.
import SwiftUI
struct ContentView: View {
@State private var isEnabled: Bool = false
var body: some View {
FullSwipeNavigationStack {
NavigationLink("Leading Swipe View") {
Text("hello").enableFullSwipePop(isEnabled)
}
}
}
}
struct FullSwipeNavigationStack<Content: View>: View {
@ViewBuilder var content: Content
/// Full Swipe Custom Gesture
@State private var customGesture: UIPanGestureRecognizer = {
let gesture = UIPanGestureRecognizer()
gesture.name = UUID().uuidString
gesture.isEnabled = false
return gesture
}()
var body: some View {
NavigationStack {
content
.background {
AttachGestureView(gesture: $customGesture)
}
}
.environment(\.popGestureID, customGesture.name)
.onReceive(NotificationCenter.default.publisher(for: .init(customGesture.name ?? "")), perform: { info in
if let userInfo = info.userInfo, let status = userInfo["status"] as? Bool {
customGesture.isEnabled = status
}
})
}
}
extension View {
@ViewBuilder
func enableFullSwipePop(_ isEnabled: Bool) -> some View {
self
.modifier(FullSwipeModifier(isEnabled: isEnabled))
}
}
/// Custom Environment Key for Passing Gesture ID to it's subviews
fileprivate struct PopNotificationID: EnvironmentKey {
static var defaultValue: String?
}
fileprivate extension EnvironmentValues {
var popGestureID: String? {
get {
self[PopNotificationID.self]
}
set {
self[PopNotificationID.self] = newValue
}
}
}
/// Helper View Modifier
fileprivate struct FullSwipeModifier: ViewModifier {
var isEnabled: Bool
/// Gesture ID
@Environment(\.popGestureID) private var gestureID
func body(content: Content) -> some View {
content
.onChange(of: isEnabled, initial: true) { oldValue, newValue in
guard let gestureID = gestureID else { return }
NotificationCenter.default.post(name: .init(gestureID), object: nil, userInfo: [
"status": newValue
])
}
.onDisappear(perform: {
guard let gestureID = gestureID else { return }
NotificationCenter.default.post(name: .init(gestureID), object: nil, userInfo: [
"status": false
])
})
}
}
/// Helper Files
fileprivate struct AttachGestureView: UIViewRepresentable {
@Binding var gesture: UIPanGestureRecognizer
func makeUIView(context: Context) -> UIView {
return UIView()
}
func updateUIView(_ uiView: UIView, context: Context) {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.02) {
/// Finding Parent Controller
if let parentViewController = uiView.parentViewController {
if let navigationController = parentViewController.navigationController {
/// Checking if already the gesture has been added to the controller
if let _ = navigationController.view.gestureRecognizers?.first(where: { $0.name == gesture.name }) {
print("Already Attached")
} else {
navigationController.addFullSwipeGesture(gesture)
print("Attached")
}
}
}
}
}
}
fileprivate extension UINavigationController {
/// Adding Custom FullSwipe Gesture
/// Special thanks for this SO Answer
/// https://stackoverflow.com/questions/20714595/extend-default-interactivepopgesturerecognizer-beyond-screen-edge
func addFullSwipeGesture(_ gesture: UIPanGestureRecognizer) {
guard let gestureSelector = interactivePopGestureRecognizer?.value(forKey: "targets") else { return }
gesture.setValue(gestureSelector, forKey: "targets")
view.addGestureRecognizer(gesture)
}
}
fileprivate extension UIView {
var parentViewController: UIViewController? {
sequence(first: self) {
$0.next
}.first(where: { $0 is UIViewController}) as? UIViewController
}
}