How to dismiss iOS 14 datePicker modal? - ios14

My development team has implemented the new iOS 14 date picker in the "compact" style, which pops a modal on top of our application. It doesn't have any controls to dismiss. As a user, I can tap outside the modal, which I figured application.windows.firstMatch.tap() would simulate well. It does when I'm debugging, but when running regularly, it is also dismissing the view behind the modal. Kind of confusing as to why it would act differently, but :shrug:
I've suggested we move to the "inline" style, but may not get my way (it's a better UX in our use-case anyway). Is anyone successfully dismissing these without unintended consequences?

This worked on the iPhone.
#IBAction func dateChanged(_ datePicker: UIDatePicker) {
model.date = datePicker.date
self.dismiss(animated: true, completion: nil)
}
Maybe if this dimisses too much on iPad you need to set definesPresentationContext = true on your view controller?

Related

Xcode 12.0 iOS 13+ UIViewController() problem with viewWillDisappear() [duplicate]

This question already has answers here:
Detecting sheet was dismissed on iOS 13
(13 answers)
Presenting modal in iOS 13 fullscreen
(27 answers)
Closed 2 years ago.
I have a simple flash card app I wrote for a friend of mine. I'm a hobby-est at best. It's essentially Tinder that you can flip the card over for a dead language. Everything has been working great up until the iOS 13 update with how Apple redid the Storyboards for flexibility.
My problem is my elegant solution to save userdefaults when exiting a view is no longer being called. This "carddeck" view controller is called when a button on another screen is pressed. To exit this same view controller before you would press a button that was tied to an "action segue - show" (not #IBAction) that would bring the view back to the "mainview" view controller. I tried the same action segue with "present modally." but no dice.
class CardDeckViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// called!
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
// called!
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(true)
// not called when "return" on this screen is pressed;
// however, it still returns to the main screen it just doesn't save the user's score
}
Any help would be appreciated. My hope is there is an easier fix than redoing the entire storyboard. I see about UIModalPresentFullScreen. I'm no Swift guru so at this point I thought I would reach out to the pros. Hopefully this is too time consuming of a fix. Luckily I think the other views are not affected by this other than one more.
Thanks for reading!
A couple of things you might try:
It sounds like you're presenting the "main view" view controller again. Hard to tell without seeing your code but I think you should probably be dismissing "CardDeck" view controller in order to get back to your "main view" view controller.
Have a look at UIAdaptivePresentationControllerDelegate. This stackoverflow post covers the change and iOS 13 solution.
I had some similar issues with iOS 12 view controller lifecycle behavior being changed in iOS 13.
I suggest considering NotificationCenter if nothing else works. You broadcast from your current view controller and someone like your AppDelegate can receive the message and act on this. It's not always the best tool for the job but it can help in certain situations.
Not related to your question but your calls to super super.viewWillAppear(true) should probably be super.viewWillAppear(animated) so you're not disregarding the method argument.
The answer was at the bottom of this question:
Presenting modal in iOS 13 fullscreen
I just had to redefine the storyboard UI property from Automatic to Full Screen.

How to present iOS keyboard along with the modal presentation of a ViewController?

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.

Preventing Popover Dismissal When Tapping Outside (Swift)

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
}

How to show UITableViewController programmatically

In my application, users can submit a new post similar to Instagram. However, if they decide not to post, they can press "Cancel" button on the UITabBarButton, and it will load the latest posts.
I've looked around for solution, however they are mainly UIViewController.
showViewController Not Working
However, what I need is to force open a UITableViewController. My codes are as follows, which I know it does not work, because it does not conform to the expected UIViewController.
#IBAction func cancelBtn(sender: UIBarButtonItem) {
self.navigationController?.pushViewController(LatestPostViewController as UITableViewController, animated: true)
}
EDIT: Added a screenshot for better illustration purposes. So the "Cancel" button is embedded in a Navigation Controller inside Camera Tab. I need to open up Latest Post tab when users press "Cancel" button on UINavigationBar
Apologies, I do not have enough reputation to display image directly.
Image
Check to make sure your cancel button action is set up correctly. In the action you want to transfer to the appropriate tab:
#IBAction func cancelBtn(sender: UIBarButtonItem) {
self.tabBarController?.selectedIndex = 0
}

performSegueWithIdentifier very slow when segue is modal

