From d8bc349b37375dd1663f90c2792e13171aa9bc0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Sundstr=C3=B6m?= Date: Tue, 27 Jun 2023 22:45:45 +0200 Subject: [PATCH] IOS7: Resize main frame depending on keyboard size and orientation It's important that the main frame, displaying the OpenGL rendered graphics, has the proper dimensions and depending on the device orientation. It's also important that the frame is not covered by the iOS keyboard. This commit calculates the frame size depening on the orientation and the keyboard status. The keyboard knows its parent view and can resize it when the keyboard becomes visible or hidden. There are multiple scenarios where the frame size is changed. - When the keyboard is hidden/shown which can be automatically change depending on the device orientation - If the system demands the keyboard to get visible or hidden - When rotating the device - When suspending/resuming the application There can also be combination of the scenarios above, e.g. if suspending the application in landscape mode and resume it in portrait mode. A lot of effort has been put into testing different scenarios to verify that the screen size becomes correct. However there might be some scenario which has not been covered. --- backends/platform/ios7/ios7_keyboard.mm | 60 ++++++++++++++++++++++++- backends/platform/ios7/ios7_video.h | 4 +- backends/platform/ios7/ios7_video.mm | 44 +++++++++++++++--- 3 files changed, 99 insertions(+), 9 deletions(-) diff --git a/backends/platform/ios7/ios7_keyboard.mm b/backends/platform/ios7/ios7_keyboard.mm index 51e84b2bd87..989c313f3cb 100644 --- a/backends/platform/ios7/ios7_keyboard.mm +++ b/backends/platform/ios7/ios7_keyboard.mm @@ -393,16 +393,72 @@ @end -@implementation SoftKeyboard +@implementation SoftKeyboard { + BOOL _keyboardVisible; +} + +#if TARGET_OS_IOS +- (void)resizeParentFrame:(NSNotification*)notification keyboardDidShow:(BOOL)didShow +{ + NSDictionary* userInfo = [notification userInfo]; + CGRect keyboardFrame = [[userInfo objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue]; + keyboardFrame = [self.superview convertRect:keyboardFrame fromView:nil]; + + // Base the new frame size on the current parent frame size + CGRect newFrame = self.superview.frame; + newFrame.size.height += (keyboardFrame.size.height) * (didShow ? -1 : 1); + + // Resize with a fancy animation + NSNumber *rate = notification.userInfo[UIKeyboardAnimationDurationUserInfoKey]; + [UIView animateWithDuration:rate.floatValue animations:^{ + self.superview.frame = newFrame; + }]; +} + +- (void)keyboardDidShow:(NSNotification*)notification +{ + // NotificationCenter might notify multiple times + // when keyboard did show because the accessoryView + // affect the keyboard height. However since we use + // UIKeyboardFrameEndUserInfoKey to get the keyboard + // it will always have the same value. Make sure to + // only handle one notification. + if (!_keyboardVisible) { + [self resizeParentFrame:notification keyboardDidShow:YES]; + _keyboardVisible = YES; + } +} + +- (void)keyboardDidHide:(NSNotification*)notification +{ + // NotificationCenter will only call this once + // when keyboard did hide. + [self resizeParentFrame:notification keyboardDidShow:NO]; + _keyboardVisible = NO; +} +#endif - (id)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; + +#if TARGET_OS_IOS + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(keyboardDidShow:) + name:UIKeyboardDidShowNotification + object:nil]; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(keyboardDidHide:) + name:UIKeyboardDidHideNotification + object:nil]; +#endif + inputDelegate = nil; inputView = [[TextInputHandler alloc] initWithKeyboard:self]; inputView.delegate = self; inputView.clearsOnBeginEditing = YES; [inputView layoutIfNeeded]; - + _keyboardVisible = NO; return self; } diff --git a/backends/platform/ios7/ios7_video.h b/backends/platform/ios7/ios7_video.h index 8ca321395a1..807460fb954 100644 --- a/backends/platform/ios7/ios7_video.h +++ b/backends/platform/ios7/ios7_video.h @@ -48,7 +48,9 @@ uint getSizeNextPOT(uint size); NSLock *_eventLock; SoftKeyboard *_keyboardView; Common::List _controllers; - +#if TARGET_OS_IOS + UIInterfaceOrientation _currentOrientation; +#endif UIBackgroundTaskIdentifier _backgroundSaveStateTask; EAGLContext *_mainContext; diff --git a/backends/platform/ios7/ios7_video.mm b/backends/platform/ios7/ios7_video.mm index e501f5cdfae..32b9e7a4215 100644 --- a/backends/platform/ios7/ios7_video.mm +++ b/backends/platform/ios7/ios7_video.mm @@ -290,6 +290,9 @@ bool iOS7_fetchEvent(InternalEvent *event) { self = [super initWithFrame: frame]; _backgroundSaveStateTask = UIBackgroundTaskInvalid; +#if TARGET_OS_IOS + _currentOrientation = UIInterfaceOrientationUnknown; +#endif [self setupGestureRecognizers]; @@ -325,7 +328,12 @@ bool iOS7_fetchEvent(InternalEvent *event) { [_keyboardView setInputDelegate:self]; [self addSubview:[_keyboardView inputView]]; [self addSubview: _keyboardView]; - [self showKeyboard]; + if ([self getScreenHeight] > [self getScreenWidth]) { + // This will make sure the keyboard is shown in portrait + // mode on start of the application since the orientation + // handling is performed before the view finish its setup + [self showKeyboard]; + } } [self adjustViewFrameForSafeArea]; @@ -344,7 +352,7 @@ bool iOS7_fetchEvent(InternalEvent *event) { #ifdef __IPHONE_11_0 if ( @available(iOS 11, tvOS 11, *) ) { CGRect screenSize = [[UIScreen mainScreen] bounds]; - CGRect newFrame = screenSize; + CGRect newFrame = self.frame; #if TARGET_OS_IOS UIEdgeInsets inset = [[[UIApplication sharedApplication] keyWindow] safeAreaInsets]; UIInterfaceOrientation orientation = UIInterfaceOrientationUnknown; @@ -353,14 +361,32 @@ bool iOS7_fetchEvent(InternalEvent *event) { } else { orientation = [[UIApplication sharedApplication] statusBarOrientation]; } + + // The code below adjust the screen size according to what Apple calls + // the "safe area". It also cover the cases when the software keyboard + // is visible and has changed the frame height so the keyboard doesn't + // cover any part of the game screen. + if (orientation != _currentOrientation) { + // If the orientation is changed the keyboard will hide or show + // depending on the current orientation. The frame size must be + // "reset" again to "full" screen size dimension. The keyboard + // will then calculate the approriate height when becoming visible. + newFrame = screenSize; + _currentOrientation = orientation; + } + // Make sure the frame height (either full screen or resized due to + // visible keyboard) is within the safe area. + CGFloat safeAreaHeight = screenSize.size.height - inset.top; + CGFloat height = newFrame.size.height < safeAreaHeight ? newFrame.size.height : safeAreaHeight; + if ( orientation == UIInterfaceOrientationPortrait ) { - newFrame = CGRectMake(screenSize.origin.x, screenSize.origin.y + inset.top, screenSize.size.width, screenSize.size.height - inset.top); + newFrame = CGRectMake(screenSize.origin.x, screenSize.origin.y + inset.top, screenSize.size.width, height); } else if ( orientation == UIInterfaceOrientationPortraitUpsideDown ) { - newFrame = CGRectMake(screenSize.origin.x, screenSize.origin.y, screenSize.size.width, screenSize.size.height - inset.top); + newFrame = CGRectMake(screenSize.origin.x, screenSize.origin.y, screenSize.size.width, height); } else if ( orientation == UIInterfaceOrientationLandscapeLeft ) { - newFrame = CGRectMake(screenSize.origin.x, screenSize.origin.y, screenSize.size.width - inset.right, screenSize.size.height); + newFrame = CGRectMake(screenSize.origin.x, screenSize.origin.y, screenSize.size.width - inset.right, height); } else if ( orientation == UIInterfaceOrientationLandscapeRight ) { - newFrame = CGRectMake(screenSize.origin.x + inset.left, screenSize.origin.y, screenSize.size.width - inset.left, screenSize.size.height); + newFrame = CGRectMake(screenSize.origin.x + inset.left, screenSize.origin.y, screenSize.size.width - inset.left, height); } #endif self.frame = newFrame; @@ -371,6 +397,8 @@ bool iOS7_fetchEvent(InternalEvent *event) { #ifdef __IPHONE_11_0 // This delegate method is called when the safe area of the view changes -(void)safeAreaInsetsDidChange { + [super safeAreaInsetsDidChange]; + [self adjustViewFrameForSafeArea]; } #endif @@ -606,6 +634,10 @@ bool iOS7_fetchEvent(InternalEvent *event) { } - (void)applicationSuspend { + // Make sure to hide the keyboard when suspended. Else the frame + // sizing might become incorrect because the NotificationCenter + // sends keyboard notifications on resume. + [self hideKeyboard]; [self addEvent:InternalEvent(kInputApplicationSuspended, 0, 0)]; }