Skip to content

Commit ff335a5

Browse files
shai-almogclaude
andauthored
iOS: orientation-validate canvas size on iPad foreground (#4767) (#4853)
The previous attempt (a09b916) silenced viewWillTransitionToSize: during background but then called updateCanvas: synchronously inside cn1ApplicationWillEnterForeground. On iPad with UIScene, view.bounds can still be in the snapshot orientation at that moment, so updateCanvas republished the swapped dimensions through screenSizeChanged -- the same behavior the issue reports as a transient wrong size between stop and start. This change combines two safeguards: it restores the next-runloop deferral around the foreground updateCanvas call so UIKit has a tick to settle the bounds, and it cross-checks the sampled size against the windowScene's interfaceOrientation, swapping the dimensions when they contradict it. The orientation is authoritative for what the user actually sees, so this catches the failure even when the deferred call still fires before UIKit has fully restored bounds. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 25dbe3e commit ff335a5

2 files changed

Lines changed: 43 additions & 7 deletions

File tree

Ports/iOSPort/nativeSources/CodenameOne_GLAppDelegate.m

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,16 @@ - (void)cn1ApplicationWillEnterForeground
248248
com_codename1_impl_ios_IOSImplementation_applicationWillEnterForeground__(CN1_THREAD_GET_STATE_PASS_SINGLE_ARG);
249249
CodenameOne_GLViewController* vc = [CodenameOne_GLViewController instance];
250250
if (vc != nil) {
251-
[vc updateCanvas:YES];
251+
// Defer to the next runloop so UIKit can settle the view bounds
252+
// after the snapshot rotation. updateCanvas itself also
253+
// orientation-validates the bounds for an extra safety net under
254+
// UIScene on iPad (issue #4767).
255+
dispatch_async(dispatch_get_main_queue(), ^{
256+
CodenameOne_GLViewController* deferredVc = [CodenameOne_GLViewController instance];
257+
if (deferredVc != nil) {
258+
[deferredVc updateCanvas:YES];
259+
}
260+
});
252261
}
253262
}
254263

Ports/iOSPort/nativeSources/CodenameOne_GLViewController.m

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,33 @@ static void updateDisplayMetricsFromView(UIView *view) {
167167
displayWidth = (int)(view.bounds.size.width * scaleValue);
168168
displayHeight = (int)(view.bounds.size.height * scaleValue);
169169
}
170+
171+
// On iPad with UIScene, view.bounds (and even window.bounds) can transiently
172+
// be in the snapshot orientation between sceneDidEnterBackground and the
173+
// first post-foreground layout pass. Cross-check against the windowScene's
174+
// interfaceOrientation -- which reflects what the user actually sees -- and
175+
// swap the dimensions if they contradict it. Without this, sampling bounds
176+
// during the foreground transition publishes a swapped-dimension
177+
// screenSizeChanged event between stop and start (issue #4767).
178+
static CGSize cn1OrientationCorrectSize(UIView *view) {
179+
CGSize size = view.bounds.size;
180+
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000
181+
if (@available(iOS 13.0, *)) {
182+
UIWindowScene *scene = view.window.windowScene;
183+
if (scene != nil) {
184+
UIInterfaceOrientation o = scene.interfaceOrientation;
185+
if (o != UIInterfaceOrientationUnknown) {
186+
BOOL shouldBePortrait = UIInterfaceOrientationIsPortrait(o);
187+
BOOL isPortrait = size.height >= size.width;
188+
if (shouldBePortrait != isPortrait) {
189+
return CGSizeMake(size.height, size.width);
190+
}
191+
}
192+
}
193+
}
194+
#endif
195+
return size;
196+
}
170197
BOOL forceSlideUpField;
171198
static UIScrollView *cn1StatusBarTapProxy = nil;
172199

@@ -2020,22 +2047,22 @@ -(void)updateCanvas:(BOOL)animated {
20202047
if(touchesArray != nil) {
20212048
[touchesArray removeAllObjects];
20222049
}
2023-
int currentWidth = (int)self.view.bounds.size.width * scaleValue;
2050+
CGSize size = cn1OrientationCorrectSize(self.view);
20242051
//if(currentWidth != displayWidth) {
20252052
// Note: While it may be tempting to only update the frame buffer if the size has changed,
2026-
// doing that causes a bug whereby the app may paint with the wrong dimensions
2053+
// doing that causes a bug whereby the app may paint with the wrong dimensions
20272054
// when opening from the background on iPad with multitasking enabled.
20282055
// https://github.com/codenameone/CodenameOne/issues/2819
20292056
// This may be caused by the fact the getDisplayWidthImpl() and getDisplayHeightImpl() update
20302057
// the display width/height each time to match the view, without performing other resizing
20312058
// details, so it is possible that the size change event still needs to be sent
20322059
// even if the display width already matches the value we're given here.
2033-
[[self eaglView] updateFrameBufferSize:(int)self.view.bounds.size.width h:(int)self.view.bounds.size.height];
2034-
updateDisplayMetricsFromView(self.view);
2035-
displayWidth = currentWidth;
2060+
[[self eaglView] updateFrameBufferSize:(int)size.width h:(int)size.height];
2061+
displayWidth = (int)size.width * scaleValue;
2062+
displayHeight = (int)size.height * scaleValue;
20362063
screenSizeChanged(displayWidth, displayHeight);
20372064
//}
2038-
2065+
20392066
}
20402067

20412068
- (BOOL)canBecomeFirstResponder {

0 commit comments

Comments
 (0)