I have a simple table view where I handle the select action on the table view. This action follows a segue.
If the segue is a push segue, the next view shows immediately.
If the segue is a modal segue, the next view either:
takes 6 seconds or so to display
shows immediately if I tap again (second tap)
I tried looking around for some ideas, but none seem applicable to my situation. In particular:
I'm performing the segue on the main UI thread
My view is very simple (so there's no issue in viewDidLoad). Plus the fact that it shows up near instantaneous when the segue is push indicates that there is no problem loading the target view
I tried passing nil to the sender; same effect.
Does anyone have any ideas on this?
Trust me and try this. I have run into this problem a few times.
In Swift 2:
dispatch_async(dispatch_get_main_queue(),{
self.performSegue(withIdentifier:mysegueIdentifier,sender: self)
})
or for Swift 3:
DispatchQueue.main.async {
self.performSegue(withIdentifier: mysegueIdentifier,sender: self)
}
As discussed here and here.
It seems (to me...) that this problem happens only when the cell selectionType is not .none.
You may change it to any other option (at the storyboard Attribute inspector, set the Selection field) and it will work fine (working for me...).
The cons is that it messing up the cell UI.
You can call the segue in DispatchQueue.main.async{} block at the didSelect delegate function of UITableViewDelegate as people mention before.
I used the first solution and added at the cell itself -
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(false, animated: false)
}
This will make the cell 'highlight' at the tap, but it will return to its usual UI immediately and it fine for me...
There seem to be various situations when performing a segue will not work properly. For example, if you call performSegue from the action handler of an unwind segue, you will run into various issues, even though you are on the main thread. On my current project, I am calling performSegue from the didSelectRowAt method of a table view. This is one of the most basic segues there is and of course I am on the main thread, yet I was seeing the exact symptoms that the OP described.
I do not know why this happens in some cases and not others, but I have found that deferring the performSegue call using async fixes any potential issues. This used to seem like a hack and made me nervous, but at this point I have several mature projects using this approach and it now seems like the "right" way to do a manual segue.
Here is the Swift 3 version of the code (see the other posts for Swift 2 and Obj-C versions):
DispatchQueue.main.async {
self.performSegue(withIdentifier: "theIdentifier", sender: theSender)
}
The accepted solution worked for me. Code updated for Swift 2.0 below:
dispatch_async(dispatch_get_main_queue(),{
self.performSegueWithIdentifier(mysegueIdentifier, sender:self)
})
Hope help this yo can create programatically modal transition like this in Swift:
let myStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let modalViewController = myStoryboard.instantiateViewControllerWithIdentifier("myModalViewController") as! myModalViewController
modalViewController.modalTransitionStyle = UIModalTransitionStyle.CoverVertical
let navController = UINavigationController(rootViewController: accountManager)
dispatch_async(dispatch_get_main_queue(),{
self.presentViewController(navController, animated: true, completion: nil)
})
For developers organizing their code through subclassing, I've come to quite simple a solution I'd like to share (Swift 4):
import UIKit
class ABCViewController: UIViewController {
// ... Other useful methods like overriding deinit and didReceiveMemoryWarning
// Performs a segue on the main thread to ensure it will
// transition once a UI-slot is available
func performSegueOnMainThread(with identifier: String, sender: Any?) {
DispatchQueue.main.async {
self.performSegue(with: identifier, sender: sender)
}
}
}
Then, simply call it from your implementation:
myViewController.performSegueOnMainThread(with: "ShowDetailsSegue", sender: self)
For me it was the that I had too many "Clear" colored views in my next view, so when the segue was animated, it seemed like it was delaying because of this. I went through the view controller's UI hierarchy looking for clear colors and replacing them with solid black or white, and making sure the alpha is 1 (if it didn't need to be otherwise). My delay is now gone and my modal presentation is smooth.
I tried fixing this multiple ways including moving it to the main thread above. This worked for me:
In storyboard, select the table view in question, (select it in document outline to make sure you've got the right thing. Then in attributes inspector you will be able to see the attributes for the table view as well as the containing scrollview underneath it (all table views are based on scroll view). There is this stupid little box there called "delays content touches". Uncheck it. There is also one "allows cancellable touches" which I'll imagine you want to make sure is unchecked as well, as I think double touches were messing mine up.

Resources