Skip to content
Merged
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
6 changes: 0 additions & 6 deletions apps/toolbox/src/app-root.xml
Original file line number Diff line number Diff line change
@@ -1,8 +1,2 @@
<Frame defaultPage="main-page">
</Frame>

<!-- Test SplitView
Must be root component of the app to work properly
-->
<!-- <Frame defaultPage="split-view/split-view-root">
</Frame> -->
10 changes: 6 additions & 4 deletions apps/toolbox/src/main.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { Application, SplitView } from '@nativescript/core';
import { Application } from '@nativescript/core';

// Application.run({ moduleName: 'app-root' });
Application.run({ moduleName: 'app-root' });

SplitView.SplitStyle = 'triple';
Application.run({ moduleName: 'split-view/split-view-root' });
// Comment above and uncomment below to test SplitView directly
// import { SplitView } from '@nativescript/core';
// SplitView.SplitStyle = 'triple';
// Application.run({ moduleName: 'split-view/split-view-root' });
74 changes: 63 additions & 11 deletions packages/core/ui/switch/index.ios.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ class SwitchChangeHandlerImpl extends NSObject {
export class Switch extends SwitchBase {
nativeViewProtected: UISwitch;
private _handler: NSObject;
// Defer color updates while iOS 26+ "glass" toggle animation runs
private _toggleColorTimer: NodeJS.Timeout | null = null;

constructor() {
super();
Expand All @@ -49,20 +51,57 @@ export class Switch extends SwitchBase {

public disposeNativeView() {
this._handler = null;
if (this._toggleColorTimer) {
clearTimeout(this._toggleColorTimer);
this._toggleColorTimer = null;
}
super.disposeNativeView();
}

private setNativeBackgroundColor(value: UIColor | Color) {
const native = this.nativeViewProtected;
if (value) {
this.nativeViewProtected.onTintColor = value instanceof Color ? value.ios : value;
this.nativeViewProtected.tintColor = value instanceof Color ? value.ios : value;
this.nativeViewProtected.backgroundColor = value instanceof Color ? value.ios : value;
this.nativeViewProtected.layer.cornerRadius = this.nativeViewProtected.frame.size.height / 2;
const nativeValue = value instanceof Color ? value.ios : value;
// Keep the legacy behavior for on/off colors
native.onTintColor = nativeValue;
native.tintColor = nativeValue;
native.backgroundColor = nativeValue;

// Since iOS 16+ the control no longer clips its background by default.
// Ensure the track-shaped background doesn't bleed outside the control bounds.
if (SDK_VERSION >= 16) {
native.clipsToBounds = true;
native.layer.masksToBounds = true;
}

// Corner radius must be based on the final laid out size; use bounds first,
// then fall back to frame. If size isn't known yet, update on the next tick.
const height = native.bounds?.size?.height || native.frame?.size?.height || 0;
if (height > 0) {
native.layer.cornerRadius = height / 2;
} else {
// Defer until after layout
setTimeout(() => {
const n = this.nativeViewProtected;
if (!n) {
return;
}
const h = n.bounds?.size?.height || n.frame?.size?.height || 0;
if (h > 0) {
n.layer.cornerRadius = h / 2;
}
}, 0);
}
} else {
this.nativeViewProtected.onTintColor = null;
this.nativeViewProtected.tintColor = null;
this.nativeViewProtected.backgroundColor = null;
this.nativeViewProtected.layer.cornerRadius = 0;
native.onTintColor = null;
native.tintColor = null;
native.backgroundColor = null;
native.layer.cornerRadius = 0;
if (SDK_VERSION >= 16) {
// Restore default clipping behavior
native.clipsToBounds = false;
native.layer.masksToBounds = false;
}
}
}

Expand All @@ -74,10 +113,23 @@ export class Switch extends SwitchBase {
super._onCheckedPropertyChanged(newValue);

if (this.offBackgroundColor) {
if (!newValue) {
this.setNativeBackgroundColor(this.offBackgroundColor);
const nextColor = !newValue ? this.offBackgroundColor : this.backgroundColor instanceof Color ? this.backgroundColor : new Color(this.backgroundColor);

// On iOS 26+, coordinate with the system's switch animation:
// delay applying track color until the toggle animation finishes to avoid a janky mid-animation recolor.
if (SDK_VERSION >= 26) {
if (this._toggleColorTimer) {
clearTimeout(this._toggleColorTimer);
}
this._toggleColorTimer = setTimeout(() => {
const ANIMATION_DELAY_MS = 0.26; // approx. system toggle duration
UIView.animateWithDurationAnimations(ANIMATION_DELAY_MS, () => {
this._toggleColorTimer = null;
this.setNativeBackgroundColor(nextColor);
});
}, 0);
} else {
this.setNativeBackgroundColor(this.backgroundColor instanceof Color ? this.backgroundColor : new Color(this.backgroundColor));
this.setNativeBackgroundColor(nextColor);
}
}
}
Expand Down