mirror of
https://github.com/NativeScript/NativeScript.git
synced 2025-11-05 13:26:48 +08:00
feat: iOS 26 types with improvements (ActionBar, Switch) + .ns-{platform}-{sdkVersion} css root scoping (#10775)
This provides for better ability to target platform > sdk > majorVersion specific features. For example, iOS 26 does not render titles when a background color is set on the actionbar. this allows that style to be overridden only on iOS 26 if desired.
This commit is contained in:
@@ -2,6 +2,7 @@ import { Application } from '../application';
|
||||
import type { View } from '../ui/core/view';
|
||||
import { AccessibilityServiceEnabledObservable } from './accessibility-service';
|
||||
import { FontScaleCategory, getCurrentFontScale, getFontScaleCategory, VALID_FONT_SCALES } from './font-scale';
|
||||
import { SDK_VERSION } from '../utils/constants';
|
||||
|
||||
// CSS-classes
|
||||
const fontScaleExtraSmallCategoryClass = `a11y-fontscale-xs`;
|
||||
@@ -14,6 +15,9 @@ const a11yServiceEnabledClass = `a11y-service-enabled`;
|
||||
const a11yServiceDisabledClass = `a11y-service-disabled`;
|
||||
const a11yServiceClasses = [a11yServiceEnabledClass, a11yServiceDisabledClass];
|
||||
|
||||
// SDK Version CSS classes
|
||||
let sdkVersionClasses: string[] = [];
|
||||
|
||||
let accessibilityServiceObservable: AccessibilityServiceEnabledObservable;
|
||||
let fontScaleCssClasses: Map<number, string>;
|
||||
|
||||
@@ -29,6 +33,31 @@ function ensureClasses() {
|
||||
fontScaleCssClasses = new Map(VALID_FONT_SCALES.map((fs) => [fs, `a11y-fontscale-${Number(fs * 100).toFixed(0)}`]));
|
||||
|
||||
accessibilityServiceObservable = new AccessibilityServiceEnabledObservable();
|
||||
|
||||
// Initialize SDK version CSS class once
|
||||
initializeSdkVersionClass();
|
||||
}
|
||||
|
||||
function initializeSdkVersionClass(): void {
|
||||
const majorVersion = Math.floor(SDK_VERSION);
|
||||
sdkVersionClasses = [];
|
||||
|
||||
let platformPrefix = '';
|
||||
if (__APPLE__) {
|
||||
platformPrefix = __VISIONOS__ ? 'ns-visionos' : 'ns-ios';
|
||||
} else if (__ANDROID__) {
|
||||
platformPrefix = 'ns-android';
|
||||
}
|
||||
|
||||
if (platformPrefix) {
|
||||
// Add exact version class (e.g., .ns-ios-26 or .ns-android-36)
|
||||
// this acts like 'gte' for that major version range
|
||||
// e.g., if user wants iOS 27, they can add .ns-ios-27 specifiers
|
||||
sdkVersionClasses.push(`${platformPrefix}-${majorVersion}`);
|
||||
}
|
||||
|
||||
// Apply the SDK version classes to root views
|
||||
applySdkVersionClass();
|
||||
}
|
||||
|
||||
function applyRootCssClass(cssClasses: string[], newCssClass: string): void {
|
||||
@@ -41,6 +70,32 @@ function applyRootCssClass(cssClasses: string[], newCssClass: string): void {
|
||||
|
||||
const rootModalViews = <Array<View>>rootView._getRootModalViews();
|
||||
rootModalViews.forEach((rootModalView) => Application.applyCssClass(rootModalView, cssClasses, newCssClass));
|
||||
|
||||
// Note: SDK version classes are applied separately to avoid redundant work
|
||||
}
|
||||
|
||||
function applySdkVersionClass(): void {
|
||||
if (!sdkVersionClasses.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const rootView = Application.getRootView();
|
||||
if (!rootView) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Batch apply all SDK version classes to root view for better performance
|
||||
const classesToAdd = sdkVersionClasses.filter((className) => !rootView.cssClasses.has(className));
|
||||
classesToAdd.forEach((className) => rootView.cssClasses.add(className));
|
||||
|
||||
// Apply to modal views only if there are any
|
||||
const rootModalViews = <Array<View>>rootView._getRootModalViews();
|
||||
if (rootModalViews.length > 0) {
|
||||
rootModalViews.forEach((rootModalView) => {
|
||||
const modalClassesToAdd = sdkVersionClasses.filter((className) => !rootModalView.cssClasses.has(className));
|
||||
modalClassesToAdd.forEach((className) => rootModalView.cssClasses.add(className));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function applyFontScaleToRootViews(): void {
|
||||
|
||||
@@ -7,13 +7,12 @@ import { LinearGradient } from '../styling/linear-gradient';
|
||||
import { colorProperty, backgroundInternalProperty, backgroundColorProperty, backgroundImageProperty } from '../styling/style-properties';
|
||||
import { ios as iosViewUtils } from '../utils';
|
||||
import { ImageSource } from '../../image-source';
|
||||
import { layout, iOSNativeHelper, isFontIconURI } from '../../utils';
|
||||
import { layout, isFontIconURI } from '../../utils';
|
||||
import { SDK_VERSION } from '../../utils/constants';
|
||||
import { accessibilityHintProperty, accessibilityLabelProperty, accessibilityLanguageProperty, accessibilityValueProperty } from '../../accessibility/accessibility-properties';
|
||||
|
||||
export * from './action-bar-common';
|
||||
|
||||
const majorVersion = iOSNativeHelper.MajorVersion;
|
||||
const UNSPECIFIED = layout.makeMeasureSpec(0, layout.UNSPECIFIED);
|
||||
|
||||
interface NSUINavigationBar extends UINavigationBar {
|
||||
@@ -271,7 +270,7 @@ export class ActionBar extends ActionBarBase {
|
||||
// show the one from the old page but the new page will still be visible (because we canceled EdgeBackSwipe gesutre)
|
||||
// Consider moving this to new method and call it from - navigationControllerDidShowViewControllerAnimated.
|
||||
const image = img ? img.imageWithRenderingMode(UIImageRenderingMode.AlwaysOriginal) : null;
|
||||
if (majorVersion >= 15) {
|
||||
if (SDK_VERSION >= 15) {
|
||||
const appearance = this._getAppearance(navigationBar);
|
||||
appearance.setBackIndicatorImageTransitionMaskImage(image, image);
|
||||
this._updateAppearance(navigationBar, appearance);
|
||||
@@ -304,6 +303,9 @@ export class ActionBar extends ActionBarBase {
|
||||
navigationItem.accessibilityLabel = this.accessibilityLabel;
|
||||
navigationItem.accessibilityLanguage = this.accessibilityLanguage;
|
||||
navigationItem.accessibilityHint = this.accessibilityHint;
|
||||
|
||||
// Configure large title support for this navigation item
|
||||
this.checkLargeTitleSupport(navigationItem);
|
||||
}
|
||||
|
||||
private populateMenuItems(navigationItem: UINavigationItem) {
|
||||
@@ -378,7 +380,7 @@ export class ActionBar extends ActionBarBase {
|
||||
}
|
||||
if (color) {
|
||||
const titleTextColor = NSDictionary.dictionaryWithObjectForKey(color.ios, NSForegroundColorAttributeName);
|
||||
if (majorVersion >= 15) {
|
||||
if (SDK_VERSION >= 15) {
|
||||
const appearance = this._getAppearance(navBar);
|
||||
appearance.titleTextAttributes = titleTextColor;
|
||||
}
|
||||
@@ -398,7 +400,7 @@ export class ActionBar extends ActionBarBase {
|
||||
}
|
||||
|
||||
const nativeColor = color instanceof Color ? color.ios : color;
|
||||
if (__VISIONOS__ || majorVersion >= 15) {
|
||||
if (__VISIONOS__ || SDK_VERSION >= 15) {
|
||||
const appearance = this._getAppearance(navBar);
|
||||
// appearance.configureWithOpaqueBackground();
|
||||
appearance.backgroundColor = nativeColor;
|
||||
@@ -416,7 +418,7 @@ export class ActionBar extends ActionBarBase {
|
||||
|
||||
let color: UIColor;
|
||||
|
||||
if (__VISIONOS__ || majorVersion >= 15) {
|
||||
if (__VISIONOS__ || SDK_VERSION >= 15) {
|
||||
const appearance = this._getAppearance(navBar);
|
||||
color = appearance.backgroundColor;
|
||||
} else {
|
||||
@@ -432,7 +434,7 @@ export class ActionBar extends ActionBarBase {
|
||||
return;
|
||||
}
|
||||
|
||||
if (__VISIONOS__ || majorVersion >= 15) {
|
||||
if (__VISIONOS__ || SDK_VERSION >= 15) {
|
||||
const appearance = this._getAppearance(navBar);
|
||||
// appearance.configureWithOpaqueBackground();
|
||||
appearance.backgroundImage = image;
|
||||
@@ -456,7 +458,7 @@ export class ActionBar extends ActionBarBase {
|
||||
|
||||
let image: UIImage;
|
||||
|
||||
if (__VISIONOS__ || majorVersion >= 15) {
|
||||
if (__VISIONOS__ || SDK_VERSION >= 15) {
|
||||
const appearance = this._getAppearance(navBar);
|
||||
image = appearance.backgroundImage;
|
||||
} else {
|
||||
@@ -507,6 +509,8 @@ export class ActionBar extends ActionBarBase {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('ActionBar._onTitlePropertyChanged', this.title);
|
||||
|
||||
if (page.frame) {
|
||||
page.frame._updateActionBar(page);
|
||||
}
|
||||
@@ -517,7 +521,7 @@ export class ActionBar extends ActionBarBase {
|
||||
|
||||
private updateFlatness(navBar: UINavigationBar) {
|
||||
if (this.flat) {
|
||||
if (majorVersion >= 15) {
|
||||
if (SDK_VERSION >= 15) {
|
||||
const appearance = this._getAppearance(navBar);
|
||||
appearance.shadowColor = UIColor.clearColor;
|
||||
this._updateAppearance(navBar, appearance);
|
||||
@@ -530,7 +534,7 @@ export class ActionBar extends ActionBarBase {
|
||||
navBar.translucent = false;
|
||||
}
|
||||
} else {
|
||||
if (majorVersion >= 15) {
|
||||
if (SDK_VERSION >= 15) {
|
||||
if (navBar.standardAppearance) {
|
||||
// Not flat and never been set do nothing.
|
||||
const appearance = navBar.standardAppearance;
|
||||
@@ -581,7 +585,7 @@ export class ActionBar extends ActionBarBase {
|
||||
public onLayout(left: number, top: number, right: number, bottom: number) {
|
||||
const titleView = this.titleView;
|
||||
if (titleView) {
|
||||
if (majorVersion > 10) {
|
||||
if (SDK_VERSION > 10) {
|
||||
// On iOS 11 titleView is wrapped in another view that is centered with constraints.
|
||||
View.layoutChild(this, titleView, 0, 0, titleView.getMeasuredWidth(), titleView.getMeasuredHeight());
|
||||
} else {
|
||||
@@ -670,4 +674,26 @@ export class ActionBar extends ActionBarBase {
|
||||
this.navBar.prefersLargeTitles = value;
|
||||
}
|
||||
}
|
||||
|
||||
private checkLargeTitleSupport(navigationItem: UINavigationItem) {
|
||||
const navBar = this.navBar;
|
||||
if (!navBar) {
|
||||
return;
|
||||
}
|
||||
// Configure large title display mode only when not using a custom titleView
|
||||
if (SDK_VERSION >= 11) {
|
||||
if (this.iosLargeTitle) {
|
||||
// Always show large title for this navigation item when large titles are enabled
|
||||
navigationItem.largeTitleDisplayMode = UINavigationItemLargeTitleDisplayMode.Always;
|
||||
} else {
|
||||
if (SDK_VERSION >= 26) {
|
||||
// Explicitly disable large titles for this navigation item
|
||||
// Due to overlapping title issue in iOS 26
|
||||
navigationItem.largeTitleDisplayMode = UINavigationItemLargeTitleDisplayMode.Never;
|
||||
} else {
|
||||
navigationItem.largeTitleDisplayMode = UINavigationItemLargeTitleDisplayMode.Automatic;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,15 +10,6 @@ export enum NavigationType {
|
||||
replace,
|
||||
}
|
||||
|
||||
export interface TransitionState {
|
||||
enterTransitionListener: any;
|
||||
exitTransitionListener: any;
|
||||
reenterTransitionListener: any;
|
||||
returnTransitionListener: any;
|
||||
transitionName: string;
|
||||
entry: BackstackEntry;
|
||||
}
|
||||
|
||||
export interface ViewEntry {
|
||||
moduleName?: string;
|
||||
create?: () => View;
|
||||
@@ -35,6 +26,17 @@ export interface NavigationEntry extends ViewEntry {
|
||||
clearHistory?: boolean;
|
||||
}
|
||||
|
||||
export interface BackstackEntry {
|
||||
entry: NavigationEntry;
|
||||
resolvedPage: Page;
|
||||
navDepth: number;
|
||||
fragmentTag: string;
|
||||
fragment?: any;
|
||||
viewSavedState?: any;
|
||||
frameId?: number;
|
||||
recreated?: boolean;
|
||||
}
|
||||
|
||||
export interface NavigationContext {
|
||||
entry: BackstackEntry;
|
||||
// TODO: remove isBackNavigation for NativeScript 7.0
|
||||
@@ -49,15 +51,13 @@ export interface NavigationTransition {
|
||||
curve?: any;
|
||||
}
|
||||
|
||||
export interface BackstackEntry {
|
||||
entry: NavigationEntry;
|
||||
resolvedPage: Page;
|
||||
navDepth: number;
|
||||
fragmentTag: string;
|
||||
fragment?: any;
|
||||
viewSavedState?: any;
|
||||
frameId?: number;
|
||||
recreated?: boolean;
|
||||
export interface TransitionState {
|
||||
enterTransitionListener: any;
|
||||
exitTransitionListener: any;
|
||||
reenterTransitionListener: any;
|
||||
returnTransitionListener: any;
|
||||
transitionName: string;
|
||||
entry: BackstackEntry;
|
||||
}
|
||||
|
||||
export interface AndroidFrame extends Observable {
|
||||
|
||||
1
packages/core/ui/frame/index.d.ts
vendored
1
packages/core/ui/frame/index.d.ts
vendored
@@ -3,6 +3,7 @@ import { NavigatedData, Page } from '../page';
|
||||
import { Observable, EventData } from '../../data/observable';
|
||||
import { Property, View } from '../core/view';
|
||||
import { Transition } from '../transition';
|
||||
import { BackstackEntry } from './frame-interfaces';
|
||||
|
||||
export * from './frame-interfaces';
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
//Types
|
||||
import { iOSFrame as iOSFrameDefinition, BackstackEntry, NavigationTransition } from '.';
|
||||
import { iOSFrame as iOSFrameDefinition, NavigationTransition } from '.';
|
||||
import type { BackstackEntry } from './frame-interfaces';
|
||||
import { FrameBase, NavigationType } from './frame-common';
|
||||
import { Page } from '../page';
|
||||
import { View } from '../core/view';
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import { SwitchBase, checkedProperty, offBackgroundColorProperty } from './switch-common';
|
||||
import { colorProperty, backgroundColorProperty, backgroundInternalProperty } from '../styling/style-properties';
|
||||
import { Color } from '../../color';
|
||||
import { iOSNativeHelper, layout } from '../../utils';
|
||||
import { SDK_VERSION } from '../../utils/constants';
|
||||
|
||||
export * from './switch-common';
|
||||
|
||||
const majorVersion = iOSNativeHelper.MajorVersion;
|
||||
|
||||
@NativeClass
|
||||
class SwitchChangeHandlerImpl extends NSObject {
|
||||
private _owner: WeakRef<Switch>;
|
||||
@@ -30,15 +28,12 @@ class SwitchChangeHandlerImpl extends NSObject {
|
||||
};
|
||||
}
|
||||
|
||||
const zeroSize = { width: 0, height: 0 };
|
||||
export class Switch extends SwitchBase {
|
||||
nativeViewProtected: UISwitch;
|
||||
private _handler: NSObject;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.width = 51;
|
||||
this.height = 31;
|
||||
}
|
||||
|
||||
public createNativeView() {
|
||||
@@ -75,7 +70,7 @@ export class Switch extends SwitchBase {
|
||||
// only add :checked pseudo handling on supported iOS versions
|
||||
// ios <13 works but causes glitchy animations when toggling
|
||||
// so we decided to keep the old behavior on older versions.
|
||||
if (majorVersion >= 13) {
|
||||
if (SDK_VERSION >= 13) {
|
||||
super._onCheckedPropertyChanged(newValue);
|
||||
|
||||
if (this.offBackgroundColor) {
|
||||
@@ -93,17 +88,6 @@ export class Switch extends SwitchBase {
|
||||
return this.nativeViewProtected;
|
||||
}
|
||||
|
||||
public onMeasure(widthMeasureSpec: number, heightMeasureSpec: number): void {
|
||||
// It can't be anything different from 51x31
|
||||
const nativeSize = this.nativeViewProtected.sizeThatFits(zeroSize);
|
||||
this.width = nativeSize.width;
|
||||
this.height = nativeSize.height;
|
||||
|
||||
const widthAndState = Switch.resolveSizeAndState(layout.toDevicePixels(nativeSize.width), layout.toDevicePixels(51), layout.EXACTLY, 0);
|
||||
const heightAndState = Switch.resolveSizeAndState(layout.toDevicePixels(nativeSize.height), layout.toDevicePixels(31), layout.EXACTLY, 0);
|
||||
this.setMeasuredDimension(widthAndState, heightAndState);
|
||||
}
|
||||
|
||||
[checkedProperty.getDefault](): boolean {
|
||||
return false;
|
||||
}
|
||||
@@ -130,7 +114,7 @@ export class Switch extends SwitchBase {
|
||||
return this.nativeViewProtected.onTintColor;
|
||||
}
|
||||
[backgroundColorProperty.setNative](value: UIColor | Color) {
|
||||
if (majorVersion >= 13) {
|
||||
if (SDK_VERSION >= 13) {
|
||||
if (!this.offBackgroundColor || this.checked) {
|
||||
this.setNativeBackgroundColor(value);
|
||||
}
|
||||
@@ -151,7 +135,7 @@ export class Switch extends SwitchBase {
|
||||
return this.nativeViewProtected.backgroundColor;
|
||||
}
|
||||
[offBackgroundColorProperty.setNative](value: Color | UIColor) {
|
||||
if (majorVersion >= 13) {
|
||||
if (SDK_VERSION >= 13) {
|
||||
if (!this.checked) {
|
||||
this.setNativeBackgroundColor(value);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user