← 输入框|github.com|刷新
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: 1 addition & 5 deletions packages/core/ui/action-bar/index.android.ts
Original file line number Diff line number Diff line change
Expand Up @@ -385,11 +385,7 @@ export class ActionBar extends ActionBarBase {
super._addViewToNativeVisualTree(child);

if (this.nativeViewProtected && child.nativeViewProtected) {
if (atIndex >= this.nativeViewProtected.getChildCount()) {
this.nativeViewProtected.addView(child.nativeViewProtected);
} else {
this.nativeViewProtected.addView(child.nativeViewProtected, atIndex);
}
AndroidHelper.insertNativeSubview(this.nativeViewProtected, child.nativeViewProtected, atIndex);

return true;
}
Expand Down
2 changes: 1 addition & 1 deletion packages/core/ui/core/view/index.android.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1826,7 +1826,7 @@ export class CustomLayoutView extends ContainerView {
if (Trace.isEnabled()) {
Trace.write(`${this}.nativeView.addView(${child}.nativeView, ${atIndex})`, Trace.categories.VisualTreeEvents);
}
this.nativeViewProtected.addView(child.nativeViewProtected, atIndex);
AndroidHelper.insertNativeSubview(this.nativeViewProtected, child.nativeViewProtected, atIndex);
if (child instanceof View) {
this._updateNativeLayoutParams(child);
}
Expand Down
6 changes: 1 addition & 5 deletions packages/core/ui/core/view/index.ios.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1270,11 +1270,7 @@ export class CustomLayoutView extends ContainerView {
const childNativeView: NativeScriptUIView = <NativeScriptUIView>child.nativeViewProtected;

if (parentNativeView && childNativeView) {
if (typeof atIndex !== 'number' || atIndex >= parentNativeView.subviews.count) {
parentNativeView.addSubview(childNativeView);
} else {
parentNativeView.insertSubviewAtIndex(childNativeView, atIndex);
}
IOSHelper.insertNativeSubview(parentNativeView, childNativeView, atIndex);

// Add outer shadow layer manually as it belongs to parent layer tree (this is needed for reusable views)
if (childNativeView.outerShadowContainerLayer && !childNativeView.outerShadowContainerLayer.superlayer) {
Expand Down
49 changes: 49 additions & 0 deletions packages/core/ui/core/view/view-helper/index.android.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { describe, it, expect, vi } from 'vitest';

vi.hoisted(() => {
(globalThis as any).androidx = { core: { graphics: {} } };
});

import { AndroidHelper } from './index.android';

/** Models android.view.ViewGroup, including its IndexOutOfBoundsException for out-of-range indexes. */
class FakeViewGroup {
readonly children: string[] = [];

getChildCount(): number {
return this.children.length;
}

addView(view: string, index?: number): void {
if (index === undefined || index === -1) {
this.children.push(view);
return;
}
if (index < 0 || index > this.children.length) {
throw new Error(`IndexOutOfBoundsException: index=${index} count=${this.children.length}`);
}
this.children.splice(index, 0, view);
}
}

function parentWith(...views: string[]): FakeViewGroup {
const parent = new FakeViewGroup();
parent.children.push(...views);
return parent;
}

describe('AndroidHelper.insertNativeSubview', () => {
it('inserts at the child index', () => {
const parent = parentWith('a', 'b', 'c');
AndroidHelper.insertNativeSubview(parent as any, 'x' as any, 1);
expect(parent.children).toEqual(['a', 'x', 'b', 'c']);
});

it('appends when the index is absent, negative, or out of range', () => {
const parent = parentWith('a', 'b');
AndroidHelper.insertNativeSubview(parent as any, 'x' as any);
AndroidHelper.insertNativeSubview(parent as any, 'y' as any, -1);
AndroidHelper.insertNativeSubview(parent as any, 'z' as any, Number.MAX_SAFE_INTEGER);
expect(parent.children).toEqual(['a', 'b', 'x', 'y', 'z']);
});
});
8 changes: 8 additions & 0 deletions packages/core/ui/core/view/view-helper/index.android.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,14 @@ export class AndroidHelper {
}
}

static insertNativeSubview(parentNativeView: android.view.ViewGroup, childNativeView: android.view.View, atIndex?: number): void {
if (typeof atIndex !== 'number' || atIndex < 0 || atIndex >= parentNativeView.getChildCount()) {
parentNativeView.addView(childNativeView);
} else {
parentNativeView.addView(childNativeView, atIndex);
}
}

static getCopyOrDrawable(drawable: android.graphics.drawable.Drawable, resources?: android.content.res.Resources): android.graphics.drawable.Drawable {
if (drawable) {
const constantState = drawable.getConstantState();
Expand Down
14 changes: 14 additions & 0 deletions packages/core/ui/core/view/view-helper/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,13 @@ export namespace AndroidHelper {
export function setDrawableColor(color: number, drawable: any /* android.graphics.drawable.Drawable */, blendMode?: any /* androidx.core.graphics.BlendModeCompat */): void;
export function clearDrawableColor(drawable: any /* android.graphics.drawable.Drawable */): void;
export function getCopyOrDrawable(drawable: any /* android.graphics.drawable.Drawable */, resources?: any /* android.content.res.Resources */): any; /* android.graphics.drawable.Drawable */
/**
* Inserts a native child view at a child index, appending when the index is absent or out of range.
* @param parentNativeView Parent ViewGroup.
* @param childNativeView Android view to insert.
* @param atIndex Child index to insert at.
*/
export function insertNativeSubview(parentNativeView: any /* android.view.ViewGroup */, childNativeView: any /* android.view.View */, atIndex?: number): void;
}

/**
Expand All @@ -66,6 +73,13 @@ export namespace IOSHelper {
export function invalidateStatusBarAppearance(controller?: any /* UIViewController */, reason?: string): void;
export function updateAutoAdjustScrollInsets(controller: any /* UIViewController */, owner: View): void;
export function updateConstraints(controller: any /* UIViewController */, owner: View): void;
/**
* Inserts a native subview at a subview index, appending when the index is absent or out of range.
* @param parentNativeView Parent UIView.
* @param childNativeView UIView to insert.
* @param atIndex Subview index to insert at.
*/
export function insertNativeSubview(parentNativeView: any /* UIView */, childNativeView: any /* UIView */, atIndex?: number): void;
export function layoutView(controller: any /* UIViewController */, owner: View): void;
export function getPositionFromFrame(frame: any /* CGRect */): Position;
export function getFrameFromPosition(position: Position, insets?: Position): any; /* CGRect */
Expand Down
59 changes: 59 additions & 0 deletions packages/core/ui/core/view/view-helper/index.ios.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { describe, it, expect } from 'vitest';
import { IOSHelper } from './index.ios';

/** Models UIView's subview ordering; `insertSubviewAtIndex` is absent so any use of it throws. */
class FakeUIView {
readonly children: FakeUIView[] = [];

constructor(readonly tag: string) {}

get subviews() {
const children = this.children;
return {
count: children.length,
objectAtIndex: (index: number) => children[index],
};
}

addSubview(view: FakeUIView): void {
this.children.push(view);
}

insertSubviewBelowSubview(view: FakeUIView, sibling: FakeUIView): void {
this.children.splice(this.children.indexOf(sibling), 0, view);
}
}

function parentWith(...tags: string[]): FakeUIView {
const parent = new FakeUIView('parent');
for (const tag of tags) {
parent.addSubview(new FakeUIView(tag));
}
return parent;
}

function order(parent: FakeUIView): string[] {
return parent.children.map((child) => child.tag);
}

describe('IOSHelper.insertNativeSubview', () => {
it('inserts below the subview currently at the index', () => {
const parent = parentWith('a', 'b', 'c');
IOSHelper.insertNativeSubview(parent as any, new FakeUIView('x') as any, 1);
expect(order(parent)).toEqual(['a', 'x', 'b', 'c']);
});

it('inserts at the front for index 0', () => {
const parent = parentWith('a', 'b');
IOSHelper.insertNativeSubview(parent as any, new FakeUIView('x') as any, 0);
expect(order(parent)).toEqual(['x', 'a', 'b']);
});

it('appends when the index is absent or out of range', () => {
const parent = parentWith('a', 'b');
IOSHelper.insertNativeSubview(parent as any, new FakeUIView('x') as any);
IOSHelper.insertNativeSubview(parent as any, new FakeUIView('y') as any, 3);
IOSHelper.insertNativeSubview(parent as any, new FakeUIView('z') as any, Number.MAX_SAFE_INTEGER);
expect(order(parent)).toEqual(['a', 'b', 'x', 'y', 'z']);
});
});
10 changes: 10 additions & 0 deletions packages/core/ui/core/view/view-helper/index.ios.ts
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,16 @@ export class IOSHelper {
return rootView.safeAreaLayoutGuide;
}

static insertNativeSubview(parentNativeView: UIView, childNativeView: UIView, atIndex?: number): void {
const subviews = parentNativeView.subviews;
if (typeof atIndex !== 'number' || atIndex >= subviews.count) {
parentNativeView.addSubview(childNativeView);
} else {
// insertSubview:atIndex: also counts non-view sublayers, e.g. gradient backgrounds
parentNativeView.insertSubviewBelowSubview(childNativeView, subviews.objectAtIndex(atIndex));
}
}

static layoutView(controller: UIViewController, owner: View): void {
let layoutGuide = controller.view.safeAreaLayoutGuide;
if (!layoutGuide) {
Expand Down
8 changes: 2 additions & 6 deletions packages/core/ui/layouts/liquid-glass-container/index.ios.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { NativeScriptUIView } from '../../utils';
import { supportsGlass } from '../../../utils/constants';
import { GlassEffectType, iosGlassEffectProperty, View } from '../../core/view';
import { GlassEffectType, iosGlassEffectProperty, IOSHelper, View } from '../../core/view';
import { LiquidGlassContainerCommon } from './liquid-glass-container-common';
import { toUIGlassStyle } from '../liquid-glass';

Expand Down Expand Up @@ -39,11 +39,7 @@ export class LiquidGlassContainer extends LiquidGlassContainerCommon {
const childNativeView: NativeScriptUIView = <NativeScriptUIView>child.nativeViewProtected;

if (parentNativeView && childNativeView) {
if (typeof atIndex !== 'number' || atIndex >= parentNativeView.subviews.count) {
parentNativeView.addSubview(childNativeView);
} else {
parentNativeView.insertSubviewAtIndex(childNativeView, atIndex);
}
IOSHelper.insertNativeSubview(parentNativeView, childNativeView, atIndex);

// Add outer shadow layer manually as it belongs to parent layer tree (this is needed for reusable views)
if (childNativeView.outerShadowContainerLayer && !childNativeView.outerShadowContainerLayer.superlayer) {
Expand Down
8 changes: 2 additions & 6 deletions packages/core/ui/layouts/liquid-glass/index.ios.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { NativeScriptUIView } from '../../utils';
import { supportsGlass } from '../../../utils/constants';
import { type GlassEffectType, type GlassEffectVariant, iosGlassEffectProperty, View } from '../../core/view';
import { type GlassEffectType, type GlassEffectVariant, iosGlassEffectProperty, IOSHelper, View } from '../../core/view';
import { LiquidGlassCommon } from './liquid-glass-common';

export class LiquidGlass extends LiquidGlassCommon {
Expand Down Expand Up @@ -35,11 +35,7 @@ export class LiquidGlass extends LiquidGlassCommon {
const childNativeView: NativeScriptUIView = <NativeScriptUIView>child.nativeViewProtected;

if (parentNativeView && childNativeView) {
if (typeof atIndex !== 'number' || atIndex >= parentNativeView.subviews.count) {
parentNativeView.addSubview(childNativeView);
} else {
parentNativeView.insertSubviewAtIndex(childNativeView, atIndex);
}
IOSHelper.insertNativeSubview(parentNativeView, childNativeView, atIndex);

// If the child has an outer shadow layer, ensure it is attached under the child's layer
if (childNativeView.outerShadowContainerLayer && !childNativeView.outerShadowContainerLayer.superlayer) {
Expand Down
6 changes: 1 addition & 5 deletions packages/core/ui/page/index.ios.ts
Original file line number Diff line number Diff line change
Expand Up @@ -548,11 +548,7 @@ export class Page extends PageBase {
}

if (nativeParent && nativeChild) {
if (typeof atIndex !== 'number' || atIndex >= nativeParent.subviews.count) {
nativeParent.addSubview(nativeChild);
} else {
nativeParent.insertSubviewAtIndex(nativeChild, atIndex);
}
IOSHelper.insertNativeSubview(nativeParent, nativeChild, atIndex);

return true;
}
Expand Down
Loading