I was trying to figure how to work with the iPhone X notch but I can't understand how to make the home bar not activate when a user swipes from the bottom.
I used the following code as suggested in this post and it does work for the top swipe but it doesn't work for the bottom swipe.
Am I doing something wrong?
override func preferredScreenEdgesDeferringSystemGestures() -> UIRectEdge {
return .all
}
This bit works perfectly. Swipe twice or "fast" to show the home bar.
override var prefersStatusBarHidden: Bool {
if #available(iOS 11.0, *) {
return super.prefersStatusBarHidden
} else {
return true
}
}
override func preferredScreenEdgesDeferringSystemGestures() -> UIRectEdge {
return [.all]
}
A question i have is--Will apple allow it if and when I send it in my code when i submit my app to the app store?
Related
I have a view controller that takes up the whole screen from top to bottom. I would like to hide the home bar indicator on the bottom of the screen on iPhone X devices.
How can I do this in iOS 11?
You should override prefersHomeIndicatorAutoHidden in your view controller to achieve that:
override var prefersHomeIndicatorAutoHidden: Bool {
return true
}
There is another alternative. If you are looking for the behavior where the indicator dims, then when the user swipes up it activates, and when they swipe up again the home action is invoked (I.E., two swipes are needed to invoke), then the answer is here: iPhone X home indicator behavior. The short of it is to override on your UIViewController:
override var preferredScreenEdgesDeferringSystemGestures: UIRectEdge {
return UIRectEdge.bottom
}
prefersHomeIndicatorAutoHidden only hides the indicator, but will not suppress the gesture.
And you will get what you want (If I understand your comments correctly - your question and the selected answer seem to imply the other answer).
If your window?.rootViewController is a UITabBarController or UINavigationController, just inherit it and add two function as follows,
override var prefersHomeIndicatorAutoHidden: Bool {
return true
}
//#available(iOS 11, *)
override var childViewControllerForHomeIndicatorAutoHidden: UIViewController? {
return nil
}
Implement -(BOOL)prefersHomeIndicatorAutoHidden in your UIViewController and return YES.
Read more https://developer.apple.com/documentation/uikit/uiviewcontroller/2887510-prefershomeindicatorautohidden.
I tried to set it and return true only when I am in full-screen :
override var prefersHomeIndicatorAutoHidden: Bool { isNavigationBarAndTabBarHidden }
but it doesn't seem to work... isNavigationBarAndTabBarHidden is a custom variable tied to my fullscreen extension.
Edit: We need to call setNeedsUpdateOfHomeIndicatorAutoHidden every time we update prefersHomeIndicatorAutoHidden's value.
var isNavigationBarAndTabBarHidden = false {
didSet {
setNeedsUpdateOfHomeIndicatorAutoHidden()
}
}
override func prefersHomeIndicatorAutoHidden() -> Bool {
return true
}
I suppose you can add this method in your AppDelegate for hide home indicator on all of your ViewControllers.
I got a iPad-designed app using a SplitViewController showing two views, one with a contacts list and another with details of this contact. The SplitView works well on iPad but has some problems on iPhones.
There is a UIControl that take all the size of the Master View, that check if there is any .touchDown interaction by the user and some methods called to enable or disable this UIControl depending if we are on editing contact mode or not allowing user to interact with the screen or not :
private var disableInteractionClosure: (()->())?
private lazy var interactionOverlay: UIControl = {
let c = UIControl()
c.autoresizingMask = [.FlexibleHeight, .FlexibleWidth]
c.addTarget(self, action: "interactionOverlayAction", forControlEvents: .TouchDown)
return c
}()
func disableInteractionWhileEditingWithClosure(callback: ()->()) {
if disableInteractionClosure == nil {
disableInteractionClosure = callback
/* display control overlay */
interactionOverlay.frame = navigationController!.view.bounds
navigationController!.view.addSubview(interactionOverlay)
}
}
func interactionOverlayAction() {
disableInteractionClosure!()
}
func enableInteraction() {
disableInteractionClosure = nil
interactionOverlay.removeFromSuperview()
}
Basically the UIControl is used to block user from switching between contact while user is editing another contact/ creating a new one by blocking interaction with the contact list and if changes have been made on the editing/creating view it fires a method that shows a pop up saying "modifications have been made do you want to continue without saving ? cancel or continue " :
func cancel() {
self.view.endEditing(true)
let closure: ()->() = {
self.layoutView.willResign()
self.delegate?.tabDetailsControllerDidCancel(self)
}
if layoutView.hasChanges() {
MKAlertViewController().instantaneousAlert(title: "Modification apportées", message: "Êtes-vous sur de vouloir continuer sans sauvegarder les modifications ?", confirmButtonTitle: "Oui", cancelButtonTitle: "Annuler", confirmed: { () -> Void in
closure()
}, canceled: { () -> Void in
})
} else {
closure()
}
}
It works fine on iPad because the UIControl is only above the Master View and is enabled when in editing mode on the Detail View (iPad 3D Debugging view), so the pop up shows only when manually cancelling the editing/creating or when trying to change contact while editing/creating, but as the splitView don't work the same on iPads and iPhones and it appears that on iPhone the Master View is placed above the Detail View, the UIControl is also above (iPhone 3D Debugging view), and it causes to block interactions on all the screen and wherever I click the cancel pop-up is shown.
Can you guys explain me a way to enable/show this UIControl only when the MasterView is showing and not everywhere. Thanks.
I ended up using the viewWillDisappear on the detail view :
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)
if self.isMovingFromParentViewController() || self.isBeingDismissed() {
if editingMode {
shared.contactsListController.disableInteractionWhileEditingWithClosure({ (_) in
self.tabDetailsController.cancel()
})
shared.contactsListController.disableToolbar()
} else {
shared.contactsListController.enableInteraction()
shared.contactsListController.enableToolbar()
}
self.navigationController?.toolbar.alpha = 1
}
}
and modifying the disableInteractionWhileEditingWithClosure method on the master view:
func disableInteractionWhileEditingWithClosure(callback: ()->()) {
if disableInteractionClosure == nil {
disableInteractionClosure = callback
/* display control overlay */
interactionOverlay.frame = navigationController!.view.bounds
view.addSubview(interactionOverlay) // this line
}
}
and it works ! :)
I have a scroll view with 2 views in it: a UIImagePicker (Snapchat-style camera view), and a UITableView.
The scroll view is inside of a navigation controller that the main viewController pushes to. I want the status bar and everything on it (time, battery, wifi, etc.) to be hidden on the camera view, but when you scroll to the right to the tableView, the status bar contents show back up, whether they do some kind of cool stretch animation as you scroll (would be awesome to figure that out) or any other solution possible.
Hopefully I worded this well enough for you to understand.
Solution I found (More of a workaround)
declare a boolean called hidden.
Then I overrode these methods:
func scrollViewDidScroll(scrollView: UIScrollView){
let xOffset = scrollView.contentOffset.x;
if(xOffset > scrollView.contentSize.width/4)
{
if hidden == true {
print("\nShow status bar\n")
hidden = false
UIView.animateWithDuration(0.3, animations: {
self.setNeedsStatusBarAppearanceUpdate()
})
}
} else
{
print("\nHide Status Bar\n")
hidden = true
UIView.animateWithDuration(0.2, animations: {
self.setNeedsStatusBarAppearanceUpdate()
})
}
}
override func preferredStatusBarUpdateAnimation() -> UIStatusBarAnimation {
if hidden == false {
return UIStatusBarAnimation.Fade
} else {
return UIStatusBarAnimation.Slide
}
}
override func prefersStatusBarHidden() -> Bool {
print("\nstatus Bar Changed to hidden = \(hidden)\n")
return hidden
}
It fades the status bar in once you've at least scrolled half way, and slides the status bar back up once you've gone back half way again.
Have you tried calling
UIApplication.sharedApplication().setStatusBarHidden(hidden: Bool, withAnimation: UIStatusBarAnimation)
In appropriate timing (which is not recommended because you'll have to set it back to what it was when leaving the view, just so you know)
Or the override method
override func prefersStatusBarHidden() -> Bool {
code
}
in your controller?
I'm making an app which supports landscape orientations only when playing a video. Otherwise, all the scenes support only portrait orientation. I've checked portrait, landscape left and right in project settings. I've written the following code in the ViewControllers where I want to restrict to only portrait.
override func viewWillAppear(animated: Bool) {
let value = UIInterfaceOrientation.Portrait.rawValue
UIDevice.currentDevice().setValue(value, forKey: "orientation")
}
override func shouldAutorotate() -> Bool {
return false
}
override func supportedInterfaceOrientations() -> Int {
return UIInterfaceOrientation.Portrait.rawValue
}
Still, when I press (Command + right or left arrow), the simulator rotates. I don't have a device, so I have to test it in simulator.
Please help! Thank you!
It's the parent navigation controller that decide if its content should rotate or note.
You will need to override UINavigationController and had something like this
override func shouldAutorotate() -> Bool {
return self.topViewController.shouldAutorotae()
}
override func supportedInterfaceOrientations() -> Int {
return self.topViewController.shouldAutorotae()
}
Two things to check. First, check your info.plist file and make sure that you have portrait deleted for both iPhone and iPad. I was having the same issue and it was because I hadn't deleted the iPad "Portrait" options. See here:
Second, the below code can help. However, it may not work if you have a navigation controller.
override var shouldAutorotate: Bool {
return true
}
I want to lock the device's orientation in a specific ViewController to only portrait but none of the available solutions are working. It is embedded in a UINavigationController.
I deployed it to iOS7 and in the general settings, both landscape and portrait are checked because I want it to rotate for other VCs other than the specific one.
extension UINavigationController {
public override func shouldAutorotate() -> Bool {
return visibleViewController.shouldAutorotate()
}
public override func supportedInterfaceOrientations() -> Int {
return visibleViewController.supportedInterfaceOrientations()
}
}
this portion of the code is in my ViewController class
override func shouldAutorotate() -> Bool {
return false
}
override func supportedInterfaceOrientations() -> Int {
return Int(UIInterfaceOrientation.Portrait.rawValue)
}
"In iOS 8, all rotation-related methods are deprecated" (See documentation on UIViewController). This could be why it isn't working for you.
I am thinking that Apple has done this to keep the users' experience more consistent throughout the app.
I figured out the problem which is since I am using SWRevealViewController, the parent VC overrides the subview's rotate settings.
Thus I just subclass it and adjust the autorotate and orientation settings as intended.