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