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.
Related
In UIScrollView, there is a feature named "Keyboard Dismiss interactively"
By using such option, this enables me to implement the following drag down to hide keyboard
However, the keyboard dismiss operation only kick start, when the UIScrollView drag action touches keyboard edge.
What I would like to achieve is, the keyboard dismiss operation kick start, when the UIScrollView drag operation touches the bottom toolbar edge.
What I wish to achieve (Same as WhatsApp)
As you can see from the video, the keyboard dismiss operation will kick start, when the drag operation touches the bottom bar edge, even before touching keyboard edge.
May I know, what technique WhatsApp is using, to achieve such behavior?
Side note
You may notice our bottom toolbar does move along with keyboard. This is because there is a bottom constraint for bottom toolbar's bottom with Safe Area's bottom.
We adjust the bottom constraint's constant value, by installing a gesture recognizer in global Window. This is the code snippet to achieve such technique.
#objc private func didPan(_ sender: UIPanGestureRecognizer){
if keyboardHeight > 0 {
let mainScrollView = editable.mainScrollView
let isScrolling = (mainScrollView.isDragging || mainScrollView.isDecelerating)
if isScrolling {
if let mainScrollViewGlobalOrigin = mainScrollView.globalOrigin {
let point = sender.location(in: sender.view!)
// Take safe area into consideration, like iPhone 12 Pro Max.
let key = UIWindow.key
let bottomSafeArea = key?.safeAreaInsets.bottom ?? 0
let dy = point.y - (
mainScrollViewGlobalOrigin.y +
mainScrollView.frame.height +
toolbarHeightLayoutConstraint.constant +
bottomSafeArea -
bottomLayoutConstraint.constant -
self.keyboardHeight
)
if dy > 0 {
bottomLayoutConstraint.constant = -(keyboardHeight - dy)
}
}
}
}
}
The reason that WhatsApp behaves like this is that their view is considered to be part of the keyboard, so when the swipe gesture reaches their custom view it will begin interactive dismissal.
To achieve this yourself all you need to do is provide the toolbar view as the inputAccessoryView for your view controller. You won't need the constraints for positioning as the keyboard window would then control your toolbar's position.
There is also inputAccessoryViewController for the times where your toolbar may not be a UIView, but instead an entire UIViewController.
The views in either of these properties will only be visible when the keyboard is visible, so to get around that you'll still want to put it into your view hierarchy, but remove/add it based on becoming/resigning first responder.
EDIT: Also, you should be using UIApplication.keyboardDidChangeFrameNotification to detect when the keyboard changes size/position/etc and allow you to adjust insets/positions of views appropriately. In modern iOS there are plenty of ways the keyboard can change size while open, and observing that notification is the correct way to handle the keyboard size.
What I want to to: I want to drag down the whole view of a viewController to dismiss to the parent viewController using a pan gesture recognizer.
The Problem: When I drag the view down, the navigationBar decreases its height and does not look good. When the view returns to its original position, the navigationBar returns to the default size. I want the navigationBar to stay at its size. I also tried to use the new large titles and some other properties of the navigationController/-bar, but that did not solve it.
Note: Everything worked fine already before iOS 11.
My code:
override func viewDidLoad() {
super.viewDidLoad()
let panGesture = UIPanGestureRecognizer(target: self, action: #selector(dragViewDown(_:)))
navigationController!.view.addGestureRecognizer(panGesture)
}
#IBAction func dragViewDown(_ gesture: UIPanGestureRecognizer) {
if let dragView = gesture.view {
let translation = gesture.translation(in: dragView)
dragView.center.y = (dragView.center.y + translation.y)
gesture.setTranslation(CGPoint.zero, in: dragView)
}
}
This test project only has one viewController and does not provide the dismissal, but the problem is the same as in my working project.
I also uploaded the project to GitHub: https://github.com/maddinK7/navitationBar-pull-down-problem
Does anyone have an idea how to solve this? Thanks in advance.
I want the navigationBar to stay at its size
It is staying at its size. If you check the navigation bar's bounds size height before, during, and after the drag, you will see that it remains the same (probably 44) at all times. What's changing is the drawing extension that causes the drawing of the nav bar to extend up behind the status bar. It can't do that when you pull the whole thing away from the top of the screen, because it is not at the top next to the status bar any more. iOS 11 is more strict about the way it performs this drawing extension, probably because it has to do it in a special way on the iPhone X.
So, let's make sure you're doing this correctly:
Make sure that the navigation bar has a top constraint pinned to the safe area layout guide's top, with a constant of zero.
Make sure that the navigation bar has a delegate that returns .topAttached from position(forBar:).
If you are doing both those things and it doesn't help, you'll have to implement this in some other way entirely. Making the view directly draggable like this, without a custom parent view controller, was always dubious.
When UINavigationController attached top, system will add safe area top margin in the navigation background.
(NOTICE: Background margin will not changed when offset value is between 1 and 0)
So you have to handle attached/detached top event by handle gesture offset to change the right offset and content insets.
You can try the solution in my lib example. ;)
My example include UITableViewController in the UINavigationController, so it will relatively complex.
https://github.com/showang/OverlayModalViewController
I'm building an iOS 8 app that makes use of the new hidesBarsOnSwipe property on UINavgitationController to hide the nav bar while scrolling. At the same time that the nav bar hides, I'm also programmatically hiding the tab bar. On top of the tab bar, there is a text field which lets users comment on a post (much like Facebook). When the tab bar is hidden (by moving it downward and off the screen), the text field is moved down as well, so that it now sits at the bottom of the screen and so that there's no gap between the bottom of the screen and the text field.
So, things look great. But, turns out that the text field doesn't respond to touch events when it moves to the bottom of the screen. I did some digging and it appears that the reason is because the text field is outside of its superview (the view controller's view), and so touch events will not be sent to the text field.
So I think I've figured out why the issue is occurring, but I haven't yet figured out how to fix it. I've tried messing with hitTest:withEvent: and pointInside:withEvent: but didn't have any luck. Anyone have any solutions?
EDIT: Here is some code to make the question clearer (hopefully). When the nav controller's barHideOnSwipeGestureRecognizer is called, I am running the following code:
- (void)barHideSwipeGestureActivated:(UIPanGestureRecognizer*)gesture
{
[self animateTabBarUpOrDown:self.navigationController.navigationBar.frame.origin.y >= 0 completion:nil];
}
The method above is the following:
- (void)animateTabBarUpOrDown:(BOOL)up completion:(void (^)(void))completionBlock
{
if(!self.animatingTabBar && self.tabbarIsUp != up)
{
self.animatingTabBar = YES;
//to animate the tabbar up, reset the comments bottom constraint to 0 and set the tab bar frame to it's original place
//to animate the tabbar down, move its frame down by its height. set comments bottom constraint to the negative value of that height.
[UIView animateWithDuration:kTabBarAnimationDuration animations:^{
UITabBar *tabBar = self.tabBarController.tabBar;
if(up)
{
tabBar.frame = CGRectMake(tabBar.frame.origin.x, tabBar.frame.origin.y - tabBar.frame.size.height, tabBar.frame.size.width, tabBar.frame.size.height);
self.addCommentViewToBottomConstraint.constant = 0.0f;
}
else
{
tabBar.frame = CGRectMake(tabBar.frame.origin.x, tabBar.frame.origin.y + tabBar.frame.size.height, tabBar.frame.size.width, tabBar.frame.size.height);
self.addCommentViewToBottomConstraint.constant = -tabBar.frame.size.height;
}
} completion:^(BOOL finished) {
self.tabbarIsUp = up;
self.animatingTabBar = NO;
if(completionBlock)
{
completionBlock();
}
}];
}
}
Ok, finally found a solution for this one. I haphazardly messed around with changing the bounds of my view controller's view, but that was too hacky and ultimately didn't accomplish what I wanted it to.
What I ended up doing was changing my view controller's edgesForExtendedLayout property to be equal to UIRectEdgeAll which basically says that the view should take up the entire screen, and extend above top bars / below bottom bars.
I had to hack around a little bit with changing auto layout constraints on my text field so that it appeared in the right place at the right time, but overall, the solution was changing edgesForExtendedLayout to be UIRectEdgeAll - this makes the view take up the entire screen, so the text field is now still in the super view even when it animates downward, thus, allowing it to still receive touch events.
How can I check if the keyboard is blocking my view?
I have UIButton that is in the centre of the screen. I also have a UITextfield that makes the keyboard appears. When I run the app on iPhone 4 the keyboard block the button, but on other models, it doesn't. I have a method that scrolls up the view when the keyboard appears. But I only want to scroll up in case the view is blocked. I can check the model of the iPhone and then decide if to scroll or not, but I thought checking if the Button is blocked would be better. How can I do it?
I solved this problem in an app like so:
Embed the UI for that screen in a UIScrollView, but configure it so that scrolling is not enabled (scrollEnabled property from code, checkbox if you're using a storyboard).
When the keyboard notification is received, get the frame from the button, then call scrollRectToVisible:animated: on the scroll view. It'll move the content the minimum amount necessary to make the button visible, which will be not at all if the screen is big enough.
Using the UIKeyboardWillShowNotification, you can get the height of the keyboard like this:
NSValue *keyboardRect = [notification.userInfo objectForKey:UIKeyboardFrameBeginUserInfoKey];
CGFloat keyboardHeight = MIN(keyboardRect.CGRectValue.size.width, keyboardRect.CGRectValue.size.height);
The get the relevant "lowest" point of your button. (like buttonMaxY = CGRectGetMaxY(yourButton.frame)).
Use the scroll methods you have implemented, but scroll only if necessary: keyboardHeight+buttonMaxY > the height of the screen.
When keyboard is about to appear, a UIKeyboardWillShowNotification notification is posted with the frame of the keyboard. You can calculate and see if the text field frame intersects with the keyboard frame, and scroll.
See documentation on keyboard notifications here.
Following Leo Natan's explanation, you can try something like:
func keyboardWillShow(notification: Notification) {
guard let keyboardFrame = notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as? CGRect,
let viewFrame = myView.frame else {
log.error("I cannot calculate keyboard & view frame")
return
}
if keyboardFrame.intersects(viewFrame) {
// view covered by Keyboard
}
}
pay attention to use UIKeyboardFrameEndUserInfoKey, you can check this link for further details.
I have an iPad app in which I show an UIPopover view. I want to set the background of the popover depending on its position on the screen. Is it possible to get the exact position of the UIPopover relative to the screen?
try this
if ([myPopOverController isPopoverVisible]) {
CGPoint myPopOverPosition = [myPopOverController.contentViewController.view
convertPoint:myPopOverController.contentViewController.view.frame.origin
toView:mainTopView];
//If you set toView to nil then it will consider as the window or you could specify as yourappDelegate.window
}