Swift alert custom show and dispense - ios

I created a custom alert in a viewcontroller, following the guidelines of the most voted answer of that question:
https://stackoverflow.com/a/37275840/6196609
I use this to display the alert, it is used as a "loading".
let pending = UIAlertController()
override func viewDidLoad() {
super.viewDidLoad()
[…]
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let pending = storyboard.instantiateViewControllerWithIdentifier("alertaLoad")
pending.modalPresentationStyle = UIModalPresentationStyle.OverCurrentContext
pending.modalTransitionStyle = UIModalTransitionStyle.CrossDissolve
[…]
}
to show:
self.presentViewController(self.pending, animated: true, completion: nil)
I succeeded in showing it, but I need to terminate it by the viewcontroller that invoked it after the end of my process, not by itself as was done in the example I quoted.
I've tried this but nothing happens.
self.pending.dismissViewControllerAnimated(false, completion: { (vetor) -> Void in
[…]
})
How could I do this correctly?

Call dismisson the presenting UIViewController, not on the presented one:
self.dismiss(animated: true) {
// go on
}

Related

How do I prevent a navigationController from returning to root when dismissing MFMailComposeViewController

When I dismiss an instance of MFMailComposeViewController or MFMessageComposeViewController that is presented modally from the third viewController in a navigation stack, the navigation stack is reset, and the root VC is reloaded. How can I prevent this behavior and remain on the original presenting viewController (third VC in the stack)? I get the same behavior whether I call dismiss from the presenting VC, the presented VC, or the navigationController.
This has been asked before, but I have not seen a solution.
App Structure looks like this:
TabBarController
Tab 1 - TripsNavController
-> Trips IntroductionVC (root VC) segue to:
-> TripsTableViewController segue to:
-> TripEditorContainerVC
- TripEditorVC (child of ContainerVC)
- HelpVC (child of ContainerVC)
Tab 2...
Tab 3...
Tab 4...
In the TripEditorVC I present the MFMailComposeViewController. The functions below are declared in an extension to UIViewController that adopts the MFMailComposeViewControllerDelegate protocol
func shareWithEmail(message: NSAttributedString) {
guard MFMailComposeViewController.canSendMail() else {
showServiceError(message: "Email Services are not available")
return
}
let composeVC = MFMailComposeViewController()
composeVC.setSubject("My Trip Plan")
composeVC.setMessageBody(getHTMLforAttributedString(attrStr: message), isHTML: true)
composeVC.mailComposeDelegate = self
present(composeVC, animated: true, completion: nil)
}
Then in the delegate method I dismiss the MFMailComposeVC:
public func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
switch result {
case .sent:
print("Mail sent")
case .saved:
print("Mail saved")
case .cancelled:
print("Mail cancelled")
case .failed:
print("Send mail failed")
}
if error != nil {
showServiceError(message: "Error: \(error!.localizedDescription)")
}
dismiss(animated: true, completion: nil)
}
I have tried the following to present and dismiss and get the same behavior, i.e.: the TripsNavController clears the nav stack and reloads the TripsIntroductionVC as its root VC:
self.present(composeVC, animated: true, completion: nil)
self.parent?.present(composeVC, animated: true, completion: nil)
self.parent?.navigationController?.present(composeVC, animated: true, completion: nil)
self.navigationController?.present(composeVC, animated: true, completion: nil)
You can also check with presentingViewController?.dismiss method to get the solution.
I have tried with following navigation stack.
I am able to send email successfully from Container VC's Send Email button using your code only.
Can you please check and verify navigation flow?
Please let me know if you still face any issue.
dismiss(animated: true, completion: nil)
to
self.dismiss(animated: true, completion: nil)
Try this
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let secondViewController = storyboard.instantiateViewControllerWithIdentifier("secondViewControllerId") as! SecondViewController
self.presentViewController(secondViewController, animated: true, completion: nil)
or
https://stackoverflow.com/a/37740006/8196100
Just Use Unwind Segue its Pretty simple and perfect in your case
I have used many times..
just look at this Unwind segue's example
. If u still don't able to find answer or not able to understand the Unwind segue then just reply to me.
Hope this will solve your problem.
I found the problem with my navigation stack today. I built a simple
project that duplicated the tabBarController/NavigationControler architecture of my problem project, component by component, until dismissing a MFMailComposeViewController caused my navigation stack to reset as described in my original post.
That immediately pointed to the source of the bug. In my subclassed UINavigationCotroller I was instantiating the root viewController in code so that I could skip an introductory view if the user had set a switch in the apps settings. In order to pick up changes in that switch setting I was calling my instantiation code in viewDidAppear of the navigationController. That worked fine EXCEPT when dismissing the mailComposeVC. The fix was to add a guard statement in viewDidAppear to return if the navControllers viewController collection was not empty, and send and respond to an NSNotification when the switch was changed.
class TopNavigationController: UINavigationController {
var sectionType: SectionType?
var defaults = UserDefaults.standard
var showIntroFlag: Bool = true
override func viewDidLoad() {
super.viewDidLoad()
// Handle initial load of the tab bar controller where we are not sent a sectionType
if sectionType == nil {
sectionType = .groups
}
setShowIntroFlag()
NotificationCenter.default.addObserver(self, selector: #selector(resetControllers), name: NSNotification.Name(rawValue: "kUserDidChangeShowIntros"), object: nil)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
guard self.viewControllers.isEmpty else {
return
}
loadControllers()
}
func setShowIntroFlag() {
showIntroFlag = true
// Check NSUserDefaults to see if we should hide the Intro Views for all sections
if defaults.bool(forKey: "SHOW_SECTION_INTROS") == false {
showIntroFlag = false
}
}
func loadControllers() {
if showIntroFlag == true {
showIntro()
} else {
skipIntro()
}
}
func resetControllers() {
setShowIntroFlag()
loadControllers()
}

swift 3 present UIViewController: nib but the view outlet was not set

Update(3)
I have the following code:
This works if I wait in the debugger:
And if I dont:
A view can only be associated with at most one view controller at a
time!
#IBAction func openTestViewController(_ sender: Any) {
let b = Bundle(identifier: "com.test")
let m = MultipleListViewController(nibName: "MultipleListViewController", bundle: b)
self.present(m, animated: true, completion: nil)
}
This does not work; error: `
A view can only be associated with at most one view controller at a
time!
#IBAction func openTestViewController(_ sender: Any) {
DispatchQueue.main.async {
let b = Bundle(identifier: "com.test")
let m = MultipleListViewController(nibName: "MultipleListViewController", bundle: b)
self.present(m, animated: true, completion: nil)
}
}
Note: The reason I do Bundle(identifier: "com.test") is because I have it in a framework.
This only works if:
1. I set a breakpoint
2. And wait for it to load - and make sure that I can see it with the little preview eye when debugging.
How can I/should i wait for it in code?

Using callbacks to reproduce UIAlertViewController

I want to reproduce the functionality of the UIAlertController as the class isn't very customisable. So here is what I did:
Create a DeleteDeckViewController that only has two buttons Yes, and No, with a callback method
class DeleteDeckViewController: UIViewController {
var deleteDeck : ((deleteDeck : Bool) -> ())?
#IBAction func yesButton(sender: AnyObject) {
deleteDeck!(deleteDeck: true)
}
#IBAction func noButton(sender: AnyObject) {
deleteDeck!(deleteDeck: false)
}
}
In the main view controller when the delete button is pressed it creates a popup DeleteDeck
func handleDelete(detailDeckView: DetailDeckView) {
let deleteDeckViewController = DeleteDeckViewController(nibName: "DeleteDeckViewController", bundle: nil)
let popup = PopupDialog(viewController: deleteDeckViewController, transitionStyle: .BounceDown, buttonAlignment: .Horizontal, gestureDismissal: true)
presentViewController(popup, animated: true, completion: nil)
deleteDeckViewController.deleteDeck(true) { () -> Void in
print("Deck deleted")
}
}
What I want to do is to use a callback to tell my main view controller whether the user pressed yes or no and then do some actions depending on yes or no. Although, I don't know the correct way to do it. I've read a few articles on callbacks, but I don't know how to apply it in this situation. I've tried using delegates it's just that I would need to pass the DetailDeckView object around without it being used so that's why I thought callback would be a better choice.
Would someone be able to point me in the right direction?
Within DeleteDeckViewController you have declared the deleteDeck variable as a function. Therefore, when you use an instance of this class, you need to set it as a variable. Therefore you code would look more like that below (untested):
func handleDelete(detailDeckView: DetailDeckView) {
let deleteDeckViewController = DeleteDeckViewController(nibName: "DeleteDeckViewController", bundle: nil)
deleteDeckViewController.deleteDeck = myDeleteHandler
let popup = PopupDialog(viewController: deleteDeckViewController, transitionStyle: .BounceDown, buttonAlignment: .Horizontal, gestureDismissal: true)
presentViewController(popup, animated: true, completion: nil)
}
func myDeleteHandler(shouldDelete: Bool) {
if shouldDelete {
print("Delete deck")
}
}
Also, in your code, rather than using deleteDeck! which will fail if deleteDeck is nil, it is better practice to use code like:
if let deleteDeck = self.deleteDeck {
deleteDeck(true)
}
This will create a local non-Optional variable called deleteDeck, that can be assured to not contain nil.
You could build this code into DeleteDeckViewController to streamline further:
class DeleteDeckViewController: ....
func showDialog(vc: UIViewController, completion: ((deleteDeck : Bool) -> Void)?) {
self.deleteDeck = completion
let popup = PopupDialog(viewController: vc, transitionStyle: .BounceDown, buttonAlignment: .Horizontal, gestureDismissal: true)
presentViewController(popup, animated: true, completion: nil)
}

Attempt to present UINavigationController whose view

AppDelegate
initialViewController = storyboard.instantiateViewControllerWithIdentifier("LoginViewController")as! UIViewController
}
self.window?.rootViewController = initialViewController
self.window?.makeKeyAndVisible()
Takes me to LoginViewController
let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("CardsNavController") as? UIViewController
self.presentViewController(vc!, animated: true, completion: nil)
I click on the login button takes me to CardsViewController
func goToProfile(button: UIBarButtonItem) {
pageController.goToPreviousVC()
}
Clicking on back button which is part of the UINavigationController runs goToProfile (above) and takes me to ViewController.swift because that's where the pageController is declared.
let pageController = ViewController(transitionStyle: UIPageViewControllerTransitionStyle.Scroll, navigationOrientation: UIPageViewControllerNavigationOrientation.Horizontal, options: nil)
The error shows up here
func goToPreviousVC() {
//let currentControllers = self.navigationController?.viewControllers
if viewControllers.isEmpty {
setViewControllers([profileVC], direction: UIPageViewControllerNavigationDirection.Reverse, animated: true, completion: nil)
pageController.presentViewController(profileVC, animated: true, completion: nil)
else {
let previousVC = pageViewController(self, viewControllerBeforeViewController: viewControllers[0] as! UIViewController)!
setViewControllers([previousVC], direction: UIPageViewControllerNavigationDirection.Reverse, animated: true, completion: nil)
}
Error:
Warning: Attempt to present <UINavigationController: 0x15ce314e0> on <MyApp.ViewController: 0x15cd2c6f0> whose view is not in the window hierarchy!
Basically the flow is like this AppDelegate -> LoginViewController -> ViewController and I need to get to ProfileViewController.
ProfileView has ProfileNavController while CardsView has CardsNavController
ViewController has a storyboardID of pageController.
Both ProfileView and and CardsView are embedded within UINavigationControllers (hence the NavController extension).
The second time I run the app after a fresh install it works perfectly (all the controllers get loaded okay). Should I push viewControllers in AppDelegate?
I've checked your code using Xcode 7, which may not be ideal for resolving this issue because I had to covert your code to Swift 2.0, but here was what I found out.
ISSUE
First time opening the app, this block:
if currentUser() != nil {
initialViewController = pageController
}
else {
initialViewController = storyboard.instantiateViewControllerWithIdentifier("LoginViewController") as UIViewController
}
self.window?.rootViewController = initialViewController
Will initialize LoginViewController and make it the current window's rootViewController.
At this point there is no pageController initialized
When user taps on the button to go to the Profile screen, this method will be called
func goToProfile(button: UIBarButtonItem) {
pageController.goToPreviousVC()
}
At this point, pageController is initialized, and off course, there is NOTHING in the viewControllers array. Let's see what happen in the goToPreviousVC method:
Original method looks like this:
let nextVC = pageViewController(self, viewControllerAfterViewController: viewControllers[0] as UIViewController)!
setViewControllers([nextVC], direction: UIPageViewControllerNavigationDirection.Forward, animated: true, completion: nil)
One thing you can see obviously is: calling viewControllers[0] could give you a crash because viewControllers is an empty array.
If you use Swift 2.0, it doesn't even let you compile your code :)
SOLUTION
Let's go directly to the solution: Ensure that the pageController is available before trying to call it's viewControllers.
I blindly tried fixing you code in Swift 2.0 and found out that this method would work for you:
BEFORE: In LoginViewController.swift line 63
let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("CardsNavController") as? UIViewController
self.presentViewController(vc!, animated: true, completion: nil)
AFTER: Let's fix it like this
let navc = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("CardsNavController") as! UINavigationController
if let viewControllers = pageController.viewControllers where viewControllers.count == 0 {
pageController.setViewControllers([navc.viewControllers[0]], direction: .Forward, animated: false, completion: nil)
}
self.presentViewController(pageController, animated: true, completion: nil)
It's working well here and probably I don't need to show you how the screen transition should look like :)
In case you would like to have the fixed source code as well, please find it HERE. Basically I converted your code to Swift 2.0 and ignored unnecessary parts like Facebook authentication for faster investigation.
Good luck & Happy coding!

opening a tab bar controller on successful login swift

I am extremely new to swift and storyboards. I have an initial view set which presents the user with login or register options. on the success of my login web service, I am trying to open a tab bar. I am getting into the success of the webservice as I can see the response.
My code for attempting to load the tab bar is as folllows in my initial view controller:
func loadHomeScreen()
{
emailField.text = ""
passwordField.text = ""
self.presentViewController(UIStoryboard.tabbarController()!, animated: true, completion: nil)
}
And at the very bottom of that file, I have the following:
private extension UIStoryboard {
class func mainStoryboard() -> UIStoryboard { return UIStoryboard(name: "Main", bundle: NSBundle.mainBundle()) }
class func tabbarController() -> TabbarController? {
return mainStoryboard().instantiateViewControllerWithIdentifier("TabbarControllerID") as? TabbarController
}
}
And in my storyboard I have given the tabbarcontroller the id above. When I run the app (tested on the simulator for iphone6), I am getting the error 'found nil while unwrapping an Optional value' and this is pointing to the line of code in my loadHome func above (self.presentViewController(UIStoryboard.tabbarController()!, animated: true, completion: nil))
Any help would be appreciated
You could instead instantiate your storyboard like so and the code would look like this under loadHomeScreen():
emailField.text = ""
passwordField.text = ""
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewControllerWithIdentifier("TabbarControllerID") as! TabbarController
self.presentViewController(vc, animated: true, completion: nil)
You may need to change the bundle to the "mainBundle" as you have in your code, but this should work.
This may not be the optimal solution if your plan is to extend UIStoryboard for instantiating your ViewControllers but I think this might be a little easier/cleaner, depending on how your project is set up.

Resources