Skip to content

Commit 5b5c2a0

Browse files
committed
feat: refactor iOS navigation session attachment and detachment
improve view cleanup logic
1 parent 816c328 commit 5b5c2a0

8 files changed

Lines changed: 168 additions & 25 deletions

File tree

ios/react-native-navigation-sdk/BaseCarSceneDelegate.m

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ - (void)sceneDidBecomeActive:(UIScene *)scene {
6969
- (void)attachSession {
7070
if ([NavModule sharedInstance] != nil && [[NavModule sharedInstance] hasSession] &&
7171
!_sessionAttached) {
72-
[self.navViewController attachToNavigationSession:[[NavModule sharedInstance] getSession]];
72+
[self.navViewController attachToNavigationSessionIfNeeded];
7373
[self.navViewController setHeaderEnabled:NO];
7474
[self.navViewController setRecenterButtonEnabled:NO];
7575
[self.navViewController setFooterEnabled:NO];

ios/react-native-navigation-sdk/NavModule.m

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ - (void)initializeSession {
110110
[self->_session.roadSnappedLocationProvider addListener:self];
111111

112112
NavViewModule *navViewModule = [NavViewModule sharedInstance];
113-
[navViewModule attachViewsToNavigationSession:_session];
113+
[navViewModule attachViewsToNavigationSession];
114114

115115
[self onNavigationReady];
116116
}
@@ -175,6 +175,7 @@ - (void)showTermsAndConditionsDialog {
175175
}
176176

177177
if (self->_session.navigator != nil) {
178+
[self->_session.navigator removeListener:self];
178179
[self->_session.navigator clearDestinations];
179180
self->_session.navigator.guidanceActive = NO;
180181
self->_session.navigator.sendsBackgroundNotifications = NO;
@@ -186,6 +187,10 @@ - (void)showTermsAndConditionsDialog {
186187

187188
self->_session.started = NO;
188189
self->_session = nil;
190+
191+
NavViewModule *navViewModule = [NavViewModule sharedInstance];
192+
[navViewModule navigationSessionDestroyed];
193+
189194
if (_navigationSessionDisposedCallback) {
190195
_navigationSessionDisposedCallback();
191196
}

ios/react-native-navigation-sdk/NavView.m

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -151,10 +151,24 @@ - (void)handleGroundOverlayClick:(GMSGroundOverlay *)groundOverlay {
151151

152152
- (void)willMoveToSuperview:(UIView *)newSuperview {
153153
[super willMoveToSuperview:newSuperview];
154-
if (newSuperview == nil && _viewController && self.cleanupBlock) {
155-
// As newSuperview is nil, the view is being removed from its superview,
156-
// call the cleanup block provided by the view manager
154+
if (newSuperview == nil && _viewController) {
155+
// View is being removed from hierarchy, cleanup the view controller
156+
[self cleanup];
157+
}
158+
}
159+
160+
- (void)dealloc {
161+
[self cleanup];
162+
}
163+
164+
- (void)cleanup {
165+
if (self.cleanupBlock) {
157166
self.cleanupBlock(self.reactTag);
167+
self.cleanupBlock = nil;
168+
}
169+
170+
if (_viewController) {
171+
[_viewController.view removeFromSuperview];
158172
_viewController = nil;
159173
}
160174
}

ios/react-native-navigation-sdk/NavViewController.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,8 @@ typedef void (^OnArrayResult)(NSArray *_Nullable result);
8181
- (void)removePolygon:(NSString *)polygonId;
8282
- (void)removeCircle:(NSString *)circleId;
8383
- (void)removeGroundOverlay:(NSString *)overlayId;
84-
- (BOOL)attachToNavigationSession:(GMSNavigationSession *)session;
84+
- (BOOL)attachToNavigationSessionIfNeeded;
85+
- (void)navigationSessionDestroyed;
8586
- (void)onPromptVisibilityChange:(BOOL)visible;
8687
- (void)setTravelMode:(GMSNavigationTravelMode)travelMode;
8788
- (void)setPadding:(UIEdgeInsets)insets;

ios/react-native-navigation-sdk/NavViewController.m

Lines changed: 118 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ @implementation NavViewController {
3636
NSString *_mapId;
3737
MapViewType _mapViewType;
3838
id<INavigationViewCallback> _viewCallbacks;
39+
BOOL _isSessionAttached;
40+
NSNumber *_isNavigationUIEnabled;
3941
}
4042

4143
- (instancetype)initWithMapViewType:(MapViewType)mapViewType {
@@ -62,17 +64,126 @@ - (void)loadView {
6264
}
6365

6466
_mapView = [[GMSMapView alloc] initWithOptions:options];
65-
67+
if (_mapViewType == NAVIGATION) {
68+
_mapView.navigationEnabled = YES;
69+
}
6670
self.view = _mapView;
6771
_mapView.delegate = self;
72+
}
73+
74+
- (void)viewDidLoad {
75+
[super viewDidLoad];
76+
77+
[_viewCallbacks handleMapReady];
78+
}
79+
80+
- (void)viewDidLayoutSubviews {
81+
[super viewDidLayoutSubviews];
82+
83+
[self attachToNavigationSessionIfNeeded];
84+
}
85+
86+
- (BOOL)attachToNavigationSessionIfNeeded {
87+
// Only attach if view has proper type, state and dimensions (not zero size)
88+
if (_mapViewType != NAVIGATION || _isSessionAttached || _mapView.bounds.size.width == 0 ||
89+
_mapView.bounds.size.height == 0) {
90+
return NO;
91+
}
6892

6993
NavModule *navModule = [NavModule sharedInstance];
70-
if (navModule != nil && [navModule hasSession]) {
71-
[self attachToNavigationSession:[navModule getSession]];
94+
if (navModule == nil || ![navModule hasSession]) {
95+
return NO;
7296
}
73-
dispatch_async(dispatch_get_main_queue(), ^{
74-
[_viewCallbacks handleMapReady];
75-
});
97+
98+
GMSNavigationSession *session = [navModule getSession];
99+
if (_mapView == nil || session == nil) {
100+
return NO;
101+
}
102+
103+
// `enableNavigationWithSession` returns false if TOS is not accepted.
104+
// This should not be possible in normal usage as the NavModule ensures TOS acceptance before
105+
// navigation session creation.
106+
BOOL result = [_mapView enableNavigationWithSession:session];
107+
108+
if (result) {
109+
_mapView.navigationUIDelegate = self;
110+
[self applyStylingOptions];
111+
112+
[self restoreNavigationUIState];
113+
114+
_isSessionAttached = YES;
115+
116+
[self forceInvalidateView];
117+
}
118+
119+
return result;
120+
}
121+
122+
- (void)forceInvalidateView {
123+
if (_mapView) {
124+
// Defer to next run loop to ensure view is properly sized
125+
dispatch_async(dispatch_get_main_queue(), ^{
126+
if (self->_mapView) {
127+
[self->_mapView setNeedsLayout];
128+
[self->_mapView.layer setNeedsDisplay];
129+
}
130+
});
131+
}
132+
}
133+
134+
- (void)restoreNavigationUIState {
135+
if (_mapView) {
136+
if (_isNavigationUIEnabled != nil) {
137+
_mapView.navigationEnabled = [_isNavigationUIEnabled boolValue];
138+
} else {
139+
_mapView.navigationEnabled = _mapViewType == NAVIGATION;
140+
}
141+
}
142+
}
143+
144+
- (void)navigationSessionDestroyed {
145+
_isSessionAttached = NO;
146+
if (_mapView) {
147+
_mapView.navigationUIDelegate = nil;
148+
_mapView.navigationEnabled = NO;
149+
}
150+
}
151+
152+
- (void)cleanup {
153+
_isSessionAttached = NO;
154+
155+
// Remove all delegates to break retain cycles
156+
if (_mapView) {
157+
_mapView.delegate = nil;
158+
_mapView.navigationUIDelegate = nil;
159+
_mapView.navigationEnabled = NO;
160+
[_mapView clear];
161+
162+
[_mapView removeFromSuperview];
163+
_mapView = nil;
164+
}
165+
166+
// Clear local arrays and set to nil
167+
[_markerList removeAllObjects];
168+
[_polylineList removeAllObjects];
169+
[_polygonList removeAllObjects];
170+
[_circleList removeAllObjects];
171+
[_groundOverlayList removeAllObjects];
172+
173+
_markerList = nil;
174+
_polylineList = nil;
175+
_polygonList = nil;
176+
_circleList = nil;
177+
_groundOverlayList = nil;
178+
179+
// Clear callbacks
180+
_viewCallbacks = nil;
181+
}
182+
183+
- (void)dealloc {
184+
[self cleanup];
185+
[self.view removeFromSuperview];
186+
self.view = nil;
76187
}
77188

78189
- (void)mapViewDidTapRecenterButton:(GMSMapView *)mapView {
@@ -195,6 +306,7 @@ - (void)setNavigationUIEnabled:(BOOL)isEnabled {
195306
if (_mapViewType != NAVIGATION) {
196307
return;
197308
}
309+
_isNavigationUIEnabled = @(isEnabled);
198310
_mapView.navigationEnabled = isEnabled;
199311
}
200312

@@ -327,16 +439,6 @@ - (void)setSpeedLimitIconEnabled:(BOOL)isEnabled {
327439

328440
#pragma mark - View Controller functions
329441

330-
- (BOOL)attachToNavigationSession:(GMSNavigationSession *)session {
331-
if (_mapViewType != NAVIGATION) {
332-
return NO;
333-
}
334-
BOOL result = [_mapView enableNavigationWithSession:session];
335-
_mapView.navigationUIDelegate = self;
336-
[self applyStylingOptions];
337-
return result;
338-
}
339-
340442
- (void)onPromptVisibilityChange:(BOOL)isVisible {
341443
[_viewCallbacks handlePromptVisibilityChanged:isVisible];
342444
}

ios/react-native-navigation-sdk/NavViewModule.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ NS_ASSUME_NONNULL_BEGIN
2222
@interface NavViewModule : NSObject <RCTBridgeModule>
2323
@property(nonatomic, strong) NSMapTable<NSNumber *, NavViewController *> *viewControllers;
2424

25-
- (void)attachViewsToNavigationSession:(GMSNavigationSession *)session;
25+
- (void)attachViewsToNavigationSession;
26+
- (void)navigationSessionDestroyed;
2627
- (void)informPromptVisibilityChange:(BOOL)visible;
2728
- (void)setTravelMode:(GMSNavigationTravelMode)travelMode;
2829

ios/react-native-navigation-sdk/NavViewModule.m

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,15 @@ + (instancetype)sharedInstance {
3737
return sharedInstance;
3838
}
3939

40-
- (void)attachViewsToNavigationSession:(GMSNavigationSession *)session {
40+
- (void)attachViewsToNavigationSession {
4141
for (NavViewController *viewController in self.viewControllers.objectEnumerator) {
42-
[viewController attachToNavigationSession:session];
42+
[viewController attachToNavigationSessionIfNeeded];
43+
}
44+
}
45+
46+
- (void)navigationSessionDestroyed {
47+
for (NavViewController *viewController in self.viewControllers.objectEnumerator) {
48+
[viewController navigationSessionDestroyed];
4349
}
4450
}
4551

ios/react-native-navigation-sdk/RCTNavViewManager.m

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,12 @@ - (instancetype)init {
4242
return self;
4343
}
4444

45+
- (void)dealloc {
46+
@synchronized(_viewControllers) {
47+
[_viewControllers removeAllObjects];
48+
}
49+
}
50+
4551
- (UIView *)view {
4652
NavView *navView = [[NavView alloc] init];
4753

@@ -74,9 +80,17 @@ - (void)registerViewController:(NavViewController *)viewController forTag:(NSNum
7480

7581
- (void)unregisterViewControllerForTag:(NSNumber *)reactTag {
7682
@synchronized(_viewControllers) {
83+
NavViewController *viewController = [_viewControllers objectForKey:reactTag];
84+
if (viewController) {
85+
// Explicitly cleanup the view controller to release resources
86+
dispatch_async(dispatch_get_main_queue(), ^{
87+
[viewController.view removeFromSuperview];
88+
});
89+
}
7790
[_viewControllers removeObjectForKey:reactTag];
7891
}
7992
}
93+
8094
RCT_EXPORT_VIEW_PROPERTY(onRecenterButtonClick, RCTDirectEventBlock);
8195
RCT_EXPORT_VIEW_PROPERTY(onMapReady, RCTDirectEventBlock);
8296
RCT_EXPORT_VIEW_PROPERTY(onMapClick, RCTDirectEventBlock);

0 commit comments

Comments
 (0)