1- import { SplitViewBase , displayModeProperty , splitBehaviorProperty , preferredPrimaryColumnWidthFractionProperty , preferredSupplementaryColumnWidthFractionProperty , preferredInspectorColumnWidthFractionProperty } from './split-view-common' ;
1+ import { SplitViewBase , displayModeProperty , splitBehaviorProperty , preferredPrimaryColumnWidthFractionProperty , preferredSupplementaryColumnWidthFractionProperty , preferredInspectorColumnWidthFractionProperty , navigationBarTintColorProperty } from './split-view-common' ;
22import { View } from '../core/view' ;
33import { layout } from '../../utils' ;
44import { SDK_VERSION } from '../../utils/constants' ;
55import { FrameBase } from '../frame/frame-common' ;
6+ import { Color } from '../../color' ;
67import type { SplitRole } from '.' ;
78
89@NativeClass
910class UISplitViewControllerDelegateImpl extends NSObject implements UISplitViewControllerDelegate {
1011 public static ObjCProtocols = [ UISplitViewControllerDelegate ] ;
1112 static ObjCExposedMethods = {
1213 toggleInspector : { returns : interop . types . void , params : [ ] } ,
14+ togglePrimary : { returns : interop . types . void , params : [ ] } ,
1315 } ;
1416 private _owner : WeakRef < SplitView > ;
1517
@@ -29,19 +31,44 @@ class UISplitViewControllerDelegateImpl extends NSObject implements UISplitViewC
2931 }
3032
3133 splitViewControllerDidCollapse ( svc : UISplitViewController ) : void {
32- // Can be used to notify owner if needed
34+ const owner = this . _owner . deref ( ) ;
35+ if ( owner ) {
36+ owner . _invalidateAllChildLayouts ( ) ;
37+ }
3338 }
3439
3540 splitViewControllerDidExpand ( svc : UISplitViewController ) : void {
36- // Can be used to notify owner if needed
41+ const owner = this . _owner . deref ( ) ;
42+ if ( owner ) {
43+ owner . _invalidateAllChildLayouts ( ) ;
44+ }
3745 }
3846
3947 splitViewControllerDidHideColumn ( svc : UISplitViewController , column : UISplitViewControllerColumn ) : void {
40- // Can be used to notify owner if needed
48+ const owner = this . _owner . deref ( ) ;
49+ if ( owner ) {
50+ if ( column === UISplitViewControllerColumn . Primary ) {
51+ owner . primaryButtonAttached = false ;
52+ } else if ( column === UISplitViewControllerColumn . Inspector ) {
53+ owner . inspectorButtonAttached = false ;
54+ }
55+ owner . _invalidateAllChildLayouts ( ) ;
56+ }
4157 }
4258
4359 splitViewControllerDidShowColumn ( svc : UISplitViewController , column : UISplitViewControllerColumn ) : void {
44- // Can be used to notify owner if needed
60+ const owner = this . _owner . deref ( ) ;
61+ if ( owner ) {
62+ owner . _invalidateAllChildLayouts ( ) ;
63+ // Re-attach buttons when columns are shown (e.g., via gesture)
64+ if ( column === UISplitViewControllerColumn . Primary ) {
65+ owner . primaryShowing = true ;
66+ setTimeout ( ( ) => owner . attachPrimaryButton ( ) , 100 ) ;
67+ } else if ( column === UISplitViewControllerColumn . Inspector ) {
68+ owner . inspectorShowing = true ;
69+ setTimeout ( ( ) => owner . attachInspectorButton ( ) , 100 ) ;
70+ }
71+ }
4572 }
4673
4774 splitViewControllerDisplayModeForExpandingToProposedDisplayMode ( svc : UISplitViewController , proposedDisplayMode : UISplitViewControllerDisplayMode ) : UISplitViewControllerDisplayMode {
@@ -62,6 +89,19 @@ class UISplitViewControllerDelegateImpl extends NSObject implements UISplitViewC
6289 }
6390 }
6491 }
92+
93+ togglePrimary ( ) : void {
94+ const owner = this . _owner . deref ( ) ;
95+ if ( owner ) {
96+ if ( owner . primaryShowing ) {
97+ owner . hidePrimary ( ) ;
98+ owner . primaryShowing = false ;
99+ } else {
100+ owner . showPrimary ( ) ;
101+ owner . primaryShowing = true ;
102+ }
103+ }
104+ }
65105}
66106
67107export class SplitView extends SplitViewBase {
@@ -75,7 +115,10 @@ export class SplitView extends SplitViewBase {
75115 // Keep role -> controller
76116 private _controllers = new Map < SplitRole , UIViewController | UINavigationController > ( ) ;
77117 private _children = new Map < SplitRole , View > ( ) ;
118+ primaryButtonAttached = false ;
119+ inspectorButtonAttached = false ;
78120 inspectorShowing = false ;
121+ primaryShowing = true ;
79122
80123 constructor ( ) {
81124 super ( ) ;
@@ -88,6 +131,11 @@ export class SplitView extends SplitViewBase {
88131 this . viewController . delegate = this . _delegate ;
89132 this . viewController . presentsWithGesture = true ;
90133
134+ // Disable automatic display mode button - we manage our own buttons
135+ if ( this . viewController . displayModeButtonVisibility !== undefined ) {
136+ this . viewController . displayModeButtonVisibility = UISplitViewControllerDisplayModeButtonVisibility . Never ;
137+ }
138+
91139 // Apply initial preferences
92140 this . _applyPreferences ( ) ;
93141
@@ -205,26 +253,34 @@ export class SplitView extends SplitViewBase {
205253 showPrimary ( ) : void {
206254 if ( ! this . viewController ) return ;
207255 this . viewController . showColumn ( UISplitViewControllerColumn . Primary ) ;
256+ this . _invalidateAllChildLayouts ( ) ;
257+ // Attach button after a short delay to ensure the column is visible
258+ setTimeout ( ( ) => this . attachPrimaryButton ( ) , 100 ) ;
208259 }
209260
210261 hidePrimary ( ) : void {
211262 if ( ! this . viewController ) return ;
212263 this . viewController . hideColumn ( UISplitViewControllerColumn . Primary ) ;
264+ this . primaryButtonAttached = false ;
265+ this . _invalidateAllChildLayouts ( ) ;
213266 }
214267
215268 showSecondary ( ) : void {
216269 if ( ! this . viewController ) return ;
217270 this . viewController . showColumn ( UISplitViewControllerColumn . Secondary ) ;
271+ this . _invalidateAllChildLayouts ( ) ;
218272 }
219273
220274 hideSecondary ( ) : void {
221275 if ( ! this . viewController ) return ;
222276 this . viewController . hideColumn ( UISplitViewControllerColumn . Secondary ) ;
277+ this . _invalidateAllChildLayouts ( ) ;
223278 }
224279
225280 showSupplementary ( ) : void {
226281 if ( ! this . viewController ) return ;
227282 this . viewController . showColumn ( UISplitViewControllerColumn . Supplementary ) ;
283+ this . _invalidateAllChildLayouts ( ) ;
228284 }
229285
230286 showInspector ( ) : void {
@@ -233,14 +289,19 @@ export class SplitView extends SplitViewBase {
233289 if ( this . viewController . preferredInspectorColumnWidthFraction !== undefined ) {
234290 this . viewController . showColumn ( UISplitViewControllerColumn . Inspector ) ;
235291 this . notifyInspectorChange ( true ) ;
292+ this . _invalidateAllChildLayouts ( ) ;
293+ // Attach button after a short delay to ensure the column is visible
294+ setTimeout ( ( ) => this . attachInspectorButton ( ) , 100 ) ;
236295 }
237296 }
238297
239298 hideInspector ( ) : void {
240299 if ( ! this . viewController ) return ;
241300 if ( this . viewController . preferredInspectorColumnWidthFraction !== undefined ) {
242301 this . viewController . hideColumn ( UISplitViewControllerColumn . Inspector ) ;
302+ this . inspectorButtonAttached = false ;
243303 this . notifyInspectorChange ( false ) ;
304+ this . _invalidateAllChildLayouts ( ) ;
244305 }
245306 }
246307
@@ -253,6 +314,46 @@ export class SplitView extends SplitViewBase {
253314 } ) ;
254315 }
255316
317+ invalidateChildLayouts ( delay : number = 0 ) : void {
318+ const refreshLayouts = ( ) => {
319+ for ( const [ role , child ] of this . _children . entries ( ) ) {
320+ if ( child && child . requestLayout ) {
321+ child . requestLayout ( ) ;
322+ }
323+ // Also trigger layout on the native view
324+ if ( child && child . nativeViewProtected ) {
325+ child . nativeViewProtected . setNeedsLayout ( ) ;
326+ child . nativeViewProtected . layoutIfNeeded ( ) ;
327+ }
328+ // If it's a Frame, also request layout on its current page
329+ if ( ( child as FrameBase ) ?. currentPage ?. requestLayout ) {
330+ ( child as FrameBase ) . currentPage . requestLayout ( ) ;
331+ if ( ( child as FrameBase ) . currentPage . nativeViewProtected ) {
332+ ( child as FrameBase ) . currentPage . nativeViewProtected . setNeedsLayout ( ) ;
333+ ( child as FrameBase ) . currentPage . nativeViewProtected . layoutIfNeeded ( ) ;
334+ }
335+ }
336+ }
337+ // Also request layout on the SplitView itself
338+ this . requestLayout ( ) ;
339+ if ( this . nativeViewProtected ) {
340+ this . nativeViewProtected . setNeedsLayout ( ) ;
341+ this . nativeViewProtected . layoutIfNeeded ( ) ;
342+ }
343+ } ;
344+
345+ if ( delay > 0 ) {
346+ setTimeout ( refreshLayouts , delay ) ;
347+ } else {
348+ refreshLayouts ( ) ;
349+ }
350+ }
351+
352+ _invalidateAllChildLayouts ( ) : void {
353+ // Call after animation typically completes to ensure native layout pass has finished
354+ this . invalidateChildLayouts ( 350 ) ;
355+ }
356+
256357 private _resolveRoleForChild ( child : SplitViewBase , atIndex : number ) : SplitRole {
257358 const explicit = SplitViewBase . getRole ( child ) ;
258359 if ( explicit ) {
@@ -318,7 +419,52 @@ export class SplitView extends SplitViewBase {
318419 targetVC . navigationItem . leftItemsSupplementBackButton = true ;
319420 }
320421
321- private _attachInspectorButton ( ) : void {
422+ attachPrimaryButton ( ) : void {
423+ if ( this . primaryButtonAttached ) {
424+ return ;
425+ }
426+
427+ const primary = this . _controllers . get ( 'primary' ) ;
428+ if ( ! ( primary instanceof UINavigationController ) ) {
429+ return ;
430+ }
431+
432+ const targetVC = primary . topViewController ;
433+ if ( ! targetVC ) {
434+ // Subscribe to Frame's navigatedTo event to know when the first page is shown
435+ const frameChild = this . _children . get ( 'primary' ) as any ;
436+ if ( frameChild && frameChild . on && ! frameChild . _splitViewPrimaryNavigatedHandler ) {
437+ frameChild . _splitViewPrimaryNavigatedHandler = ( ) => {
438+ // Use setTimeout to ensure the navigation controller's topViewController is updated
439+ setTimeout ( ( ) => this . attachPrimaryButton ( ) , 0 ) ;
440+ } ;
441+ frameChild . on ( FrameBase . navigatedToEvent , frameChild . _splitViewPrimaryNavigatedHandler ) ;
442+ }
443+ return ;
444+ }
445+
446+ // Set the navigation bar tint color for the primary column
447+ if ( primary . navigationBar && this . navigationBarTintColor ) {
448+ primary . navigationBar . tintColor = this . navigationBarTintColor . ios ;
449+ }
450+
451+ // Add a sidebar button to primary column matching the inspector style
452+ const cfg = UIImageSymbolConfiguration . configurationWithPointSizeWeightScale ( 18 , UIImageSymbolWeight . Regular , UIImageSymbolScale . Medium ) ;
453+ const image = UIImage . systemImageNamedWithConfiguration ( 'sidebar.leading' , cfg ) ;
454+ const item = UIBarButtonItem . alloc ( ) . initWithImageStyleTargetAction ( image , UIBarButtonItemStyle . Plain , this . _delegate , 'togglePrimary' ) ;
455+ if ( this . navigationBarTintColor ) {
456+ item . tintColor = this . navigationBarTintColor . ios ;
457+ }
458+ // Use rightBarButtonItems array to ensure we control exactly what's shown
459+ targetVC . navigationItem . rightBarButtonItems = NSArray . arrayWithObject ( item ) ;
460+ this . primaryButtonAttached = true ;
461+ }
462+
463+ attachInspectorButton ( ) : void {
464+ if ( this . inspectorButtonAttached ) {
465+ return ;
466+ }
467+
322468 const inspector = this . _controllers . get ( 'inspector' ) ;
323469 if ( ! ( inspector instanceof UINavigationController ) ) {
324470 return ;
@@ -331,23 +477,27 @@ export class SplitView extends SplitViewBase {
331477 if ( frameChild && frameChild . on && ! frameChild . _splitViewNavigatedHandler ) {
332478 frameChild . _splitViewNavigatedHandler = ( ) => {
333479 // Use setTimeout to ensure the navigation controller's topViewController is updated
334- setTimeout ( ( ) => this . _attachInspectorButton ( ) , 0 ) ;
480+ setTimeout ( ( ) => this . attachInspectorButton ( ) , 100 ) ;
335481 } ;
336482 frameChild . on ( FrameBase . navigatedToEvent , frameChild . _splitViewNavigatedHandler ) ;
337483 }
338484 return ;
339485 }
340486
341- // Avoid duplicates
342- if ( targetVC . navigationItem . rightBarButtonItem ) {
343- return ;
487+ // Set the navigation bar tint color for the inspector column
488+ if ( inspector . navigationBar && this . navigationBarTintColor ) {
489+ inspector . navigationBar . tintColor = this . navigationBarTintColor . ios ;
344490 }
345491
346- // TODO: can provide properties to customize this
492+ // Note: could provide properties to customize this
347493 const cfg = UIImageSymbolConfiguration . configurationWithPointSizeWeightScale ( 18 , UIImageSymbolWeight . Regular , UIImageSymbolScale . Medium ) ;
348494 const image = UIImage . systemImageNamedWithConfiguration ( 'sidebar.trailing' , cfg ) ;
349495 const item = UIBarButtonItem . alloc ( ) . initWithImageStyleTargetAction ( image , UIBarButtonItemStyle . Plain , this . _delegate , 'toggleInspector' ) ;
496+ if ( this . navigationBarTintColor ) {
497+ item . tintColor = this . navigationBarTintColor . ios ;
498+ }
350499 targetVC . navigationItem . rightBarButtonItem = item ;
500+ this . inspectorButtonAttached = true ;
351501 }
352502
353503 private _syncControllers ( ) : void {
@@ -428,17 +578,17 @@ export class SplitView extends SplitViewBase {
428578 const supplementary = this . _controllers . get ( 'supplementary' ) ;
429579 const inspector = this . _controllers . get ( 'inspector' ) ;
430580
431- // Attach displayModeButtonItem to the secondary column's first page
432581 if ( secondary instanceof UINavigationController ) {
433582 this . _attachSecondaryDisplayModeButton ( ) ;
434583 }
584+ if ( primary instanceof UINavigationController ) {
585+ this . attachPrimaryButton ( ) ;
586+ }
435587 if ( supplementary ) {
436588 this . showSupplementary ( ) ;
437589 }
438590 if ( inspector ) {
439591 this . showInspector ( ) ;
440- // Ensure the inspector column gets its toggle button as soon as the first page is shown
441- this . _attachInspectorButton ( ) ;
442592 }
443593
444594 // Width fractions
@@ -480,4 +630,8 @@ export class SplitView extends SplitViewBase {
480630 [ preferredInspectorColumnWidthFractionProperty . setNative ] ( value : number ) {
481631 this . _applyPreferences ( ) ;
482632 }
633+
634+ [ navigationBarTintColorProperty . setNative ] ( value : Color ) {
635+ this . _applyPreferences ( ) ;
636+ }
483637}
0 commit comments