Skip to content

Commit a96cc35

Browse files
authored
Fix system theme change handling (JetBrains#2735)
- Read actual value of the `traitCollection.userInterfaceStyle` in `ComposeHostingView` and `ComposeHostingViewController`. - Ensure that the initial system theme is propagated correctly. Fixes https://youtrack.jetbrains.com/issue/CMP-9707/iOS-window-update-overrideUserInterfaceStyle-value-isSystemInDarkTheme-not-updating-correctly ## Release Notes ### Fixes - iOS - Fix an issue where manually overriding the user interface style does not propagate properly to the `LocalSystemTheme`.
1 parent 5807278 commit a96cc35

5 files changed

Lines changed: 81 additions & 10 deletions

File tree

compose/ui/ui/src/iosMain/kotlin/androidx/compose/ui/scene/ComposeContainer.ios.kt

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,6 @@ internal class ComposeContainer(
170170

171171
window ?: return
172172

173-
userInterfaceStyleDidChange()
174173
updateInterfaceOrientationState()
175174

176175
layersHolder?.layersViewController?.referenceWindow = view.window
@@ -199,9 +198,8 @@ internal class ComposeContainer(
199198
navigationEventInput.onDidMoveToWindow(null, view)
200199
}
201200

202-
fun userInterfaceStyleDidChange() {
203-
systemThemeState.value = view.traitCollection.userInterfaceStyle
204-
.asComposeSystemTheme()
201+
fun updateUserInterfaceStyle(style: UIUserInterfaceStyle) {
202+
systemThemeState.value = style.asComposeSystemTheme()
205203
}
206204

207205
fun initializeComposeScene() {

compose/ui/ui/src/iosMain/kotlin/androidx/compose/ui/scene/ComposeHostingView.ios.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,10 +113,12 @@ internal class ComposeHostingView(
113113
}
114114

115115
override fun userInterfaceStyleDidChange() {
116-
container.userInterfaceStyleDidChange()
116+
container.updateUserInterfaceStyle(traitCollection.userInterfaceStyle)
117117
}
118118

119119
override fun viewDidEnterWindowHierarchy() {
120+
container.updateUserInterfaceStyle(traitCollection.userInterfaceStyle)
121+
120122
container.initializeComposeScene()
121123
}
122124

compose/ui/ui/src/iosMain/kotlin/androidx/compose/ui/scene/ComposeHostingViewController.ios.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,11 +83,11 @@ internal class ComposeHostingViewController(
8383
super.viewDidLoad()
8484

8585
configuration.delegate.viewDidLoad()
86-
container.userInterfaceStyleDidChange()
86+
container.updateUserInterfaceStyle(traitCollection.userInterfaceStyle)
8787
}
8888

8989
override fun userInterfaceStyleDidChange() {
90-
container.userInterfaceStyleDidChange()
90+
container.updateUserInterfaceStyle(traitCollection.userInterfaceStyle)
9191
}
9292

9393
override fun viewWillTransitionToSize(
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/*
2+
* Copyright 2026 The Android Open Source Project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package androidx.compose.ui.platform
18+
19+
import androidx.compose.ui.LocalSystemTheme
20+
import androidx.compose.ui.SystemTheme
21+
import androidx.compose.ui.test.runUIKitInstrumentedTest
22+
import kotlin.test.Test
23+
import kotlin.test.assertEquals
24+
import kotlin.test.assertNotNull
25+
import platform.UIKit.UIUserInterfaceStyle
26+
27+
class SystemThemeTest {
28+
29+
@Test
30+
fun testInitialOverrideUserInterfaceStyleLight() = runUIKitInstrumentedTest {
31+
appDelegate.window?.overrideUserInterfaceStyle = UIUserInterfaceStyle.UIUserInterfaceStyleLight
32+
var systemTheme: SystemTheme? = null
33+
setContent {
34+
systemTheme = LocalSystemTheme.current
35+
}
36+
37+
assertEquals(SystemTheme.Light, systemTheme)
38+
}
39+
40+
@Test
41+
fun testInitialOverrideUserInterfaceStyleDark() = runUIKitInstrumentedTest {
42+
appDelegate.window?.overrideUserInterfaceStyle = UIUserInterfaceStyle.UIUserInterfaceStyleDark
43+
var systemTheme: SystemTheme? = null
44+
setContent {
45+
systemTheme = LocalSystemTheme.current
46+
}
47+
48+
assertEquals(SystemTheme.Dark, systemTheme)
49+
}
50+
51+
@Test
52+
fun testOverrideUserInterfaceStyle() = runUIKitInstrumentedTest {
53+
var systemTheme: SystemTheme? = null
54+
setContent {
55+
systemTheme = LocalSystemTheme.current
56+
}
57+
58+
assertNotNull(systemTheme)
59+
60+
appDelegate.window?.overrideUserInterfaceStyle =
61+
UIUserInterfaceStyle.UIUserInterfaceStyleLight
62+
waitUntil("System theme should eventually be Light") { systemTheme == SystemTheme.Light }
63+
64+
appDelegate.window?.overrideUserInterfaceStyle =
65+
UIUserInterfaceStyle.UIUserInterfaceStyleDark
66+
waitUntil("System theme should eventually be Dark") { systemTheme == SystemTheme.Dark }
67+
68+
appDelegate.window?.overrideUserInterfaceStyle =
69+
UIUserInterfaceStyle.UIUserInterfaceStyleLight
70+
waitUntil("System theme should eventually be Light") { systemTheme == SystemTheme.Light }
71+
}
72+
}

compose/ui/ui/src/uikitInstrumentedTest/kotlin/androidx/compose/ui/test/UIKitInstrumentedTest.kt

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -434,7 +434,7 @@ internal class UIKitInstrumentedTest(
434434

435435
@OptIn(ExperimentalForeignApi::class)
436436
internal class MockAppDelegate: NSObject(), UIApplicationDelegateProtocol {
437-
private var _window: UIWindow? = null
437+
private var _window: UIWindow? = UIWindow(frame = UIScreen.mainScreen.bounds)
438438
override fun window(): UIWindow? = _window
439439

440440
private var supportedInterfaceOrientations: UIInterfaceOrientationMask = UIInterfaceOrientationMaskAll
@@ -444,9 +444,8 @@ internal class MockAppDelegate: NSObject(), UIApplicationDelegateProtocol {
444444

445445
val scene = UIApplication.sharedApplication().connectedScenes.first() as? UIWindowScene
446446
?: error("No window scene found")
447-
val allWindows = scene.windows
447+
val allWindows = scene.windows - _window
448448

449-
_window = UIWindow(frame = UIScreen.mainScreen.bounds)
450449
_window?.backgroundColor = UIColor.systemBackgroundColor
451450
_window?.windowScene = scene
452451

0 commit comments

Comments
 (0)