Skip to content

Commit 7f4ccae

Browse files
shai-almogclaude
andauthored
iOS: add BrowserComponent.interfaceStyle property to pin web-view appearance (#5203)
WKWebView feeds the device's light/dark trait into the page's prefers-color-scheme media query and the UA rendering of default backgrounds and form controls. Since #4786 enabled the UIScene lifecycle by default, the window is created programmatically and the UIUserInterfaceStyle Info.plist key is no longer reliably applied to it, so embedded web views started following the device dark mode with no way to override it per component. Add a BrowserComponent.BROWSER_PROPERTY_INTERFACE_STYLE property ("light"/"dark"/"auto") routed through setBrowserProperty to the native widget, where it sets the web view's overrideUserInterfaceStyle. This is the natural per-component vertical rather than an app-wide window hack. overrideUserInterfaceStyle is a UIView property so the single cast covers both WKWebView and the legacy UIWebView. Other platforms ignore the unknown property as usual. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 89bb334 commit 7f4ccae

4 files changed

Lines changed: 46 additions & 0 deletions

File tree

CodenameOne/src/com/codename1/ui/BrowserComponent.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,14 @@ public class BrowserComponent extends Container {
9191
/// Browser property key to control whether links with `target="_blank"` or `target="_new"`
9292
/// should be followed in the current browser view. Defaults to `true`.
9393
public static final String BROWSER_PROPERTY_FOLLOW_TARGET_BLANK = "BrowserComponent.followTargetBlank";
94+
/// Browser property key to pin the appearance (light/dark) of the native web widget regardless
95+
/// of the device-wide setting. This drives the `prefers-color-scheme` CSS media query and the
96+
/// user-agent rendering of form controls and default backgrounds inside the page. Accepted
97+
/// values are `"light"`, `"dark"` and `"auto"` (follow the device, the default). Use it via
98+
/// `browser.setProperty(BrowserComponent.BROWSER_PROPERTY_INTERFACE_STYLE, "light")`.
99+
/// Currently honored on iOS (WKWebView); platforms that do not support pinning the web-view
100+
/// appearance ignore it.
101+
public static final String BROWSER_PROPERTY_INTERFACE_STYLE = "BrowserComponent.interfaceStyle";
94102
/// String constant for web event listener `com.codename1.ui.events.ActionListener)`
95103
public static final String onStart = "onStart";
96104
/// String constant for web event listener `com.codename1.ui.events.ActionListener)`

Ports/iOSPort/nativeSources/IOSNative.m

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3079,6 +3079,29 @@ void com_codename1_impl_ios_IOSNative_setBrowserFollowTargetBlank___long_boolean
30793079
});
30803080
}
30813081

3082+
// Pin the appearance of the native web widget independently of the device-wide
3083+
// setting. style: 0 = unspecified/auto (follow device), 1 = light, 2 = dark.
3084+
// Setting overrideUserInterfaceStyle on the WKWebView feeds the trait collection
3085+
// that drives the page's prefers-color-scheme media query and the UA rendering of
3086+
// default backgrounds / form controls, so a page that adapts to dark mode can be
3087+
// kept light (or dark) regardless of the user's system appearance.
3088+
void com_codename1_impl_ios_IOSNative_setBrowserInterfaceStyle___long_int(CN1_THREAD_STATE_MULTI_ARG JAVA_OBJECT instanceObject, JAVA_LONG peer, JAVA_INT style) {
3089+
dispatch_sync(dispatch_get_main_queue(), ^{
3090+
POOL_BEGIN();
3091+
if (@available(iOS 13.0, *)) {
3092+
UIView* w = (BRIDGE_CAST UIView*)((void *)peer);
3093+
UIUserInterfaceStyle uiStyle = UIUserInterfaceStyleUnspecified;
3094+
if (style == 1) {
3095+
uiStyle = UIUserInterfaceStyleLight;
3096+
} else if (style == 2) {
3097+
uiStyle = UIUserInterfaceStyleDark;
3098+
}
3099+
w.overrideUserInterfaceStyle = uiStyle;
3100+
}
3101+
POOL_END();
3102+
});
3103+
}
3104+
30823105

30833106
void com_codename1_impl_ios_IOSNative_setPinchToZoomEnabled___long_boolean(CN1_THREAD_STATE_MULTI_ARG JAVA_OBJECT instanceObject, JAVA_LONG peer, JAVA_BOOLEAN enabled) {
30843107
dispatch_sync(dispatch_get_main_queue(), ^{

Ports/iOSPort/src/com/codename1/impl/ios/IOSImplementation.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7928,6 +7928,19 @@ public void setBrowserProperty(PeerComponent browserPeer, String key, Object val
79287928
if (BrowserComponent.BROWSER_PROPERTY_FOLLOW_TARGET_BLANK.equals(key)) {
79297929
nativeInstance.setBrowserFollowTargetBlank(get(browserPeer), Boolean.TRUE.equals(value));
79307930
}
7931+
if (BrowserComponent.BROWSER_PROPERTY_INTERFACE_STYLE.equals(key)) {
7932+
// Maps to UIUserInterfaceStyle: 0 = unspecified/auto, 1 = light, 2 = dark.
7933+
int style = 0;
7934+
if (value != null) {
7935+
String v = value.toString();
7936+
if ("light".equalsIgnoreCase(v)) {
7937+
style = 1;
7938+
} else if ("dark".equalsIgnoreCase(v)) {
7939+
style = 2;
7940+
}
7941+
}
7942+
nativeInstance.setBrowserInterfaceStyle(get(browserPeer), style);
7943+
}
79317944
}
79327945

79337946
/**

Ports/iOSPort/src/com/codename1/impl/ios/IOSNative.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,8 @@ native void fillGradient(int kind, int stopCount, float[] positions, float[] pre
253253

254254
native void setBrowserUserAgent(long browserPeer, String ua);
255255
native void setBrowserFollowTargetBlank(long browserPeer, boolean follow);
256+
// style: 0 = unspecified/auto (follow device), 1 = light, 2 = dark
257+
native void setBrowserInterfaceStyle(long browserPeer, int style);
256258

257259
native void browserBack(long browserPeer);
258260
native void browserStop(long browserPeer);

0 commit comments

Comments
 (0)