Is it possible to disable the dock that pops up in iOS?
This is my View Controller. Notice that it has a draggable view controller in the footer.
But when I try to pull it up quickly, the dock shows up:
Is there any way to disable it?
I think the closest you can get is iOS 11's preferredScreenEdgesDeferringSystemGestures(), which will show an indicator at the bottom but not pull up the dock on the first swipe. For example, in your view controller:
override func preferredScreenEdgesDeferringSystemGestures() -> UIRectEdge {
return [.bottom]
}
In my experience it still eats the swipe gesture, but it still gives the user a second chance to hit the right target.
On iOS <11 however, this behavior can only be obtained by hiding the status bar.
Edit:
Usually when faced with implementing a design choice like this, I try to offer a second, non-interfering gesture as a backup, such as a tap in that area, that has the same effect.
As in iOS 11, you cannot disable the dock in an application, nor in Settings. I'd suggest providing a larger area for swiping up from the bottom.
Normally such conflicts should be avoided, as they degrade user experience: how do you know that the user does not actually want to use the dock?
But if you really want, you can override the preferredScreenEdgesDeferringSystemGestures() method in the root controller to specify which edges should NOT (immediately) trigger system gestures.
e.g.
override func preferredScreenEdgesDeferringSystemGestures() -> UIRectEdge {
return .bottom
}
Related
I have a horizontally scrolled UICollectionView with a title label above it and a UIPageControl below it.
UILabel
UICollectionView
UIPageControl
When I turn on the VoiceOver accessibility feature and start traversing the screen sequentially, the collection view scrolls to the beginning or end automatically. Making the page jump off suddenly. For example, if I scroll to the 2nd page using page control, and move back to the collection view, it shows and reads the last page unexpectedly. Since I'm using the page control for navigation in the accessibility mode, I'd like to prevent the automatic scrolling.
How do I prevent or counter that?
I found an issue that seems to describe the same problem, but there's no workaround suggestion: iOS 8.4: Scroll view resets contentOffset with Voice Over enabled shortly after view appear
I encountered it on iOS 13.4.1 iPhone 11 Pro
UIScrollViewDelegate.scrollViewDidScroll(_:)
A change in the accessibility focus that triggers an automatic scrolling also triggers a call to scrollViewDidScroll(_:) in your UIScrollViewDelegate. Use that to counter the automatic scrolling effect, f.i. by setting contentOffset the way you prefer it.
You may need to detect that the scrolling was actually triggered by accessibility features, and not the user dragging or pinching. UIAccessibility.isVoiceOverRunning and UIAccessibilityFocus.accessibilityElementDidBecomeFocused() are your friends here. Beware that changing contentOffset (or zoomScale or whatever is needed) may trigger another call to scrollViewDidScroll(_:), so you need to prevent an infinite recursion.
Using #pommy's suggestions, I was able to fix my similar issue. In the code I was working on, the most appropriate place to make the change ended up being CalendarCollectionView.setContentOffset(_:animated:), where CalendarCollectionView is a UICollectionView subclass. Specifically, it's a JTACMonthView subclass, but that should not be of any relevance to this answer.
From the name, you can see my use case: a calendar which shows a month at a time. It could have many months both into the future and the past, but the usual user focus is likely to start somewhere in the middle.
Like the OP, I found that swiping from an outer element to the collection view with VoiceOver enabled caused the focus to go to the first date in the calendar, in my case 1st January 1951 (Date.farPast, I believe.) An interesting aside: Switch Control navigation did not cause the same behaviour.
The underlying behaviour was that contentOffset was getting set to 0.0 in the dimension that the collection view scrolls. In my code, that direction is held in style, and changes based on configuration, but in most applications it's likely to be fixed.
My code simply blocks any offset changes to 0.0 when VoiceOver is enabled. This is pretty naïve, and won't be suitable for all apps, but gives a concrete example which I hope will help some others!
override func setContentOffset(_ contentOffset: CGPoint, animated: Bool) {
if shouldPreventAccessibilityFocusScrollback(for: contentOffset) {
return
}
super.setContentOffset(contentOffset, animated: animated)
}
func shouldPreventAccessibilityFocusScrollback(for newContentOffset: CGPoint) -> Bool {
if UIAccessibility.isVoiceOverRunning {
switch style {
case .horizontal:
return newContentOffset.x == 0
case .vertical:
return newContentOffset.y == 0
}
}
return false
}
I spent quite a long time trying to determine when UIAccessibilityFocus moved from something outside the collection view, to something inside the collection view, which is ideally the only time we want to block these automatic scrolls. I was unsuccessful, but I think that was mostly due to subclassing a third party collection view (the calendar). There's definitely more merit to that approach, if you can get it to work... but it will require some careful management of state.
I was using SideMenu library from this github repo.
https://github.com/jonkykong/SideMenu
And after going high and low, I can't pinned down how to disable swipe on a navigation bar of viewcontroller.
I will use example included in library as example. At MainViewController.swift file, SideMenu is initiated and as far as I know, there are only 2 methods/functions related to gesture. And none of them are related to disable reacting to gesture. And I added this line at setUp method in the file.
SideMenuManager.default.menuPushStyle = .preserveAndHideBackButton
I have tried using childrenController in these methods but as soon as I did that, I can't use swipe/gesture on the directed view after touching on items of left sidemenu/drawer.
Is there anyway I could disable gesture on navigation bar using this library? And kindly let me know if I need to edit question, as this is my first question in stackoverflow.
As checked with demo example from above github project,
SideMenuManager.default.menuAddPanGestureToPresent(toView: self.navigationController!.navigationBar)
This line is in class 'MainViewController' in method 'setupSideMenu' which is responsible for adding pan gesture to navigation bar, which causing swipe gesture work for navigation bar. If you don't want this feature, you can simply comment the line and it will work.
Hope it helps...
Not sure about your scenario, but in my case when user logs out and reach on login screen i wanted to disable gesture from left to right.
At th etime user clicks on logout i set below property to 0.0 and that's it.
SideMenuManager.default.menuWidth = 0.0
Now again reset it to 85% after user re-login.
I just figured out how to do this myself for a UITabBarController. The approach will be similar for a UINavigationController. I created the following function to enable/disable the swipe gestures:
/// Keeps references to the gestures created by the SideMenu manager.
private var sideMenuGestures : [UIGestureRecognizer] = []
/// Enables or disables the swipe gestures used to show a SideMenu on the given side (default: left).
func enableSideMenuGestures(_ enable: Bool, forMenu: UIRectEdge = .left) {
if enable {
if sideMenuGestures.count == 0 {
sideMenuGestures.append(SideMenuManager.default.menuAddPanGestureToPresent(toView: self.tabBarController!.view))
sideMenuGestures.append(contentsOf: SideMenuManager.default.menuAddScreenEdgePanGesturesToPresent(toView: self.tabBarController!.view, forMenu: forMenu))
}
} else {
self.tabBarController?.view.gestureRecognizers?.removeAll(where: { sideMenuGestures.contains($0) })
sideMenuGestures.removeAll()
}
}
In your case, simply replace self.tabBarController with self.navigationController and it should work the same. (The self. prefix is not required but I like to include it as a convention to show it's an inherited field rather than one I've declared.)
This works because SideMenuManager handily returns references to all the gestures it creates; otherwise you'd have to clear all gestures from the navigation controller, which would be a bad idea as explained in this answer.
Usage is then simply enableSideMenuGestures(true) in the action handler for a button which displays the menu (for example) and enableSideMenuGestures(false) when you want to disable the gestures again.
I am working on the IOS application, related to voice over, my Question is : When accessibility voice over was enabled how can i get the swipe gestures left, right, top and down, what re the function for detecting these in swift?
First of all, you need to let VoiceOver know that about your view (or another element). So if you are in a view controller, this should work: self.view.isAccessibilityElement = true
Second, you need to let VoiceOver know that your view will handle user interactions on its own: self.view.accessibilityTraits = UIAccessibilityTraitAllowsDirectInteraction. After that your view should start getting gestures notifications.
Here's another relevant answer: https://stackoverflow.com/a/20712889/2219578
It isn't possible to catch the left, right, top and bottom VoiceOver gestures : I've seen neither a protocol nor a kind of notification for this.
However, you can detect a scrolling action and be aware of the element focus provided by VoiceOver.
I understand the concept that the focus engine will decide what can be selected. It also seems that it moves linearly vertically or horizontally and tries to find the next closest neighbor whose view intersects that vertical or horizontal line.
The problem I haven't solved just yet is how to set things up so that panning to switch between focusable subviews does not get prevented if one of them has a scrollable area (like a map).
I have two collection views that take up the width of the screen and sit one on top of the other. I can pan to switch between these just fine. Here is the code that overrides their shared custom UICollectionView class
override public func canBecomeFocused() -> Bool {
return true
}
public override func shouldUpdateFocusInContext(context: UIFocusUpdateContext) -> Bool {
return true
}
public override func didUpdateFocusInContext(context: UIFocusUpdateContext, withAnimationCoordinator coordinator: UIFocusAnimationCoordinator) {
super.didUpdateFocusInContext(context, withAnimationCoordinator: coordinator)
}
In a separate view controller, I have a map view and a collection view above it. Both take up the width of the screen. I can pan to switch from the collection view down to the map view, but no matter how slow or fast I try to pan/swipe up, I cannot get the map view to lose its focus.
I did try adding some gesture recognizers and setting the delegate methods to try and make my GR win over the map view's scrolling GRs, but to no avail.
Any one else have similar experience? How do I get back out of the map view without having to add another dialogue or something to switch context back to the collection view?
Thank you in advance.
I'm sure there are a number of ways to solve the problem (like the one suggested in the comment by Eugene for example). In general, you'll probably need to determine when you want the focus to leave the map and trigger a focus update with setNeedsFocusUpdate(), updateFocusIfNeeded(). You would then override preferredFocusView (part of UIFocusEnvironment which is adopted by UIViewController and UIView among other things), to set the focus to whatever you want.
The real trick is to determine when it's appropriate to do this. A map is particularly hard because it's possible that it may scroll for a very long time prior to hitting a boundary (if it ever does). As such, you may need to utilize a button press as suggested by Eugene or perhaps by implementing some of the MKMapViewDelegate methods like mapView(_:regionWillChangeAnimated:) to determine when the map has moved a large distance. The "correct" answer would be determined by your desired behavior, of course.
I’d like to use a UIScreenEdgePanGestureRecognizer on the bottom edge of my display. The recognizer works perfectly if I set its edges to UIRectEdgeLeft or UIRectEdgeRight, but UIRectEdgeTop or UIRectEdgeBottom do not work – they’ve overridden by some UISystemGestureGateGestureRecognizer.
I’m actually trying to override the Control Center in the same way that Facebook Paper has managed to do – figured the screen edge pan may have been their trick. Paper allows you to scroll from the bottom screen edge to pull their menu up, and Control Center doesn’t pop up at all. It’s definitely possible, I’m just wondering how they’ve done it.
Any thoughts?
There is a trick that lets you prevent the accidental launch of the Control Center. Just disable the status bar. Then on swipe the user will be prompted whether the control centre have to be launched or not.
It won't be launched in a single swipe. Instead an arrow appears on the first swipe and the user need to click and drag the arrow to launch the control centre, hence prevent accidental launch. Use this code to disable status bar.
I don't know if it will work in your case and your gesture recognizer will be fired but I would give it a try.
You can disable the status bar using this delegate method:
- (BOOL) prefersStatusBarHidden
{
return YES;
}
Hope it helps.
I don't know what I'm doing wrong but because the Facebook app hides the status bar they happen have the same behavior as in all full-screen apps in iOS.
I have tested it multiple times - the Control Center does not pop up, but the little tiny arrow to bring it up - does.
So your assumption is wrong - Facebook didn't disable Control Center, nor did they override any behaviors. The just hid the statusBar across the whole app.
Just set the key value View controller-based status bar appearance to NO in plist. Then hide it.
And it is the way to go.
And BTW, when you swipe the apps view down to reveal your profile and the status bar appears - try to swipe up from the very bottom edge of the screen. Believe me, you'll be totally amazed.
There's no magic here, no nothing. Facebook are big but they can't defy iOS rules. And I'd like it to be that way, honestly.
Here's the video of the process:
http://www.youtube.com/watch?v=A2CWvhxhGoY&feature=youtu.be
How about using UISwipeGestureRecognizer instead?