Skip to content

Commit

Permalink
chore(ios): add screen view event (#1721)
Browse files Browse the repository at this point in the history
  • Loading branch information
adwinross authored Jan 13, 2025
1 parent 9517e8e commit def6b8d
Show file tree
Hide file tree
Showing 15 changed files with 152 additions and 6 deletions.
2 changes: 1 addition & 1 deletion ios/DemoApp/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
let clientInfo = ClientInfo(apiKey: "msrsh_48153449fa6045685d605a6dcb684cbf42d5b1cdf780cd79bd58a4423ce8b23d_e6b33343",
let clientInfo = ClientInfo(apiKey: "msrsh_38514d61493cf70ce99a11abcb461e9e6d823e2068c7124a0902b745598f7ffb_65ea2c1c",
apiUrl: "http://localhost:8080")
let config = BaseMeasureConfig(enableLogging: true,
trackScreenshotOnCrash: false,
Expand Down
1 change: 1 addition & 0 deletions ios/DemoApp/Controller/ObjcDetailViewController.m
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ - (void)viewDidLoad {
[self setTitle:@"Objc View Controller"];

[self.view addSubview:tableView];
[[Measure shared] trackScreenView:@"ObjcViewController"];
}

// MARK: - Create Table Header with Buttons
Expand Down
5 changes: 5 additions & 0 deletions ios/DemoApp/Controller/ViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@ import MeasureSDK
Measure.shared.trackEvent(name: "custom_event", attributes: attributes, timestamp: nil)
}

override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
Measure.shared.trackScreenView("Home")
}

// MARK: - Table Header View with Buttons

func createTableHeaderView() -> UIView {
Expand Down
8 changes: 8 additions & 0 deletions ios/MeasureSDK.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,8 @@
52BCF2342CB5AC6E003102DF /* GestureCollectorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52BCF2332CB5AC6E003102DF /* GestureCollectorTests.swift */; };
52BCF2352CB65167003102DF /* MockLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 523287762C8619E0000EE268 /* MockLogger.swift */; };
52BCF2372CB651C8003102DF /* MockMeasureInitializer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52BCF2362CB651C8003102DF /* MockMeasureInitializer.swift */; };
52BFED302D2E476E00AC1A06 /* UserTriggeredEventCollector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52BFED2F2D2E476E00AC1A06 /* UserTriggeredEventCollector.swift */; };
52BFED322D2E486800AC1A06 /* ScreenViewData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52BFED312D2E486800AC1A06 /* ScreenViewData.swift */; };
52C03C442CB3FB29002E3C36 /* GestureTargetFinderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52C03C432CB3FB29002E3C36 /* GestureTargetFinderTests.swift */; };
52C844B92CB68A2D004BFE71 /* MeasureSDK.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 524CC5BA2C6A4B11001AB506 /* MeasureSDK.framework */; platformFilter = ios; };
52C844BA2CB68A2D004BFE71 /* MeasureSDK.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 524CC5BA2C6A4B11001AB506 /* MeasureSDK.framework */; platformFilter = ios; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
Expand Down Expand Up @@ -446,6 +448,8 @@
52BCF2282CB5AA8E003102DF /* MeasureUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MeasureUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
52BCF2332CB5AC6E003102DF /* GestureCollectorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GestureCollectorTests.swift; sourceTree = "<group>"; };
52BCF2362CB651C8003102DF /* MockMeasureInitializer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockMeasureInitializer.swift; sourceTree = "<group>"; };
52BFED2F2D2E476E00AC1A06 /* UserTriggeredEventCollector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserTriggeredEventCollector.swift; sourceTree = "<group>"; };
52BFED312D2E486800AC1A06 /* ScreenViewData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenViewData.swift; sourceTree = "<group>"; };
52C03C432CB3FB29002E3C36 /* GestureTargetFinderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GestureTargetFinderTests.swift; sourceTree = "<group>"; };
52CC63C02C9C608E00F7CA0A /* CrashDataPersistence.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CrashDataPersistence.swift; sourceTree = "<group>"; };
52CC63C22C9C609F00F7CA0A /* CrashDataWriter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CrashDataWriter.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -633,6 +637,8 @@
5202BE752C8B117900A3496E /* Event.swift */,
5202BE762C8B117900A3496E /* EventProcessor.swift */,
5202BE772C8B117900A3496E /* EventType.swift */,
52BFED312D2E486800AC1A06 /* ScreenViewData.swift */,
52BFED2F2D2E476E00AC1A06 /* UserTriggeredEventCollector.swift */,
);
path = Events;
sourceTree = "<group>";
Expand Down Expand Up @@ -1430,6 +1436,7 @@
526E30F62CE77BCB00F484B4 /* AppLaunchEvents.swift in Sources */,
52A853402C994D7900B2A39F /* Exception.swift in Sources */,
5222C9E82D14605000B198DA /* NetworkInterceptor.swift in Sources */,
52BFED322D2E486800AC1A06 /* ScreenViewData.swift in Sources */,
52159E272CC802A800486F54 /* EventSerializer.swift in Sources */,
522532CA2D295F3F001B5D7C /* AttributeValue.swift in Sources */,
528EAB892C804AA100CB1574 /* SessionManager.swift in Sources */,
Expand Down Expand Up @@ -1459,6 +1466,7 @@
52AE72032CABAE9000F2830A /* GestureEvents.swift in Sources */,
52A3C0782CDB732F00C8F047 /* CpuUsageData.swift in Sources */,
5202BE7D2C8B117900A3496E /* EventType.swift in Sources */,
52BFED302D2E476E00AC1A06 /* UserTriggeredEventCollector.swift in Sources */,
52A8533C2C994BCE00B2A39F /* StackFrame.swift in Sources */,
5225D0332D088B7100FD240D /* URLSessionTaskSwizzler.swift in Sources */,
52CD911E2C7B397C000189BA /* BaseConfigProvider.swift in Sources */,
Expand Down
2 changes: 1 addition & 1 deletion ios/MeasureSDK/Config/Config.swift
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ struct Config: InternalConfig, MeasureConfig {
self.enableLogging = enableLogging
self.trackScreenshotOnCrash = trackScreenshotOnCrash
self.sessionSamplingRate = sessionSamplingRate
self.eventsBatchingIntervalMs = 30000 // 30 seconds
self.eventsBatchingIntervalMs = 3000 // 30 seconds
self.maxEventsInBatch = 500
self.sessionEndLastEventThresholdMs = 20 * 60 * 1000 // 20 minitues
self.timeoutIntervalForRequest = 30 // 30 seconds
Expand Down
24 changes: 23 additions & 1 deletion ios/MeasureSDK/CoreData/Entities/EventEntity.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ struct EventEntity { // swiftlint:disable:this type_body_length
let warmLaunch: Data?
let hotLaunch: Data?
let networkChange: Data?
let screenView: Data?
let userTriggered: Bool
let attachmentSize: Number
let timestampInMillis: Number
Expand Down Expand Up @@ -211,6 +212,17 @@ struct EventEntity { // swiftlint:disable:this type_body_length
self.customEvent = nil
}

if let screenView = event.data as? ScreenViewData {
do {
let data = try JSONEncoder().encode(screenView)
self.screenView = data
} catch {
self.screenView = nil
}
} else {
self.screenView = nil
}

if let attributes = event.attributes {
do {
let data = try JSONEncoder().encode(attributes)
Expand Down Expand Up @@ -255,7 +267,8 @@ struct EventEntity { // swiftlint:disable:this type_body_length
hotLaunch: Data?,
http: Data?,
networkChange: Data?,
customEvent: Data?) {
customEvent: Data?,
screenView: Data?) {
self.id = id
self.sessionId = sessionId
self.timestamp = timestamp
Expand All @@ -282,6 +295,7 @@ struct EventEntity { // swiftlint:disable:this type_body_length
self.http = http
self.networkChange = networkChange
self.customEvent = customEvent
self.screenView = screenView
}

func getEvent<T: Codable>() -> Event<T> { // swiftlint:disable:this cyclomatic_complexity function_body_length
Expand Down Expand Up @@ -408,6 +422,14 @@ struct EventEntity { // swiftlint:disable:this type_body_length
decodedData = nil
}
}
case .screenView:
if let screenViewData = self.screenView {
do {
decodedData = try JSONDecoder().decode(T.self, from: screenViewData)
} catch {
decodedData = nil
}
}
case nil:
decodedData = nil
}
Expand Down
10 changes: 7 additions & 3 deletions ios/MeasureSDK/CoreData/EventStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ final class BaseEventStore: EventStore {
eventOb.http = event.http
eventOb.networkChange = event.networkChange
eventOb.customEvent = event.customEvent
eventOb.screenView = event.screenView

do {
try context.saveIfNeeded()
Expand Down Expand Up @@ -101,7 +102,8 @@ final class BaseEventStore: EventStore {
hotLaunch: eventOb.hotLaunch,
http: eventOb.http,
networkChange: eventOb.networkChange,
customEvent: eventOb.customEvent)
customEvent: eventOb.customEvent,
screenView: eventOb.screenView)
}
} catch {
guard let self = self else { return }
Expand Down Expand Up @@ -146,7 +148,8 @@ final class BaseEventStore: EventStore {
hotLaunch: eventOb.hotLaunch,
http: eventOb.http,
networkChange: eventOb.networkChange,
customEvent: eventOb.customEvent)
customEvent: eventOb.customEvent,
screenView: eventOb.screenView)
}
} catch {
guard let self = self else { return }
Expand Down Expand Up @@ -210,7 +213,8 @@ final class BaseEventStore: EventStore {
hotLaunch: eventOb.hotLaunch,
http: eventOb.http,
networkChange: eventOb.networkChange,
customEvent: eventOb.customEvent))
customEvent: eventOb.customEvent,
screenView: eventOb.screenView))
}
} catch {
guard let self = self else {
Expand Down
1 change: 1 addition & 0 deletions ios/MeasureSDK/Events/EventType.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,5 @@ enum EventType: String, Codable {
case http
case networkChange = "network_change"
case custom
case screenView = "screen_view"
}
12 changes: 12 additions & 0 deletions ios/MeasureSDK/Events/ScreenViewData.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
//
// ScreenViewData.swift
// MeasureSDK
//
// Created by Adwin Ross on 08/01/25.
//

import Foundation

struct ScreenViewData: Codable {
let name: String
}
46 changes: 46 additions & 0 deletions ios/MeasureSDK/Events/UserTriggeredEventCollector.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
//
// UserTriggeredEventCollector.swift
// MeasureSDK
//
// Created by Adwin Ross on 08/01/25.
//

import Foundation

protocol UserTriggeredEventCollector {
func trackScreenView(_ screenName: String)
func enable()
func disable()
}

final class BaseUserTriggeredEventCollector: UserTriggeredEventCollector {
private let eventProcessor: EventProcessor
private let timeProvider: TimeProvider
private var isEnabled = false

init(eventProcessor: EventProcessor, timeProvider: TimeProvider) {
self.eventProcessor = eventProcessor
self.timeProvider = timeProvider
}

func enable() {
isEnabled = true
}

func disable() {
isEnabled = false
}

func trackScreenView(_ screenName: String) {
guard isEnabled else { return }

let data = ScreenViewData(name: screenName)
eventProcessor.trackUserTriggered(data: data,
timestamp: timeProvider.now(),
type: .screenView,
attributes: nil,
sessionId: nil,
attachments: nil,
userDefinedAttributes: nil)
}
}
17 changes: 17 additions & 0 deletions ios/MeasureSDK/Exporter/EventSerializer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,16 @@ struct EventSerializer { // swiftlint:disable:this type_body_length
}
}
return nil
case .screenView:
if let screenViewData = event.screenView {
do {
let decodedData = try JSONDecoder().decode(ScreenViewData.self, from: screenViewData)
return serialiseScreenViewData(decodedData)
} catch {
return nil
}
}
return nil
case nil:
return nil
}
Expand Down Expand Up @@ -418,6 +428,13 @@ struct EventSerializer { // swiftlint:disable:this type_body_length
return result
}

