Sources/Confidence/ConfidenceScreenTracker.swift (87 lines of code) (raw):

#if os(iOS) || os(tvOS) || os(visionOS) || targetEnvironment(macCatalyst) import Foundation import UIKit import Combine public class ConfidenceScreenTracker: ConfidenceEventProducer { private var events = BufferedPassthrough<Event>() static let notificationName = Notification.Name(rawValue: "ConfidenceScreenTracker") static let screenName = "screen_name" static let messageKey = "message_json" static let controllerKey = "controller" public init() { swizzle( forClass: UIViewController.self, original: #selector(UIViewController.viewDidAppear(_:)), new: #selector(UIViewController.confidence__viewDidAppear) ) swizzle( forClass: UIViewController.self, original: #selector(UIViewController.viewDidDisappear(_:)), new: #selector(UIViewController.confidence__viewDidDisappear) ) NotificationCenter.default.addObserver( forName: Self.notificationName, object: nil, queue: OperationQueue.main) { [weak self] notification in let name = notification.userInfo?[Self.screenName] as? String let messageJson = (notification.userInfo?[Self.messageKey] as? String)?.data(using: .utf8) var data: ConfidenceStruct = [:] if let messageData = messageJson { let decoder = JSONDecoder() do { data = try decoder.decode(ConfidenceStruct.self, from: messageData) } catch { } } guard let self = self else { return } if let name = name { self.events.send(Event(name: name, data: data)) } } } public func produceEvents() -> AnyPublisher<Event, Never> { events.publisher() } private 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) } } public protocol TrackableComponent { func trackName() -> String } public protocol TrackableComponentWithMessage: TrackableComponent { func trackMessage() -> ConfidenceStruct } extension UIViewController { private func sendNotification(event: String) { var className = String(describing: type(of: self)) .replacingOccurrences(of: "ViewController", with: "") var message: [String: String] = [ConfidenceScreenTracker.screenName: className] if let trackable = self as? TrackableComponent { className = trackable.trackName() if let trackableWithMessage = self as? TrackableComponentWithMessage { let encoder = JSONEncoder() do { let data = try encoder.encode(trackableWithMessage.trackMessage()) let messageString = String(data: data, encoding: .utf8) ?? "internal_error" message.updateValue(messageString, forKey: ConfidenceScreenTracker.messageKey) } catch { } } } NotificationCenter.default.post( name: ConfidenceScreenTracker.notificationName, object: self, userInfo: message ) } @objc internal func confidence__viewDidAppear(animated: Bool) { } @objc internal func confidence__viewDidDisappear(animated: Bool) { } } #endif