Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions ExampleApp/ExampleApp.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
/* End PBXBuildFile section */

/* Begin PBXFileReference section */
AA00000000000000000001 /* Secrets.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = ../TestAppShared/Secrets.xcconfig; sourceTree = "<group>"; };
E7904AC42E6A523D00A15337 /* ExampleApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ExampleApp.app; sourceTree = BUILT_PRODUCTS_DIR; };
/* End PBXFileReference section */

Expand Down Expand Up @@ -39,6 +40,7 @@
E7904ABB2E6A523D00A15337 = {
isa = PBXGroup;
children = (
AA00000000000000000001 /* Secrets.xcconfig */,
E7904AC62E6A523D00A15337 /* ExampleApp */,
E7E464C62F3A9DFF0080DEF9 /* Frameworks */,
E7904AC52E6A523D00A15337 /* Products */,
Expand Down Expand Up @@ -266,6 +268,7 @@
};
E7904AD02E6A523E00A15337 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = AA00000000000000000001 /* Secrets.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
Expand All @@ -274,6 +277,9 @@
DEVELOPMENT_TEAM = "";
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_backendUrl = "$(backendUrl)";
INFOPLIST_KEY_mobileKey = "$(mobileKey)";
INFOPLIST_KEY_otlpEndpoint = "$(otlpEndpoint)";
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
Expand All @@ -296,6 +302,7 @@
};
E7904AD12E6A523E00A15337 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = AA00000000000000000001 /* Secrets.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
Expand All @@ -304,6 +311,9 @@
DEVELOPMENT_TEAM = "";
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_backendUrl = "$(backendUrl)";
INFOPLIST_KEY_mobileKey = "$(mobileKey)";
INFOPLIST_KEY_otlpEndpoint = "$(otlpEndpoint)";
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,18 +50,6 @@
ReferencedContainer = "container:ExampleApp.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<EnvironmentVariables>
<EnvironmentVariable
key = "MOBILE_KEY"
value = "mob-48fd3788-eab7-4b72-b607-e41712049dbd"
isEnabled = "YES">
</EnvironmentVariable>
<EnvironmentVariable
key = "OPTL_ENDPOINT"
value = "http://localhost:4318"
isEnabled = "YES">
</EnvironmentVariable>
</EnvironmentVariables>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
Expand Down
12 changes: 10 additions & 2 deletions ExampleApp/ExampleApp/Client.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,23 @@ import LaunchDarklySessionReplay

struct Client {
let config = { () -> LDConfig in
guard let secrets = Bundle.main.infoDictionary,
let mobileKey = secrets["mobileKey"] as? String, !mobileKey.isEmpty else {
fatalError("Missing mobileKey in Info.plist. See TestAppShared/Secrets.xcconfig.example.")
}
let otlpEndpoint = secrets["otlpEndpoint"] as? String
let backendUrl = secrets["backendUrl"] as? String

var config = LDConfig(
mobileKey: Env.mobileKey,
mobileKey: mobileKey,
autoEnvAttributes: .enabled
)
config.plugins = [
Observability(
options: .init(
isEnabled: false,
otlpEndpoint: Env.otelHost,
otlpEndpoint: otlpEndpoint,
backendUrl: backendUrl,
sessionBackgroundTimeout: 3,
isDebug: true,
logsApiLevel: .info,
Expand Down
11 changes: 0 additions & 11 deletions ExampleApp/ExampleApp/Env.swift

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
65E170A423632ABD00F5FDA1 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 65E170A223632ABD00F5FDA1 /* LaunchScreen.storyboard */; };
E7B1A2C32EC7E01300B157F3 /* LaunchDarklyObservability in Frameworks */ = {isa = PBXBuildFile; productRef = E7B1A2C22EC7E01300B157F3 /* LaunchDarklyObservability */; };
E7B1A2C52EC7E01300B157F3 /* LaunchDarklySessionReplay in Frameworks */ = {isa = PBXBuildFile; productRef = E7B1A2C42EC7E01300B157F3 /* LaunchDarklySessionReplay */; };
E7B1A2C92ECCB68500B157F3 /* Env.swift in Sources */ = {isa = PBXBuildFile; fileRef = E7B1A2C82ECCB68500B157F3 /* Env.swift */; };
E7B1A2CA2ECCB68500B157F3 /* Client.swift in Sources */ = {isa = PBXBuildFile; fileRef = E7B1A2C72ECCB68500B157F3 /* Client.swift */; };
E7B1A2CD2ECD2DD700B157F3 /* LaunchDarklyObservability in Frameworks */ = {isa = PBXBuildFile; productRef = E7B1A2CC2ECD2DD700B157F3 /* LaunchDarklyObservability */; };
E7B1A2CF2ECD2DD700B157F3 /* LaunchDarklySessionReplay in Frameworks */ = {isa = PBXBuildFile; productRef = E7B1A2CE2ECD2DD700B157F3 /* LaunchDarklySessionReplay */; };
Expand All @@ -32,8 +31,8 @@
65E1709D23632ABD00F5FDA1 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
65E170A323632ABD00F5FDA1 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
65E170A523632ABD00F5FDA1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
AA00000000000000000001 /* Secrets.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = ../TestAppShared/Secrets.xcconfig; sourceTree = "<group>"; };
E7B1A2C72ECCB68500B157F3 /* Client.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Client.swift; sourceTree = "<group>"; };
E7B1A2C82ECCB68500B157F3 /* Env.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Env.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand All @@ -54,6 +53,7 @@
65E1708B23632ABC00F5FDA1 = {
isa = PBXGroup;
children = (
AA00000000000000000001 /* Secrets.xcconfig */,
65E1709623632ABC00F5FDA1 /* MultiwindowPad */,
65E1709523632ABC00F5FDA1 /* Products */,
);
Expand Down Expand Up @@ -87,7 +87,6 @@
isa = PBXGroup;
children = (
E7B1A2C72ECCB68500B157F3 /* Client.swift */,
E7B1A2C82ECCB68500B157F3 /* Env.swift */,
);
path = Config;
sourceTree = "<group>";
Expand Down Expand Up @@ -168,7 +167,6 @@
65E1709823632ABC00F5FDA1 /* AppDelegate.swift in Sources */,
6564E23E2368955400E9AF7C /* CatsOverviewViewController.swift in Sources */,
6564E2422368978300E9AF7C /* CatDetailViewController.swift in Sources */,
E7B1A2C92ECCB68500B157F3 /* Env.swift in Sources */,
E7B1A2CA2ECCB68500B157F3 /* Client.swift in Sources */,
6564E2402368975400E9AF7C /* CatSceneDelegate.swift in Sources */,
65E1709A23632ABC00F5FDA1 /* SceneDelegate.swift in Sources */,
Expand Down Expand Up @@ -305,6 +303,7 @@
};
65E170A923632ABD00F5FDA1 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = AA00000000000000000001 /* Secrets.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_IDENTITY = "Apple Development";
Expand All @@ -325,6 +324,7 @@
};
65E170AA23632ABD00F5FDA1 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = AA00000000000000000001 /* Secrets.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_IDENTITY = "Apple Development";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,18 +50,6 @@
ReferencedContainer = "container:MultiwindowPad.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<EnvironmentVariables>
<EnvironmentVariable
key = "MOBILE_KEY"
value = "mob-48fd3788-eab7-4b72-b607-e41712049dbd"
isEnabled = "YES">
</EnvironmentVariable>
<EnvironmentVariable
key = "OPTL_ENDPOINT"
value = "http://localhost:4318"
isEnabled = "YES">
</EnvironmentVariable>
</EnvironmentVariables>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
Expand Down
99 changes: 77 additions & 22 deletions MultiSceneExampleApp/MultiwindowPad/AppDelegate.swift
Original file line number Diff line number Diff line change
@@ -1,36 +1,91 @@
//
// AppDelegate.swift
// MultiwindowPad
//
// Created by Wals, Donny on 25/10/2019.
// Copyright © 2019 Wals, Donny. All rights reserved.
//

import UIKit
import LaunchDarklyObservability

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
let client = Client()

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
LDObserve.shared.start()
return true
}

// MARK: UISceneSession Lifecycle

func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
if let activity = options.userActivities.first, activity.activityType == "com.donnywals.viewCat" {
return UISceneConfiguration(name: "Cat Detail", sessionRole: connectingSceneSession.role)
}
return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
}

func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {}
}

// MARK: - Scene Launch Tracking (demo)

return true
}
enum SceneLaunchType: String {
case cold = "Cold Launch"
case warm = "Warm Launch"
case sceneCreation = "Scene Created"

// MARK: UISceneSession Lifecycle
init(_ classification: SceneLaunchClassification) {
switch classification {
case .cold: self = .cold
case .warm: self = .warm
case .sceneCreation: self = .sceneCreation
}
}

func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
if let activity = options.userActivities.first, activity.activityType == "com.donnywals.viewCat" {
return UISceneConfiguration(name: "Cat Detail", sessionRole: connectingSceneSession.role)
var color: UIColor {
switch self {
case .cold: return .systemBlue
case .warm: return .systemGreen
case .sceneCreation: return .systemOrange
}
}
}

return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
}
struct SceneLaunchEvent {
let sceneID: String
let type: SceneLaunchType
let durationMs: Double
let date: Date
}

func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {
// Called when the user discards a scene session.
// If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
// Use this method to release any resources that were specific to the discarded scenes, as they will not return.
}
extension Notification.Name {
static let sceneLaunchEventRecorded = Notification.Name("SceneLaunchEventRecorded")
}

/// Shared log that classifies scene lifecycle events into cold / warm / sceneCreation launches
/// and stores them for display.
final class SceneLaunchEventLog {
static let shared = SceneLaunchEventLog()
private init() {}

private(set) var events: [SceneLaunchEvent] = []
private var classifier = SceneLaunchClassifier()

/// Call from `sceneWillEnterForeground` and `sceneDidBecomeActive` to record one launch event.
/// - Parameters:
/// - sceneID: The persistent session identifier of the scene.
/// - foregroundUptime: `ProcessInfo.processInfo.systemUptime` captured in `sceneWillEnterForeground`.
/// - activateUptime: `ProcessInfo.processInfo.systemUptime` captured in `sceneDidBecomeActive`.
func record(sceneID: String, foregroundUptime: TimeInterval, activateUptime: TimeInterval) {
classifier.sceneWillEnterForeground(.init(sceneID: sceneID, systemUptime: foregroundUptime))
guard let launchInfo = classifier.sceneDidBecomeActive(.init(sceneID: sceneID, systemUptime: activateUptime)) else {
return
}
Comment thread
cursor[bot] marked this conversation as resolved.

let durationMs = max(launchInfo.end - launchInfo.start, 0) * 1000
let shortID = String(sceneID.prefix(8))
let event = SceneLaunchEvent(
sceneID: shortID,
type: SceneLaunchType(launchInfo.type),
durationMs: durationMs,
date: Date()
)
events.append(event)
NotificationCenter.default.post(name: .sceneLaunchEventRecorded, object: event)
}
}
84 changes: 47 additions & 37 deletions MultiSceneExampleApp/MultiwindowPad/CatSceneDelegate.swift
Original file line number Diff line number Diff line change
@@ -1,48 +1,58 @@
//
// CatSceneDelegate.swift
// MultiwindowPad
//
// Created by Wals, Donny on 29/10/2019.
// Copyright © 2019 Wals, Donny. All rights reserved.
//

import UIKit

class CatSceneDelegate: UIResponder, UISceneDelegate {
var window: UIWindow?

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
let detail: CatDetailViewController
if let activity = connectionOptions.userActivities.first ?? session.stateRestorationActivity,
let identifier = activity.targetContentIdentifier {
detail = CatDetailViewController(catName: identifier)
} else {
detail = CatDetailViewController(catName: "default")
var window: UIWindow?

private var foregroundUptime: TimeInterval?

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
let detail: CatDetailViewController
if let activity = connectionOptions.userActivities.first ?? session.stateRestorationActivity,
let identifier = activity.targetContentIdentifier {
detail = CatDetailViewController(catName: identifier)
} else {
detail = CatDetailViewController(catName: "default")
}

if let windowScene = scene as? UIWindowScene {
let window = UIWindow(windowScene: windowScene)
window.rootViewController = detail
window.backgroundColor = .white
self.window = window
window.makeKeyAndVisible()
}
}

if let windowScene = scene as? UIWindowScene {
let window = UIWindow(windowScene: windowScene)
window.rootViewController = detail
window.backgroundColor = .white
self.window = window
window.makeKeyAndVisible()
func sceneWillEnterForeground(_ scene: UIScene) {
foregroundUptime = ProcessInfo.processInfo.systemUptime
}
}

override func restoreUserActivityState(_ activity: NSUserActivity) {
super.restoreUserActivityState(activity)
print("WILL RESTORE")
}
func sceneDidBecomeActive(_ scene: UIScene) {
let activateUptime = ProcessInfo.processInfo.systemUptime
if let foregroundUptime {
SceneLaunchEventLog.shared.record(
sceneID: scene.session.persistentIdentifier,
foregroundUptime: foregroundUptime,
activateUptime: activateUptime
)
self.foregroundUptime = nil
}
LaunchStatsOverlayView.install(in: window)
}

func stateRestorationActivity(for scene: UIScene) -> NSUserActivity? {
return scene.userActivity
}
override func restoreUserActivityState(_ activity: NSUserActivity) {
super.restoreUserActivityState(activity)
}

func sceneDidDisconnect(_ scene: UIScene) {
scene.session.stateRestorationActivity = scene.userActivity
}
func stateRestorationActivity(for scene: UIScene) -> NSUserActivity? {
return scene.userActivity
}

func sceneWillResignActive(_ scene: UIScene) {
scene.session.stateRestorationActivity = scene.userActivity
}
func sceneDidDisconnect(_ scene: UIScene) {
scene.session.stateRestorationActivity = scene.userActivity
}

func sceneWillResignActive(_ scene: UIScene) {
scene.session.stateRestorationActivity = scene.userActivity
}
}
Loading
Loading