From 1233cf396e7d9f5d7ca5cf7fbd860ec6da8171e9 Mon Sep 17 00:00:00 2001
From: Manoel Aranda Neto <marandaneto@gmail.com>
Date: Mon, 23 Oct 2023 13:53:58 +0200
Subject: [PATCH] add captureScreenViews

---
 PostHog.xcodeproj/project.pbxproj |  4 ++
 PostHog/PostHogSDK.swift          |  3 +-
 PostHog/UIViewController.swift    | 86 +++++++++++++++++++++++++++++++
 3 files changed, 92 insertions(+), 1 deletion(-)
 create mode 100644 PostHog/UIViewController.swift

diff --git a/PostHog.xcodeproj/project.pbxproj b/PostHog.xcodeproj/project.pbxproj
index 731c6c51f..ff09f0072 100644
--- a/PostHog.xcodeproj/project.pbxproj
+++ b/PostHog.xcodeproj/project.pbxproj
@@ -56,6 +56,7 @@
 		69261D232AD9784200232EC7 /* PostHogVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69261D222AD9784200232EC7 /* PostHogVersion.swift */; };
 		69261D252AD9787A00232EC7 /* PostHogExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69261D242AD9787A00232EC7 /* PostHogExtensions.swift */; };
 		6926DA8E2ADD2876005760D2 /* PostHogContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6926DA8D2ADD2876005760D2 /* PostHogContext.swift */; };
+		69779BEC2AE68E6900D7A48E /* UIViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69779BEB2AE68E6900D7A48E /* UIViewController.swift */; };
 /* End PBXBuildFile section */
 
 /* Begin PBXContainerItemProxy section */
@@ -144,6 +145,7 @@
 		69261D222AD9784200232EC7 /* PostHogVersion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostHogVersion.swift; sourceTree = "<group>"; };
 		69261D242AD9787A00232EC7 /* PostHogExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostHogExtensions.swift; sourceTree = "<group>"; };
 		6926DA8D2ADD2876005760D2 /* PostHogContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostHogContext.swift; sourceTree = "<group>"; };
+		69779BEB2AE68E6900D7A48E /* UIViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIViewController.swift; sourceTree = "<group>"; };
 /* End PBXFileReference section */
 
 /* Begin PBXFrameworksBuildPhase section */
@@ -278,6 +280,7 @@
 				69261D222AD9784200232EC7 /* PostHogVersion.swift */,
 				69261D242AD9787A00232EC7 /* PostHogExtensions.swift */,
 				6926DA8D2ADD2876005760D2 /* PostHogContext.swift */,
+				69779BEB2AE68E6900D7A48E /* UIViewController.swift */,
 			);
 			path = PostHog;
 			sourceTree = "<group>";
@@ -481,6 +484,7 @@
 				3AE3FB4E2993D1D600AFFC18 /* PostHogSessionManager.swift in Sources */,
 				3AE3FB49299391DF00AFFC18 /* PostHogStorage.swift in Sources */,
 				69261D232AD9784200232EC7 /* PostHogVersion.swift in Sources */,
+				69779BEC2AE68E6900D7A48E /* UIViewController.swift in Sources */,
 				3A0F108929C9BD76002C0084 /* Errors.swift in Sources */,
 				3AE3FB37299162EA00AFFC18 /* PostHogApi.swift in Sources */,
 				6926DA8E2ADD2876005760D2 /* PostHogContext.swift in Sources */,
diff --git a/PostHog/PostHogSDK.swift b/PostHog/PostHogSDK.swift
index 1068c28ea..47046d92b 100644
--- a/PostHog/PostHogSDK.swift
+++ b/PostHog/PostHogSDK.swift
@@ -95,6 +95,7 @@ let maxRetryDelay = 30.0
             queue?.start()
 
             registerNotifications()
+            captureScreenViews()
 
             DispatchQueue.main.async {
                 NotificationCenter.default.post(name: PostHogSDK.didStartNotification, object: nil)
@@ -562,7 +563,7 @@ let maxRetryDelay = 30.0
 
     private func captureScreenViews() {
         if config.captureScreenViews {
-//            swizzle(selector: #selector(), with: <#T##Selector#>, inClass: <#T##AnyClass#>, usingClass: <#T##AnyClass#>)
+            UIViewController.swizzleScreenView()
         }
     }
 
diff --git a/PostHog/UIViewController.swift b/PostHog/UIViewController.swift
new file mode 100644
index 000000000..51c18480e
--- /dev/null
+++ b/PostHog/UIViewController.swift
@@ -0,0 +1,86 @@
+//
+//  UIViewController.swift
+//  PostHog
+//
+// Inspired by
+// https://raw.githubusercontent.com/segmentio/analytics-swift/e613e09aa1b97144126a923ec408374f914a6f2e/Examples/other_plugins/UIKitScreenTracking.swift
+//
+//  Created by Manoel Aranda Neto on 23.10.23.
+//
+
+import Foundation
+import UIKit
+
+extension UIViewController {
+    static func swizzle(forClass: AnyClass, original: Selector, new: Selector) {
+        guard let originalMethod = class_getInstanceMethod(forClass, original) else { return }
+        guard let swizzledMethod = class_getInstanceMethod(forClass, new) else { return }
+        method_exchangeImplementations(originalMethod, swizzledMethod)
+    }
+
+    static func swizzleScreenView() {
+        UIViewController.swizzle(forClass: UIViewController.self,
+                                 original: #selector(UIViewController.viewDidAppear(_:)),
+                                 new: #selector(UIViewController.viewDidApperOverride))
+    }
+
+    private func activeController() -> UIViewController? {
+        // if a view is being dismissed, this will return nil
+        if let root = viewIfLoaded?.window?.rootViewController {
+            return root
+        } else if #available(iOS 13.0, *) {
+            // preferred way to get active controller in ios 13+
+            for scene in UIApplication.shared.connectedScenes where scene.activationState == .foregroundActive {
+                let windowScene = scene as? UIWindowScene
+                let sceneDelegate = windowScene?.delegate as? UIWindowSceneDelegate
+                if let target = sceneDelegate, let window = target.window {
+                    return window?.rootViewController
+                }
+            }
+        } else {
+            // this was deprecated in ios 13.0
+            return UIApplication.shared.keyWindow?.rootViewController
+        }
+        return nil
+    }
+
+    private func captureScreenView() {
+        var rootController = viewIfLoaded?.window?.rootViewController
+        if rootController == nil {
+            rootController = activeController()
+        }
+        guard let top = findVisibleViewController(activeController()) else { return }
+
+        var name = String(describing: top.classForCoder).replacingOccurrences(of: "ViewController", with: "")
+
+        if name.count == 0 {
+            name = top.title ?? "Unknown"
+        }
+
+        if name != "Unknown" {
+            PostHogSDK.shared.capture(name)
+        }
+    }
+
+    @objc func viewDidApperOverride(animated: Bool) {
+        captureScreenView()
+        // it looks like we're calling ourselves, but we're actually
+        // calling the original implementation of viewDidAppear since it's been swizzled.
+        viewDidApperOverride(animated: animated)
+    }
+
+    private func findVisibleViewController(_ controller: UIViewController?) -> UIViewController? {
+        if let navigationController = controller as? UINavigationController {
+            return findVisibleViewController(navigationController.visibleViewController)
+        }
+        if let tabController = controller as? UITabBarController {
+            if let selected = tabController.selectedViewController {
+                return findVisibleViewController(selected)
+            }
+        }
+        if let presented = controller?.presentedViewController {
+            return findVisibleViewController(presented)
+        }
+        return controller
+    }
+}