private func serialiseScreenViewData(_ screenViewData: ScreenViewData) -> String {
var result = "{"
result += "\"name\":\"\(screenViewData.name)\""
result += "}"
return result
}

private func getSerialisedAttributes(for event: EventEntity) -> String? {
let decodedAttributes: Attributes?
if let attributeData = event.attributes {
Expand Down
20 changes: 20 additions & 0 deletions ios/MeasureSDK/Measure.swift
Original file line number Diff line number Diff line change
Expand Up @@ -159,4 +159,24 @@ import Foundation

customEventCollector.trackEvent(name: name, attributes: transformedAttributes, timestamp: timestamp?.int64Value)
}

/// Call when a screen is viewed by the user.
///
/// Measure SDK automatically collects screen view events for UIKit and SwiftUI navigation.
/// However, if your app uses a custom navigation system, you can use this method to track
/// screen view events and gain more context when debugging issues.
///
/// Example usage:
/// ```swift
/// Measure.shared.trackScreenView("Home")
/// ```
///
/// ```objc
/// [[Measure shared] trackScreenView:@"ObjcViewController"]
/// ```
@objc public func trackScreenView(_ screenName: String) {
guard let userTriggeredEventCollector = measureInternal?.userTriggeredEventCollector else { return }

userTriggeredEventCollector.trackScreenView(screenName)
}
}
5 changes: 5 additions & 0 deletions ios/MeasureSDK/MeasureInitializer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ protocol MeasureInitializer {
var httpEventCollector: HttpEventCollector { get }
var networkChangeCollector: NetworkChangeCollector { get }
var customEventCollector: CustomEventCollector { get }
var userTriggeredEventCollector: UserTriggeredEventCollector { get }
}

