Landscape apps made with Xcode 8.3 are letterboxed on the iPhone X, and the home bar is partially disabled, meaning that the user must swipe up to "wake it" then swipe up again to exit the app. It's this second functionality that I want to implement while taking full advantage of the screen size, so how do I replicate that feature using Xcode 9?
If I set the view controller's prefersHomeIndicatorAutoHidden() to return true, the home bar temporarily disappears, but every time the user touches the screen it comes back (a bit jarring), but it still only takes a single swipe to exit the app. I have not been able to find any other options to do what I want, but clearly it should be possible since it automatically happens for older apps.
Suggestions?
[Note crossposted on Apple Developer Forum]
This behavior is set up by implementing preferredScreenEdgesDeferringSystemGestures in a UIViewController, as follows:
- (UIRectEdge)
preferredScreenEdgesDeferringSystemGestures
{
// prevent home bar from interfering
return (UIRectEdgeTop | UIRectEdgeBottom);
}
Once this is done, the Home Bar should not be auto-hidden:
- (BOOL)
prefersHomeIndicatorAutoHidden
{
return NO;
}
In addition, in a convenient place (such as viewDidAppear:), you need to notify the system that these properties have changed:
- (void)
viewDidAppear:(BOOL) isAnimated
{
[super viewDidAppear:isAnimated];
if (#available(iOS 11.0, *))
{
[self setNeedsUpdateOfHomeIndicatorAutoHidden];
[self setNeedsUpdateOfScreenEdgesDeferringSystemGestures];
}
}
Related
The new iPhone X has gotten rid of the home button and replaced it with a "home indicator" at the very bottom that allows the user to swipe up to get back to the home screen.
My question is, how do we detect if this home indicator is on the screen? I want to do something like:
if (!notfullScreen)
{
if (swipeBarExists)
{
viewHeight -= swipeBarHeight;
}
}
I checked in [UIApplication sharedApplication] and found nothing. Basically I don't really know what to call this thing and am having trouble finding an answer.
You can utilize safeAreaInsets.bottom, defined for UIView to get the amount of space you should inset your content to ensure it doesn’t underlap the home indicator (or other elements). Note that this value may change, for example, when you rotate to landscape on iPhone it shrinks. To be notified when this occurs, implement safeAreaInsetsDidChange in your view controller. You can also utilize the safeAreaLayoutGuide with Auto Layout.
So, if you have a full screen view, you could check like so:
override func viewSafeAreaInsetsDidChange() {
super.viewSafeAreaInsetsDidChange()
if view.safeAreaInsets.bottom > 0 {
//home indicator
} else {
//no home indicator
}
}
Note that there is no API to get the height of just the home indicator bar itself.
The rotation of the app works in three of four cases. If I hold the ipad not edgewise with the home button at the bottom, the app adjusts correctly. But if I rotate the ipad in the edgewise (home button on bottom) position, the app remains in the last orientation. Thus the portrait view works correctly but just if the home button is on the top. Any ideas?
Please, check the following answer
https://stackoverflow.com/a/14057998/2382237
According to it, You should override this methods:
- (BOOL)shouldAutorotate {
return YES;
}
- (NSUInteger)supportedInterfaceOrientations {
return (UIInterfaceOrientationMaskPortrait | UIInterfaceOrientationMaskPortraitUpsideDown);
}
Maybe it can help you to figure out what is wrong
I am trying to create a user interface enabling users to switch between the keyboard and other menus when using a chat application.
On a click of the textField bar I want to raise either the keyboard or a collection view.
The problem occurs when I click the 'menu' button. I want the textField bar to raise revealing my menu view. Then, on a click on the keyboard button, instantly switch to the keyboard, rather than having it slide up from the bottom. This means I need to have the keyboard already loaded and hidden but in the background of the app.
Currently though the earliest I am managing to assign a variable to the keyboard is in the keyboardDidShow function.
-(void) keyboardDidShow: (NSNotification *) notification {
// Get the window the keyboard is a subview of
_window = [UIApplication sharedApplication].windows.lastObject;
_keyboard = _window.subviews[0];
}
This means that after it has been loaded once I can hide and reveal it, but I don't want it visible when it is loading this first time.
To achieve this using alternate means I have tried adding my extra views as subviews of the UIWindow the keyboard is created in:
[_window addSubview:_menuView];
[_window addSubview:_gamesView];
[_window addSubview:_stickerView];
[self hideSpecificView];
Unfortunately I keep coming across the same problem, until I have loaded the keyboard once it needs to fully load before I can get a pointer to it to hide it.
Here is a picture of my toolBar incase I am not being clear:
On clicking the menu icon or the stickers icon I want the bar to raise with a collection view. If I then click the textfield, with these views visible, I want to hide the visible view to immediately show the keyboard behind.
I have also tried experimenting with keyboardWillShow but as the window hasn't been loaded in front our screen I can't get a pointer to the keyboard to hide it before it loads.
An example of what I am after can be found many chat apps (facebook messenger, LINE, Kakao Talk)
Any help would be greatly appreciated
Although the way I came up with isn't perfect it works almost perfectly so hopefully this might help people in the future. If anyone else has solved it differently please post as it would be interesting to know how you did it.
I started by adding a class variable to a UIWindow in my header file and then setting off a timer to ping just after the keyboard will show method finishes. After this method has finished the keyboard has been created, just, and so I allocate it and hide it.
-(void) keyboardWillShow: (NSNotification *) notification {
// More keyboard code
_window = [UIApplication sharedApplication].windows.lastObject;
[NSTimer scheduledTimerWithTimeInterval:0.01
target:self
selector:#selector(allocateKeyboard)
userInfo:nil
repeats:NO];
}
- (void)allocateKeyboard {
if (!_keyboard) {
_keyboard = _window.subviews[0];
}
_keyboard.hidden = YES;
[self setViewForButtonType];
}
I have already previously added my other views, hidden them and constrained them to the bottom of the main view, this means that when the keyboard rises they do too.
- (void)viewDidLoad {
[self.view addSubview:_menuView];
[self.view addSubview:_gamesView];
[self.view addSubview:_stickerView];
}
...
- (void)hideViews {
_keyboard.hidden = YES;
_menuView.hidden = YES;
_gamesView.hidden = YES;
_stickerView.hidden = YES;
}
When buttons get pressed I simple then unhide the view that I want to see and hide the rest of the views.
When I say that this method doesn't work perfectly it is because if you load view main view and then click a button before the keyboard has loaded for the first time then you get a quick glimpse of the keyboard before the view appears over the top. This though only happens the first time and only if they don't click in the text field first.
Anyway, I found this was the best way of making views look like they are in front of the keyboard. Obviously my code was a lot longer and more complex (too long for here) but this is the gist of the method I used to solve it. Comment if you have any queries and I hope this helps.
Previously as noted in a few questions:
How to detect iPad user tap on keyboard hide button?
detect iPad keyboard Hiding button
...the way to detect if the user hit the hide keyboard button on an iPad was to subscribe to the UIKeyboardWillHideNotification notification.
However, apparently from iOS 6 on, this notification has been sent in a number of places not just when the user hits the retract keyboard button. For example, that notification is sent when the iPad rotates even though the keyboard itself does not hide.
I have discovered an inelegant work around, which I will post here in case anyone else is struggling with this problem, but would much appreciate any elegant solutions people have found to this problem.
The current solution I have come up with for this problem is to note that the only time the keyboard actually retracts during this notification is if the user actually hits the retract button. In other cases there is nearly 0 delay between the UIKeyboardWillHideNotification and the UIKeyboardDidHideNotification.
static BOOL immediate;
- (void) checkImmediate {
if (immediate) return;
/* Do Stuff */
}
- (void) keyboardWillHide:(NSNotification*)note {
immediate = NO;
[self performSelector:#selector(checkImmediate) withObject:self afterDelay:.01 inModes:#[NSRunLoopCommonModes]];
}
- (void) keyboardDidHide:(NSNotification*)note {
immediate = YES;
}
I'm trying to fix a bug that involves UIView hitTest:withEvent: being called on my view when the touches are on the UIKeyboard, but only after the app has been in the background.
It was occurring in my app with a complex view hierarchy so I reproduced it in an app with only 2 views:
1 UIView 768x1024 (fullscreen)
1 UITextView 200x200 in the upper half of the fullscreen view
The behavior is as follows:
Tapping the textview causes the fullscreen view's hitTest method to fire, the textfield becomes first responder, and then the keyboard appears all as expected. Tapping on keyboard keys works fine.
Now dismiss the keyboard.
Send the app to the background.
Then resume the app.
Make the textview first responder again. Here's the trouble, now when tapping keys on the keyboard the fullscreen view's hitTest method is firing.
I'm seeing this on an iOS 5 iPad 2. Only on device though, never in the simulator. Any idea why hitTesting could get messed up in this way? Thanks.
The issue described above seems to be caused by the keyboard's UIWindow getting stuck in a bad state. Ensuring that the keyboard window's hidden property gets set to YES (even if it is already YES) fixes the problem for me. This can be done in your UIApplicationDelegate class:
- (void)applicationWillEnterForeground:(UIApplication *)application {
// The keyboard sometimes disables interaction when the app enters the
// background due to an iOS bug. This brings it back to normal.
for (UIWindow *testWindow in [UIApplication sharedApplication].windows) {
if (!testWindow.opaque && [NSStringFromClass(testWindow.class) hasPrefix:#"UIText"]) {
BOOL wasHidden = testWindow.hidden;
testWindow.hidden = YES;
if (!wasHidden) {
testWindow.hidden = NO;
}
break;
}
}
}
The class name of the keyboard window, at least in iOS 5 with a standard US keyboard, is UITextEffectsWindow. As usual it is not a good idea to rely on undocumented class names but in the case of an OS-specific bug it works for my purposes. There could be any number of windows, including the root application window, keyboard, alerts, and other windows that your app or other frameworks have added, so don't be too inspecific.
Got the same issue here. It does happen ONLY when I hit home and return to the app.
Does not happen in the first fresh run.
And it is related to iOS5 as well.
I got the same problem, and my work around is to listen to UIKeyboardDidShowNotification and UIKeyboardDidHideNotification, calculate the keyboard height using UIKeyboardFrameEndUserInfoKey, then in my hitTest:withEvent: method I would see whether the hit was on the keyboard "zone" or not.
Just to expand on the answer by #enzo-tran , this is what I ended up doing: I added a keyboardRect property to my UIView subclass, registered for UIKeyboardDidShowNotification and UIKeyboardDidHideNotification, and added:
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
if (CGRectContainsPoint([self keyboardRect], point)) {
// Ignore
} else {
...
}
}
- (void)keyboardDidShow:(NSNotification *)notif {
CGRect keyboardRect;
[[[notif userInfo] valueForKey:UIKeyboardFrameEndUserInfoKey] getValue:&keyboardRect];
keyboardRect = [self convertRect:keyboardRect fromView:nil];
[self setKeyboardRect:keyboardRect];
}
- (void)keyboardDidHide:(NSNotification *)notif {
[self setKeyboardRect:CGRectZero];
}