FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

fix(ios): lay out a Frame or TabView hosted in a layout from its layo… · NativeScript/NativeScript@1c40591 · GitHub

Repository navigation

Commit 1c40591

Browse files
committed
fix(ios): lay out a Frame or TabView hosted in a layout from its layout slot
A Frame or TabView placed directly in a NativeScript view (TabView as Page content, Frame in a GridLayout cell) was never positioned by core, so its controller's view kept the UIScreen.main bounds UIKit created it with. On iPhone Duo that is the outer display, so a TabView launched on the inner display rendered outer-sized. Sizing to the container's bounds also ignored the view's own cell, e.g. a Frame in the second row of a grid covered the first. When hosted in a NativeScript view, both now take the regular View frame path and extend the edges that sit on the container's safe area out to its bounds, since the controller insets its own content. Frames and TabViews under a UIKit container (window root, TabView items, modals) are unchanged.
1 parent 37d8d33 commit 1c40591

5 files changed

Lines changed: 92 additions & 22 deletions

File tree

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,36 @@
1+
import { Frame, GridLayout, Label, TabView, View } from '@nativescript/core';
2+
import * as TKUnit from '../../tk-unit';
3+
import * as helper from '../../ui-helper';
4+
15
export * from './frame-tests-common';
6+
7+
function assertHostedBelowHeader(hosted: View) {
8+
const header = new Label();
9+
header.text = 'Header';
10+
const grid = new GridLayout();
11+
grid.rows = '50, *';
12+
GridLayout.setRow(hosted, 1);
13+
grid.addChild(header);
14+
grid.addChild(hosted);
15+
16+
helper.buildUIAndRunTest(grid, () => {
17+
helper.waitUntilLayoutReady(hosted);
18+
19+
const headerFrame = (header.nativeViewProtected as UIView).frame;
20+
const hostedFrame = (hosted.nativeViewProtected as UIView).frame;
21+
const container = (grid.nativeViewProtected as UIView).bounds;
22+
23+
TKUnit.assertAreClose(hostedFrame.origin.y, headerFrame.origin.y + headerFrame.size.height, 0.5, 'top');
24+
TKUnit.assertAreClose(hostedFrame.origin.y + hostedFrame.size.height, container.size.height, 0.5, 'bottom');
25+
TKUnit.assertAreClose(hostedFrame.origin.x, 0, 0.5, 'left');
26+
TKUnit.assertAreClose(hostedFrame.size.width, container.size.width, 0.5, 'width');
27+
});
28+
}
29+
30+
export function test_frame_hosted_in_layout_fills_its_cell() {
31+
assertHostedBelowHeader(new Frame());
32+
}
33+
34+
export function test_tab_view_hosted_in_layout_fills_its_cell() {
35+
assertHostedBelowHeader(new TabView());
36+
}

