ios – Swift UI execute function programmatically when view disappears


Hello I have some code that creates a custom navigation stack that enables a left-right swipe to pop a view from the stack. It basically allows the left edge swipe but anywhere on the screen instead of only the left edge. I want to execute a function when a view is popped off the navigation stack. I do not want this function to run half way through a swipe or before the user lets go. I know I can use onDisappear but this does not run fast enough for my specific case, there is always a delay between when the view actually leaves the view and when the onDisappear is called. Is there a way to manually call the function as soon as the view is popped off?

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

Latest articles

spot_imgspot_img

Related articles

Leave a reply

Please enter your comment!
Please enter your name here

spot_imgspot_img