Skip to content

Commit

Permalink
feat: get current window screen size (#247)
Browse files Browse the repository at this point in the history
* feat: get current window screen size

* fix: wrap in compiler directive

* fix: lint

* fix: build for tvOS and watchOS

* fix: filter foregrounded scenes by default

* feat: search for UIWindowSceneDelegate when getting current window

* feat: get device size for watchOS

* fix: iterate over window scenes once

* fix: iOS15 fallback

* chore: update CHANGELOG
  • Loading branch information
ioannisj authored Nov 12, 2024
1 parent 8d74acb commit 9966d10
Show file tree
Hide file tree
Showing 8 changed files with 80 additions and 58 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
## Next

- fix: accessing UI APIs off main thread to get screen size ([#247](https://github.com/PostHog/posthog-ios/pull/247))

## 3.15.0 - 2024-11-11

- add autocapture support for UIKit ([#224](https://github.com/PostHog/posthog-ios/pull/224))
Expand Down
4 changes: 4 additions & 0 deletions PostHog.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@
69F518382BB2BA0100F52C14 /* PostHogSwizzler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69F518372BB2BA0100F52C14 /* PostHogSwizzler.swift */; };
69F5183A2BB2BA8300F52C14 /* UIApplicationTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69F518392BB2BA8300F52C14 /* UIApplicationTracker.swift */; };
DA26419C2CC0499300CB427B /* PostHogAutocaptureEventTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA26419A2CC0499300CB427B /* PostHogAutocaptureEventTracker.swift */; };
DA5AA7192CE245D2004EFB99 /* UIApplication+.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA5AA7132CE245CD004EFB99 /* UIApplication+.swift */; };
DA5B85882CD21CBB00686389 /* AutocaptureEventProcessing.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA5B85872CD21CBB00686389 /* AutocaptureEventProcessing.swift */; };
DA979D7B2CD370B700F56BAE /* PostHogAutocaptureEventTrackerSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA979D7A2CD370B700F56BAE /* PostHogAutocaptureEventTrackerSpec.swift */; };
DAC699D62CC790D9000D1D6B /* PostHogAutocaptureIntegration.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAC699D52CC790D9000D1D6B /* PostHogAutocaptureIntegration.swift */; };
Expand Down Expand Up @@ -390,6 +391,7 @@
69F518372BB2BA0100F52C14 /* PostHogSwizzler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostHogSwizzler.swift; sourceTree = "<group>"; };
69F518392BB2BA8300F52C14 /* UIApplicationTracker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIApplicationTracker.swift; sourceTree = "<group>"; };
DA26419A2CC0499300CB427B /* PostHogAutocaptureEventTracker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostHogAutocaptureEventTracker.swift; sourceTree = "<group>"; };
DA5AA7132CE245CD004EFB99 /* UIApplication+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIApplication+.swift"; sourceTree = "<group>"; };
DA5B85872CD21CBB00686389 /* AutocaptureEventProcessing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutocaptureEventProcessing.swift; sourceTree = "<group>"; };
DA8D37242CBEAC02005EBD27 /* PostHogExampleAutocapture.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = PostHogExampleAutocapture.xcodeproj; path = PostHogExampleAutocapture/PostHogExampleAutocapture.xcodeproj; sourceTree = "<group>"; };
DA979D7A2CD370B700F56BAE /* PostHogAutocaptureEventTrackerSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PostHogAutocaptureEventTrackerSpec.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -506,6 +508,7 @@
3AA4C09B2988315D006C4731 /* Utils */ = {
isa = PBXGroup;
children = (
DA5AA7132CE245CD004EFB99 /* UIApplication+.swift */,
3AE3FB422992985A00AFFC18 /* Reachability.swift */,
3AE3FB462992AB0000AFFC18 /* Hedgelog.swift */,
3A0F108429C9ABB6002C0084 /* ReadWriteLock.swift */,
Expand Down Expand Up @@ -1176,6 +1179,7 @@
69F23A7A2BB309F3001194F6 /* MethodSwizzler.swift in Sources */,
69261D1B2AD9678C00232EC7 /* PostHogEvent.swift in Sources */,
69EE82BC2BA9C53000EB9542 /* PostHogSessionReplayConfig.swift in Sources */,
DA5AA7192CE245D2004EFB99 /* UIApplication+.swift in Sources */,
69EE82CE2BAAC76000EB9542 /* ViewTreeSnapshotStatus.swift in Sources */,
69ED1AD42C90A0F100FE7A91 /* URLSessionExtension.swift in Sources */,
69ED1A9F2C8F451B00FE7A91 /* PostHogPersonProfiles.swift in Sources */,
Expand Down
11 changes: 1 addition & 10 deletions PostHog/Autocapture/PostHogAutocaptureEventTracker.swift
Original file line number Diff line number Diff line change
Expand Up @@ -342,17 +342,8 @@
}
}

extension UIApplication {
static var ph_currentWindow: UIWindow? {
Array(UIApplication.shared.connectedScenes)
.compactMap { $0 as? UIWindowScene }
.flatMap(\.windows)
.first { $0.windowLevel != .statusBar }
}
}

