Skip to content

chore: adopt UIScene lifecycle - WPB-25097#4826

Open
samwyndham wants to merge 31 commits into
developfrom
chore/adopt-uiscene-lifecycle-WPB-25097
Open

chore: adopt UIScene lifecycle - WPB-25097#4826
samwyndham wants to merge 31 commits into
developfrom
chore/adopt-uiscene-lifecycle-WPB-25097

Conversation

@samwyndham

@samwyndham samwyndham commented Jun 8, 2026

Copy link
Copy Markdown
Contributor
TaskWPB-25097 [iOS] Adopt UISceneDelegate

Issue

This PR migrates the app for the UIScene lifecycle. This is necessary because apple will stope supporting the legacy lifecycle with iOS 27 SDK and the app will crash on launch:

Considering we have big plans for our apps architecture, this PR attempts to do a kind of minimum to solve the immediate problem without resorting to big hacks. It only supports a single app instance so we don't gain anything from these changes accept compatibility with the future SDK. To achieve this the following changes were necessary:

  • Introduced SceneDelegate that takes over some responsibilities from AppDelegate
  • Launch options are now bundled in UIScene.ConnectionOptions - an app can be launched with an array of URLs. In our case only the 1st URL is supported.
  • Deleted the ApplicationLaunchType. This abstraction is hard to make work with the UIScene lifecycle and it was only being used in two places:
    • CallQualityController where a simple alternative was found
    • SoundEventListener from testing this code never hit the appDelegate.launchType == .push path (the behavior doesn't work) so I just removed the related code.

Snapshot updates

There were 4 screens whose snapshots needed to be update. I looked into this and I don't believe there was any changes in the app - rather they relate to how snapshots are rendered in the library - specifically the view.safeAreaInsets that are applied to the view controllers being tested by the library.

I decided just to update the snapshots as the safe area insets applied in the snapshots are not related to those applied in the actual app.

Testing

General testing of the app.


Checklist

  • Title contains a reference JIRA issue number like [WPB-XXX].
  • Description is filled and free of optional paragraphs.
  • Adds/updates automated tests.

samwyndham added 29 commits June 2, 2026 17:55
Launch options are nil when app is configured for scenes
This is already being set in AppDelegate.application(_:didReceiveRemoteNotification:fetchCompletionHandler:) and apples docs state on the deprecation notice:

> "Continue using UIApplicationDelegate's application(_:didReceiveRemoteNotification:fetchCompletionHandler:) to process silent remote notifications after scene connection."
This `launchType == .push` code path is never reached.
This notification is no longer observed
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
<false/>

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't support multiple scenes. Changing this would be a big task

static let pushChannel = WireLogger(tag: "push-channel")
static let webSocket = WireLogger(tag: "websocket")
static let proteus = WireLogger(tag: "proteus")
static let sceneDelegate = WireLogger(tag: "SceneDelegate")

@samwyndham samwyndham Jun 8, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the only content change in this file. Other than that I sorted the options.

_ application: ZMApplication,
didFinishLaunching launchOptions: [UIApplication.LaunchOptionsKey: Any?]
) {
startEphemeralTimers()

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was moved to the caller of this method as this method no longer makes sense.

.appendingPathComponent(remoteIdentifier.uuidString, isDirectory: true)
}

NotificationCenter.default.post(name: NSNotification.Name.ZMUserSessionDidBecomeAvailable, object: nil)

@samwyndham samwyndham Jun 8, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is no longer observed.

// Singletons
var unauthenticatedSession: UnauthenticatedSession? {
SessionManager.shared?.unauthenticatedSession
var mainWindow: UIWindow? {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is now optional as its existence is a little later in the lifecycle.

Comment on lines -49 to -56
private let cookieStorage = CookieStorage(cookieEncryptionKey: UserDefaults.cookiesKey())
// MARK: - Private properties

private lazy var voIPPushManager: VoIPPushManager = .init(
application: UIApplication.shared,
pushTokenService: pushTokenService
)

private let pushTokenService = PushTokenService()

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These have moved to AppDependencies so they are accessible here and from the SceneDelegate.

@samwyndham samwyndham requested review from David-Henner and netbe June 8, 2026 15:57
@github-actions

github-actions Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Test Results

3 010 tests   2 983 ✅  3m 54s ⏱️
  436 suites     27 💤
    3 files        0 ❌

Results for commit e12dbf3.

♻️ This comment has been updated with latest results.

Summary: workflow run #27207229732
Allure report (download zip): html-report-30614-chore_adopt-uiscene-lifecycle-WPB-25097

@datadog-wireapp

datadog-wireapp Bot commented Jun 8, 2026

Copy link
Copy Markdown

Tests

🎉 All green!

❄️ No new flaky tests detected
🧪 All tests passed

This comment will be updated automatically if new data arrives.
🔗 Commit SHA: e12dbf3 | Docs | Give us feedback!

@sonarqubecloud

sonarqubecloud Bot commented Jun 9, 2026

Copy link
Copy Markdown

@netbe netbe left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

left some questions before approving

public func start(connectionOptions: UIScene.ConnectionOptions) async {
if
let url = launchOptions[UIApplication.LaunchOptionsKey.url] as? URL,
let url = connectionOptions.urlContexts.first?.url, // Currently we only support one URL

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: what's the url? same as launch url so deeplink?

@samwyndham samwyndham Jun 10, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes exactly. I was not able to find the reason that this is now a collection of URLs. The best I could work out with the help of a LLM (e.g. maybe nonsense) is that:

  1. An app could quickly try and launch our app with multiple calls in succession. The OS might batch these into a single instantiation.
  2. Future proofing for future Apple plans.

I couldn't find a better explanation.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: why are snapshots recorded here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh right, I forgot to mention this in the description. I've updated the PR description. Please see there.


// MARK: - UISceneDelegate

var window: UIWindow?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: private set

@samwyndham samwyndham Jun 10, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't unfortunately because this is the UISceneDelegate API:

optional var window: UIWindow? { get set }

sceneConnectionOptions: connectionOptions
)

(UIApplication.shared.delegate as? AppDelegate)?.queueInitializationOperations()

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: should this be called inside the scenedelegate? maybe the launchOperations need some review. I wonder if some should be done even if a scene is not connected (background)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Conceptually launch options should really be handled by the AppDelegate as they shouldn't really be specific to a scene. I've taken some time to understand the launch operations better. As far as I understand these should really be done before we setup the appRouteRouter but there is some complexity here as access to things like group user defaults can be blocked by file protection. Making worthwhile changes here is somewhat risky and I don't want to really add additional risk to this current PR. Therefore I've created a Jira ticket to look into to this further: https://wearezeta.atlassian.net/browse/WPB-26555 and don't plan to address your comment in this PR.

Comment on lines -253 to -273
func applicationWillEnterForeground(_ application: UIApplication) {
WireLogger.appDelegate.info(
"applicationWillEnterForeground: (applicationState = \(application.applicationState)",
attributes: .safePublic
)
}

func applicationDidBecomeActive(_ application: UIApplication) {
WireLogger.appDelegate.info(
"applicationDidBecomeActive (applicationState = \(application.applicationState))",
attributes: .safePublic
)

switch launchType {
case .url,
.push:
break
default:
launchType = .direct
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: what replaces the app lifecycle events since these are removed ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These app lifecycle are no longer called on the AppDelegate. However, the equivalent notifications still exist and can be observed.

@samwyndham samwyndham requested a review from netbe June 24, 2026 09:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants