Search code examples
iosswiftuiviewcontrollerfirebase-analytics

How to track changes in currently visible ViewController as user navigating the screens in iOS?


I'm trying to replicate Firebase Analytics behaviour, which automatically fire screen events whenever ViewController screen get's changed with another. Though I'm able to find currently visible ViewController using :

UIApplication.shared.windows.first?.rootViewController?.presentedViewController

But I need some way to get notified for any change in rootViewController. I tried to observe this rootViewController using KVO, but I don't get any callback. I found that KVO only works on NSObject with dynamic properties. Is there any way I could receive callback for change in ViewController? Since this will be a library project, I couldn't make changes in main code to support the feature.


Solution

  • Following solution worked for me:-

    import Foundation
    import UIKit
    
    public extension UIViewController {
    
    @objc dynamic func _tracked_viewWillAppear(_ animated: Bool) {
        UserActivityTracker.startTracking(viewController: self)
    }
    
    static func swizzle() {
            //Make sure This isn't a subclass of UIViewController,
            //So that It applies to all UIViewController childs
            if self != UIViewController.self {
                return
            }
            let _: () = {
                let originalSelector =
                    #selector(UIViewController.viewWillAppear(_:))
                let swizzledSelector =
                    #selector(UIViewController._tracked_viewWillAppear(_:))
                let originalMethod =
                    class_getInstanceMethod(self, originalSelector)
                let swizzledMethod =
                    class_getInstanceMethod(self, swizzledSelector)
                method_exchangeImplementations(originalMethod!, swizzledMethod!);
            }()
        }
     } 
    

    In above code _tracked_viewWillAppear() is my custom function which I want to call my implementation before actual implementation called. Then in AppDeligate class, call UIViewController.swizzle() method, as follows:-

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // Override point for customization after application launch.
        UIViewController.swizzle()
        return true
    }