‎packages/core/ui/core/view/view-helper/index.d.ts‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,16 @@ export namespace IOSHelper {
7676
* view, or hides them all for `none`. No-op before iOS 26.
7777
*/
7878
export function setScrollEdgeEffect(scrollView: any /* UIScrollView */, effect: CoreTypes.ScrollEdgeEffectType): void;
79+
/**
80+
* Whether the view's native view is a direct subview of its nearest NativeScript ancestor's
81+
* native view, rather than placed by a UIKit container controller.
82+
*/
83+
export function isHostedInView(view: View): boolean;
84+
/**
85+
* Moves the frame's edges that lie on the container's safe-area edges out to the container's
86+
* bounds, for controller-backed views that inset their own content.
87+
*/
88+
export function extendUnderContainerSafeArea(nativeView: any /* UIView */, frame: any /* CGRect */): any; /* CGRect */
7989
export function invalidateStatusBarAppearance(controller?: any /* UIViewController */, reason?: string): void;
8090
export function updateAutoAdjustScrollInsets(controller: any /* UIViewController */, owner: View): void;
8191
export function updateConstraints(controller: any /* UIViewController */, owner: View): void;

‎packages/core/ui/core/view/view-helper/index.ios.ts‎

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,40 @@ export class IOSHelper {
329329
return view;
330330
}
331331

332+
static isHostedInView(view: View): boolean {
333+
const container = view.nativeViewProtected?.superview;
334+
if (!container) {
335+
return false;
336+
}
337+
338+
let parent = view.parent as View;
339+
while (parent && !parent.nativeViewProtected) {
340+
parent = parent.parent as View;
341+
}
342+
343+
return container === parent?.nativeViewProtected;
344+
}
345+
346+
static extendUnderContainerSafeArea(nativeView: UIView, frame: CGRect): CGRect {
347+
const container = nativeView?.superview;
348+
if (!container) {
349+
return null;
350+
}
351+
352+
// Edges laid out against the container's safe area move out to its bounds; a controller's view insets its own content.
353+
const safeArea = container.safeAreaLayoutGuide.layoutFrame;
354+
const bounds = container.bounds;
355+
const tolerance = 0.5;
356+
const left = frame.origin.x <= safeArea.origin.x + tolerance ? bounds.origin.x : frame.origin.x;
357+
const top = frame.origin.y <= safeArea.origin.y + tolerance ? bounds.origin.y : frame.origin.y;
358+
const frameRight = frame.origin.x + frame.size.width;
359+
const frameBottom = frame.origin.y + frame.size.height;
360+
const right = frameRight >= safeArea.origin.x + safeArea.size.width - tolerance ? bounds.origin.x + bounds.size.width : frameRight;
361+
const bottom = frameBottom >= safeArea.origin.y + safeArea.size.height - tolerance ? bounds.origin.y + bounds.size.height : frameBottom;
362+
363+
return CGRectMake(left, top, right - left, bottom - top);
364+
}
365+
332366
static invalidateStatusBarAppearance(controller?: UIViewController, reason = ''): void {
333367
try {
334368
if (!controller) {

‎packages/core/ui/frame/index.ios.ts‎

Lines changed: 6 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -361,27 +361,15 @@ export class Frame extends FrameBase {
361361
this.setMeasuredDimension(widthAndState, heightAndState);
362362
}
363363

364-
public layoutNativeView(left: number, top: number, right: number, bottom: number): void {
365-
const nativeView = this.nativeViewProtected;
366-
const container = nativeView?.superview;
367-
if (!container) {
368-
return;
369-
}
370-
371-
let parent = this.parent as View;
372-
while (parent && !parent.nativeViewProtected) {
373-
parent = parent.parent as View;
374-
}
375-
376-
// UIKit never sizes a nested Frame; it keeps UIScreen.main bounds.
377-
const isNestedInView = container === parent?.nativeViewProtected;
378-
if (isNestedInView && !CGRectEqualToRect(nativeView.frame, container.bounds)) {
379-
nativeView.frame = container.bounds;
364+
public _setNativeViewFrame(nativeView: UIView, frame: CGRect) {
365+
// UIKit lays out the controller's view inside a container controller, but not inside a NativeScript view.
366+
if (IOSHelper.isHostedInView(this)) {
367+
super._setNativeViewFrame(nativeView, frame);
380368
}
381369
}
382370

383-
public _setNativeViewFrame(nativeView: UIView, frame: CGRect) {
384-
//
371+
protected applySafeAreaInsets(frame: CGRect): CGRect {
372+
return IOSHelper.extendUnderContainerSafeArea(this.nativeViewProtected, frame);
385373
}
386374

387375
// Emits an event whenever the UINavigationController shows a view controller.

‎packages/core/ui/tab-view/index.ios.ts‎

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -390,12 +390,15 @@ export class TabView extends TabViewBase {
390390
return this._ios;
391391
}
392392

393-
public layoutNativeView(left: number, top: number, right: number, bottom: number): void {
394-
//
393+
public _setNativeViewFrame(nativeView: UIView, frame: CGRect) {
394+
// UIKit lays out the controller's view inside a container controller, but not inside a NativeScript view.
395+
if (IOSHelper.isHostedInView(this)) {
396+
super._setNativeViewFrame(nativeView, frame);
397+
}
395398
}
396399

397-
public _setNativeViewFrame(nativeView: UIView, frame: CGRect) {
398-
//
400+
protected applySafeAreaInsets(frame: CGRect): CGRect {
401+
return IOSHelper.extendUnderContainerSafeArea(this.nativeViewProtected, frame);
399402
}
400403

401404
public onSelectedIndexChanged(oldIndex: number, newIndex: number): void {

0 commit comments

Comments
 (0)

Back | FazBrowse Home | New Git URL