Skip to content

Add Class name support in the ScreenViewedEvent event and ElementInteractionEvent #283

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
VitorFerraz opened this issue Apr 30, 2025 · 0 comments
Labels
enhancement New feature or request

Comments

@VitorFerraz
Copy link

Summary

Currently, both ScreenViewedEvent and ElementInteractionEvent include the screenName as one of their properties.
The value of screenName is determined by a logic implemented inside UIKitScreenViews:

    static func screenName(for viewController: UIViewController) -> String {
        if let title = viewController.title, !title.isEmpty {
            return title
        }
        let className = String(describing: type(of: viewController))
            .replacingOccurrences(of: "ViewController", with: "")
        if !className.isEmpty {
            return className
        }
        return "Unknown"
    }

In many cases, a view controller’s title can be dynamic and may not accurately represent the actual screen name.
Proposed solution:
In the UIKitScreenViews create a new static method for the class name

    static func screenName(for viewController: UIViewController) -> String {
        if let title = viewController.title, !title.isEmpty {
            return title
        }
        
        return Self.className(for: viewController)
    }
    
    static func className(for viewController: UIViewController) -> String {
        let className = String(describing: type(of: viewController))
            .replacingOccurrences(of: "ViewController", with: "")
        if !className.isEmpty {
            return className
        }
        return "Unknown"
    }

ScreenViewedEvent

public class ScreenViewedEvent: BaseEvent {
    override public var eventType: String {
        get {
            Constants.AMP_SCREEN_VIEWED_EVENT
        }
        set {
        }
    }

    public init(screenName: String, className: String? = nil) {
        super.init(eventType: Constants.AMP_SCREEN_VIEWED_EVENT, eventProperties: [
            Constants.AMP_APP_SCREEN_NAME_PROPERTY: screenName,
            Constants.AMP_APP_CLASS_NAME_PROPERTY: className
        ])
    }

    required public init(from decoder: Decoder) throws {
        try super.init(from: decoder)
    }
}

ElementInteractionEvent:

public class ElementInteractionEvent: BaseEvent {
    convenience init(
        screenName: String? = nil,
        className: String? = nil,
        accessibilityLabel: String? = nil,
        accessibilityIdentifier: String? = nil,
        action: String,
        targetViewClass: String,
        targetText: String? = nil,
        hierarchy: String,
        actionMethod: String? = nil,
        gestureRecognizer: String? = nil
    ) {
        self.init(eventType: Constants.AMP_ELEMENT_INTERACTED_EVENT, eventProperties: [
            Constants.AMP_APP_SCREEN_NAME_PROPERTY: screenName,
            Constants.AMP_APP_CLASS_NAME_PROPERTY: className,
            Constants.AMP_APP_TARGET_AXLABEL_PROPERTY: accessibilityLabel,
            Constants.AMP_APP_TARGET_AXIDENTIFIER_PROPERTY: accessibilityIdentifier,
            Constants.AMP_APP_ACTION_PROPERTY: action,
            Constants.AMP_APP_TARGET_VIEW_CLASS_PROPERTY: targetViewClass,
            Constants.AMP_APP_TARGET_TEXT_PROPERTY: targetText,
            Constants.AMP_APP_HIERARCHY_PROPERTY: hierarchy,
            Constants.AMP_APP_ACTION_METHOD_PROPERTY: actionMethod,
            Constants.AMP_APP_GESTURE_RECOGNIZER_PROPERTY: gestureRecognizer
        ])
    }
}

UIKitScreenViews file, @objc func amp_viewDidAppear(_ animated: Bool) ->

 @objc func amp_viewDidAppear(_ animated: Bool) {
        amp_viewDidAppear(animated)

        // Only record screen events for view controllers owned by the app
        let viewControllerBundlePath = Bundle(for: classForCoder).bundleURL.resolvingSymlinksInPath().path
        let mainBundlePath = Bundle.main.bundleURL.resolvingSymlinksInPath().path
        guard viewControllerBundlePath.hasPrefix(mainBundlePath) else {
            return
        }

        guard let rootViewController = viewIfLoaded?.window?.rootViewController else {
            return
        }

        guard let top = Self.amp_topViewController(rootViewController),
              top !== UIKitScreenViews.lastTopViewController else {
            return
        }

        UIKitScreenViews.lastTopViewController = top

        let screenName = UIKitScreenViews.screenName(for: top)
        let className = UIKitScreenViews.className(for: top)

        for amplitude in UIKitScreenViews.amplitudes.allObjects {
            amplitude.track(event: ScreenViewedEvent(screenName: screenName, className: className))
        }
    }

UIKitElementInteractions

extension UIView {
    private static let viewHierarchyDelimiter = ""

    var eventData: UIKitElementInteractions.EventData {
        return UIKitElementInteractions.EventData(
            screenName: owningViewController
                .flatMap(UIViewController.amp_topViewController)
                .flatMap(UIKitScreenViews.screenName),
            className: owningViewController
                .flatMap(UIViewController.amp_topViewController)
                .flatMap(UIKitScreenViews.className),
            accessibilityLabel: accessibilityLabel,
            accessibilityIdentifier: accessibilityIdentifier,
            targetViewClass: descriptiveTypeName,
            targetText: amp_title,
            hierarchy: sequence(first: self, next: \.superview)
                .map { $0.descriptiveTypeName }
                .joined(separator: UIView.viewHierarchyDelimiter))
    }
}

UIKitElementInteractions.EventData

struct EventData {
        enum Source {
            case actionMethod

            case gestureRecognizer
        }

        let screenName: String?
 
        let className: String?
 
        let accessibilityLabel: String?

        let accessibilityIdentifier: String?

        let targetViewClass: String

        let targetText: String?

        let hierarchy: String

        fileprivate func elementInteractionEvent(for action: String, from source: Source? = nil, withName sourceName: String? = nil) -> ElementInteractionEvent {
            return ElementInteractionEvent(
                screenName: screenName,
                className: className,
                accessibilityLabel: accessibilityLabel,
                accessibilityIdentifier: accessibilityIdentifier,
                action: action,
                targetViewClass: targetViewClass,
                targetText: targetText,
                hierarchy: hierarchy,
                actionMethod: source == .actionMethod ? sourceName : nil,
                gestureRecognizer: source == .gestureRecognizer ? sourceName : nil
            )
        }
    }

Motivations

In many cases, a view controller’s title can be dynamic and may not accurately represent the actual screen name.
For example, the title might display a device name (such as a camera), a status, or any other dynamic value.
In these scenarios, using the view controller’s class name can provide a more consistent and reliable identifier for dashboards, making it easier to track specific screens within the app.
Therefore, having the class name available in the event properties list can lead to more accurate and flexible analytics.

@VitorFerraz VitorFerraz added the enhancement New feature or request label Apr 30, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

1 participant