I am trying to prevent a popup from being dismissed when the user taps outside of the popup. I have seem other questions/answers about this, and they all seem to suggest using the modalInPopover for the view. I have done this in the viewDidAppear as I have seen suggested. I have text fields along with buttons that fill in a label according to a selection from a dropdown menu. Before any information is entered, it works fine, and the popup is not dismissed when tapping outside. It also works fine for when text is entered in the text fields. However, as soon as I make a selection from a dropdown after tapping one of the buttons, the popup will dismiss after touching outside of it.
Are there any other suggestions as to why this could be? Could it have something to do with calling resignFirstResponder on the text fields?
In swift 3, ios 10
After implementing UIPopoverPresentationControllerDelegate the following function seems to do the trick.
func popoverPresentationControllerShouldDismissPopover(_ popoverPresentationController: UIPopoverPresentationController) -> Bool {
return false
}
I hope this helps if anyone is still looking for a solution.
You can implement the UIPopoverControllerDelegate:
func popoverControllerShouldDismissPopover(popoverController: UIPopoverController) -> Bool {
//return true when you need
return false
}
This is deprecated in iOS 9.0 but if you have a project which supports iOS 8 you have to use it.
Let me know if it works for you
When displayed, taps outside of the popover window cause the popover to be dismissed automatically. To allow the user to interact with the specified views and not dismiss the popover, you can assign one or more views to the passthroughViews property. Taps inside the popover window do not automatically cause the popover to be dismissed. Your view and view controller code must handle actions and events inside the popover explicitly and call the dismiss(animated:) method as needed.
docs
Update:
Use UIPopoverPresentationControllerDelegate
func popoverPresentationControllerShouldDismissPopover(_ popoverPresentationController: UIPopoverPresentationController) -> Bool {
return false
}
Related
When a UIContextMenuInteraction/UITargetedPreview is visible, is it possible to manually dismiss it? For example, when the app goes to the background, I would like to dismiss it manually.
https://kylebashour.com/posts/context-menu-guide
I was interested in the same thing, and calling UIViewController.dismiss(animated:completion:) on the source view controller dismisses it as it would a modally presented controller.
UIContextMenuInteraction has a method which is called dismissMenu.
I believe this is what you would rather call when in need to hide a menu, because calling dismiss(animated:completion:) could lead to unwanted behaviour if you have another modally presented controller and you are not sure if the menu is active.
Moreover, starting from iOS 13.2 you can get access to contextMenuInteraction as a property of a UICollectionView and call dismissMenu.
contextMenuInteraction is also available as a property for a UITableView starting from iOS 14.0
It becomes quite complicated when your minimal iOS version is 13.0 and you need somehow to dismiss a menu for a cell in a collectionView. This is a bump in the road I am currently having trouble with. I believe the only appropriate way is to create, save and provide a custom PreviewViewController in this method:
collectionView(collectionView: contextMenuConfigurationForItemAt: point:) -> UIContextMenuConfiguration?
When having access to your custom PreviewViewController, you can call dismiss(animated:completion:) method on it whenever you want.
I am currently working on an app and I am using UIAccessibility to make it intuitive and easy to use for everyone.
I am facing what I think is a simple challenge but I just can't figure it out and I'm in need of any guidance and/or assistance.
I have a tableView and when a cell is tapped depending on its content it either presents an alert view or a viewController. When voice over is enabled and a cell is tapped the voice over is stuck at the previous view and not on the presented viewController or alertView. How do I make it so when a cell is tapped voice over focuses on the present view and not on the previous one.
I have tried setting accessibilityViewIsModal for the previous view to false and true for the presented view.
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView. accessibilityViewIsModal = false
}
Actual result: voice over focuses on the previous view
Expected result: Voice over should focus on the presented alert view.
Most of times when presenting an alert, or a modal view controller, it should just work. Can you share more details on how are you presenting those? If you are using custom modal presentations or you have a custom alert view, you may sometimes need to notify UIAccessibility that the screen changed. You can do it posting a notification like this:
UIAccessibility.post(notification: .screenChanged, argument: customModalView)
Where customModalView is the view you want to get VoiceOver's focus. Sometimes you may also find the problem you described where the focus can still jump to elements in the view underneath the presented one. For that you can use the property you mentioned accessibilityViewIsModal. But the value of this property is false by default, you have to set your 'modalView' to be modal for accessibility purposes when presented and that will allow VoiceOver to know that it needs to skip any sibling views.
customModalView.accessibilityViewIsModal = true
I hope this helps!
I am building a pin entry viewController very similar to the iOS one you see when you go to Settings->TouchID and it prompts you for pin.
I am trying to mimic its behavior of presenting the iOS keyboard along with (at the same) of the modal presentation of the pin entry viewController. I have noticed other applications like Venmo are able to achieve this as well.
How can I achieve this behavior? My pinEntryView is a textField. I have tried sending it the becomeFirstResponder message in viewDidAppear, and this seems to work; however, it will present the iOS keyboard AFTER the viewController modal presentation has finished. I want the presentation to occur at the same time to give the feeling that the iOS keyboard is actually baked-in/part of the ViewController.
I have tried sending the becomeFirstResponder messages in viewWillAppear, viewWillLayoutSubviews as well, but these are not stable solutions. Sometimes the keyboard is displayed and sometimes its not. Is there anyway to do this?
I made the test by setting becomeFirstResponder in the viewDidLoad and it is working fine.
I have one button that calls the modal, and that modal has the next code:
override func viewDidLoad() {
super.viewDidLoad()
self.textField.becomeFirstResponder()
}
It has no unexpected behavior.
I believe I have had this issue before, and it has quite an interesting solution. What you want to do is call becomeFirstResponder in viewWillAppear before calling super 😱.
override func viewWillAppear(animated: Bool) {
textField.becomeFirstResponder()
super.viewWillAppear(animated)
}
Then when you call super the first responder is already set, and then iOS picks up this state and includes the keyboard appear animation in the appearance transaction.
Hopefully this can help you too.
here is resume of my app:
I have tab bar. When user tap on the tabbar item
appropriate view controller is presented by "slide-to-side"
animation (like in iP homescreen).
Code is in method tabBarController(tabBarController: UITabBarController, shouldSelectViewController viewController: UIViewController) -> Bool.
I have alert. When user tap on button, I want to direct him to the specific tab. But when I use self.selectedIndex = #, VC is showen, but without animation. Is there any way to achieve same action like tap on item? Thank you
Programmatically selecting the tab doesn't trigger any delegate methods. This is true of any control. Since you explicitly chose to do something, you already know you did it. You don't need a delegate method to tell you. This is by design and it is a good thing.
There is a simple solution. Put whatever animation code is inside the delegate method into its own method. Then call that method from the delegate method. Now you can also call that new method when you call self.selectedIndex = #.
I know that the view controller must be firstResponder in order for the inputAccessory to stay at the bottom. I am using a custom inputView / keyboard. I can manually dismiss it with a done button by removing the inputView but not resigning first responder. However when I enable the interactive drag to dismiss on my scrollview, the code automatically resigns first responder. So how can I use the interactive drag to dismiss and yet keep my viewcontroller as first responder? Anyone done this before? I thought maybe it is not possible and that I may need to make my own interactive drag to dismiss using a gesture recognizer.
More info:
I have a button that swaps between standard keyboard and my custom one. I have seen dismissing these cause 2 keyboard did dismiss notifications. I thought I could become firstResponder in the keyboardDidHide method but this didn't work well since I couldn't tell the difference between when I manually dismissed the keyboard and when the interactive drag does it. This matters because I don't need to reload the input view or become first responder when I manually dismiss because I took care of it already.
Any suggestions would be amazing. I am trying to use inputView and inputAccessoryView on the UIViewController level.
Well after a day of pulling my hair, I have an answer.
Using the canResignFirstResponder of my viewcontroller did the trick. In viewWillAppear I set a BOOL responderOverride = YES;
In viewWillDisappear I call
responderOverride = NO;
[self resignFirstResponder];
When the interactive drag on the scrollview tries to resignFirstResponder, canResignFirstResponder returns no which prevents my viewcontroller from resigning and keeps my input accessory retained and sitting at the bottom of the screen.
There is a lot of other code with reloading input views but since the real question was how to force a controller to stay first responder so we don't lose our input accessory view, then this solution works.
override var canBecomeFirstResponder : Bool {
get {
retrun true
}
}
This works for me