/// `BaseMeasureInitializer` is responsible for setting up the internal configuration
Expand Down Expand Up @@ -82,6 +83,7 @@ protocol MeasureInitializer {
/// - `memoryUsageCollector`: `MemoryUsageCollector` object which is responsible for detecting and saving memory usage data.
/// - `appLaunchCollector`: `AppLaunchCollector` object which is responsible for detecting and saving launch related events.
/// - `httpEventCollector`: `HttpEventCollector` object that collects HTTP request data.
/// - `userTriggeredEventCollector`: `UserTriggeredEventCollector` object which is responsible for tracking user triggered events.
/// - `gestureTargetFinder`: `GestureTargetFinder` object that determines which view is handling the gesture.
/// - `cpuUsageCalculator`: `CpuUsageCalculator` object that generates CPU usage data.
/// - `memoryUsageCalculator`: `MemoryUsageCalculator` object that generates memory usage data.
Expand Down Expand Up @@ -136,6 +138,7 @@ final class BaseMeasureInitializer: MeasureInitializer {
var httpEventCollector: HttpEventCollector
let networkChangeCollector: NetworkChangeCollector
let customEventCollector: CustomEventCollector
let userTriggeredEventCollector: UserTriggeredEventCollector

init(config: MeasureConfig, // swiftlint:disable:this function_body_length
client: Client) {
Expand Down Expand Up @@ -251,6 +254,8 @@ final class BaseMeasureInitializer: MeasureInitializer {
eventProcessor: eventProcessor,
timeProvider: timeProvider,
configProvider: configProvider)
self.userTriggeredEventCollector = BaseUserTriggeredEventCollector(eventProcessor: eventProcessor,
timeProvider: timeProvider)
self.client = client
self.httpEventCollector = BaseHttpEventCollector(logger: logger,
eventProcessor: eventProcessor,
Expand Down
4 changes: 4 additions & 0 deletions ios/MeasureSDK/MeasureInternal.swift
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,9 @@ final class MeasureInternal {
var customEventCollector: CustomEventCollector {
return measureInitializer.customEventCollector
}
var userTriggeredEventCollector: UserTriggeredEventCollector {
return measureInitializer.userTriggeredEventCollector
}
private let lifecycleObserver: LifecycleObserver

init(_ measureInitializer: MeasureInitializer) {
Expand All @@ -122,6 +125,7 @@ final class MeasureInternal {
self.sessionManager.start()
self.customEventCollector.enable()
self.appLaunchCollector.enable()
self.userTriggeredEventCollector.enable()
self.lifecycleObserver.applicationDidEnterBackground = applicationDidEnterBackground
self.lifecycleObserver.applicationWillEnterForeground = applicationWillEnterForeground
self.lifecycleObserver.applicationWillTerminate = applicationWillTerminate
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
<attribute name="lifecycleViewController" optional="YES" attributeType="Binary"/>
<attribute name="memoryUsage" optional="YES" attributeType="Binary"/>
<attribute name="networkChange" optional="YES" attributeType="Binary"/>
<attribute name="screenView" optional="YES" attributeType="Binary"/>
<attribute name="sessionId" attributeType="String" defaultValueString=""/>
<attribute name="timestamp" attributeType="String"/>
<attribute name="timestampInMillis" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
Expand Down

0 comments on commit def6b8d

Please sign in to comment.