extension UIViewController {
class func ph_topViewController(base: UIViewController? = UIApplication.ph_currentWindow?.rootViewController) -> UIViewController? {
class func ph_topViewController(base: UIViewController? = UIApplication.getCurrentWindow()?.rootViewController) -> UIViewController? {
if let nav = base as? UINavigationController {
return ph_topViewController(base: nav.visibleViewController)

Expand Down
36 changes: 24 additions & 12 deletions PostHog/PostHogContext.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,33 @@ import Foundation
import UIKit
#elseif os(macOS)
import AppKit
#elseif os(watchOS)
import WatchKit
#endif

class PostHogContext {
#if !os(watchOS)
private let reachability: Reachability?
#endif

private var screenSize: CGSize? {
let getWindowSize: () -> CGSize? = {
#if os(iOS) || os(tvOS)
return UIApplication.getCurrentWindow(filterForegrounded: false)?.bounds.size
#elseif os(macOS)
return NSScreen.main?.visibleFrame.size
#elseif os(watchOS)
return WKInterfaceDevice.current().screenBounds.size
#else
return nil
#endif
}

return Thread.isMainThread
? getWindowSize()
: DispatchQueue.main.sync { getWindowSize() }
}

private lazy var theStaticContext: [String: Any] = {
// Properties that do not change over the lifecycle of an application
var properties: [String: Any] = [:]
Expand Down Expand Up @@ -122,18 +142,10 @@ class PostHogContext {
func dynamicContext() -> [String: Any] {
var properties: [String: Any] = [:]

#if os(iOS) || os(tvOS)
if let screen = UIApplication.shared.windows.first?.screen {
properties["$screen_width"] = Float(screen.bounds.width)
properties["$screen_height"] = Float(screen.bounds.height)
}
#elseif os(macOS)
if let mainScreen = NSScreen.main {
let screenFrame = mainScreen.visibleFrame
properties["$screen_width"] = Float(screenFrame.size.width)
properties["$screen_height"] = Float(screenFrame.size.height)
}
#endif
if let screenSize {
properties["$screen_width"] = Float(screenSize.width)
properties["$screen_height"] = Float(screenSize.height)
}

if Locale.current.languageCode != nil {
properties["$locale"] = Locale.current.languageCode
Expand Down
26 changes: 1 addition & 25 deletions PostHog/Replay/PostHogReplayIntegration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -509,30 +509,6 @@
return wireframe
}

static func getCurrentWindow() -> UIWindow? {
// TODO: support multi windows

// UIApplication.shared.windows is deprecated
for scene in UIApplication.shared.connectedScenes {
if scene is UIWindowScene,
scene.activationState == .foregroundActive,
let windowScene = scene as? UIWindowScene
{
if #available(iOS 15.0, *) {
if let keyWindow = windowScene.keyWindow {
return keyWindow
}
}

for window in windowScene.windows where window.isKeyWindow {
return window
}
}
}

return nil
}

@objc private func snapshot() {
if !PostHogSDK.shared.isSessionReplayActive() {
return
Expand All @@ -543,7 +519,7 @@
}
ViewLayoutTracker.clear()

guard let window = PostHogReplayIntegration.getCurrentWindow() else {
guard let window = UIApplication.getCurrentWindow() else {
return
}

Expand Down
2 changes: 1 addition & 1 deletion PostHog/Replay/UIApplicationTracker.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
guard event.type == .touches else {
return
}
guard let window = PostHogReplayIntegration.getCurrentWindow() else {
guard let window = UIApplication.getCurrentWindow() else {
return
}
guard let touches = event.touches(for: window) else {
Expand Down
12 changes: 2 additions & 10 deletions PostHog/UIViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,17 +29,9 @@
// if a view is being dismissed, this will return nil
if let root = viewIfLoaded?.window?.rootViewController {
return root
} else {
// 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
}
}
}
return nil
// TODO: handle container controllers (see ph_topViewController)
return UIApplication.getCurrentWindow()?.rootViewController
}

static func getViewControllerName(_ viewController: UIViewController) -> String? {
Expand Down
45 changes: 45 additions & 0 deletions PostHog/Utils/UIApplication+.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
//
// UIApplication+.swift
// PostHog
//
// Created by Yiannis Josephides on 11/11/2024.
//

#if os(iOS) || os(tvOS)
import UIKit

extension UIApplication {
static func getCurrentWindow(filterForegrounded: Bool = true) -> UIWindow? {
let windowScenes = UIApplication.shared
.connectedScenes
.compactMap { $0 as? UIWindowScene }
.filter {
!filterForegrounded || $0.activationState == .foregroundActive
}

for scene in windowScenes {
// attempt to retrieve directly from UIWindowScene
if #available(iOS 15.0, tvOS 15.0, *) {
if let keyWindow = scene.keyWindow {
return keyWindow
}
} else {
// check scene.windows.isKeyWindow
for window in scene.windows {
if window.isKeyWindow {
return window
}
}
}

// check scene.delegate.window property
let sceneDelegate = scene.delegate as? UIWindowSceneDelegate
if let target = sceneDelegate, let window = target.window {
return window
}
}

return nil
}
}
#endif

0 comments on commit 9966d10

Please sign in to comment.