Having trouble 'segueing' from one view controller to another, getting weird warnings - ios

I am getting a very strange error. I think the compiler is trying to tell me that it can't segue to another view controller until it is done executing all the code in my current view controller but I am not sure.
I'm literally getting input by using an alert box (i.e. calling a function called generateTextField).
Then when I am done I'm saying "Hey I want you to go to another view controller" - but the the compiler instead tells me "Hey I don't think so".
Here is my error:
Warning: Attempt to present HairStyle1ViewController: 0x7...> on browseBarbersViewController: 0x7...> which is already presenting
Warning: Attempt to present HairStyle1ViewController: 0x7..> on browseBarbersViewController: 0x7...> which is already presenting
#IBAction func AddNewStyleButtonClicked(sender: AnyObject)
{
// Get the "hairstyle name" from the user
generateTextField();
// OK We are done with that function, now transition to the
// next screen
performSegueWithIdentifier("HairStyle1", sender: self);
}
// Generate a text field for user input (i.e. call the alert function)
func generateTextField()
{
//1. Create the alert controller.
var tempStyle = "";
var alert = UIAlertController(title: "Add a New Style", message: "Enter the name of the new hairstyle below", preferredStyle: .Alert);
//2. Add the text field. You can configure it however you need.
alert.addTextFieldWithConfigurationHandler({ (textField) -> Void in
textField.placeholder = "Your New Hairstyle Goes Here..";
})
//3. Grab the value from the text field, and print it when the user clicks OK.
alert.addAction(UIAlertAction(title: "OK", style: .Default, handler: { (action) -> Void in
let textField = alert.textFields![0] as UITextField
tempStyle = textField.text!;
print("New Style Added is: " + tempStyle);
HairStyle = tempStyle;
}))
// 4. Present the alert.
self.presentViewController(alert, animated: true, completion: nil)
}
It's also weird that when I take out the generateTextField() function it performs the segue perfectly. I'm very confused.

Wow, I figured it out. I had to instead, segue in the body of the alert function.
I fixed this by adding
self.performSegueWithIdentifier("HairStyle1", sender: self);
after the
HairStyle = tempStyle; line

Related

How to disable touch outside screen dismiss view using MDCAlertController Material Design - swift

I am new iOS programming and now am fascinated in using MaterialComponents which provide by google. Now i facing one problem in component named Dialog.
When the view has been pop up on screen when i touch outside that pop up view and then that view has been dismiss. I don't want that to happen in my app.
I don't want user to click outside popup view to dismiss that popup view. What i want i just want user to click on action button that i provide for user's choice then the view should be dismiss when click on that action button only.
Really glade that you help.
MDCAlertController is inherited from UIViewController.
So, in order to restrict user to click outside MDCAlertController you have to access its property named view and then superview?.subviews[0].isUserInteractionEnabled = false
I have completed one example using MDCAlertController
let alert = MDCAlertController(title: title, message: message)
alert.buttonTitleColor = UIColor(red:0.03, green:0.62, blue:0.09, alpha:1.0)
//MDCAlertControllerThemer.applyScheme(alertScheme, to: alert)
let okayAction = MDCAlertAction(title: "Okay") { (action) in
print("User click okay")
}
let cancelAction = MDCAlertAction(title: "Cancel", handler: nil)
alert.addAction(okayAction)
alert.addAction(cancelAction)
self.present(alert, animated: true, completion: {
// When the Dialog view has pop up on screen then just put this line of code when Dialog view has completed pop up.
alert.view.superview?.subviews[0].isUserInteractionEnabled = false
})
use this.
let alert = MDCAlertController(title: title, message: message)
alert.mdc_dialogPresentationController.dismissOnBackgroundTap = false
https://material.io/develop/ios/components/dialogs/api-docs/Categories/UIViewController_28MaterialDialogs_29.html
https://material.io/develop/ios/components/dialogs/api-docs/Classes/MDCDialogPresentationController.html#/c:objc(cs)MDCDialogPresentationController(py)dismissOnBackgroundTap

Segue performing on separate button click

I'm following a course online, the app I'm building is basically a cut down version of Instagram as you can see on the left hand side I have a view for signing in, on the right hand side I have a view for signing up.
Pretty straight forward right, now I have created a Seque from the signin page from the button Register which when pressed takes you to the registration view (one on the right), this works as expected however if I'm on the signin page and click sign in and it errors for whatever reason I display the error message but straight after is performs the Seque to the registration screen even though I never pressed register
This is my storyboard:
This is my code behind the Sign In button located on the left view
#IBAction func btnSignIn(sender: AnyObject) {
if txtUsername.text == "" || txtPassword.text == "" {
displayAlert("Error", message: "Username and Password required!")
}
}
I have no other code inside this controller which would cause the Seque to initialise.
If someone can shed some light into how I can stop this Seque from happening on Sign In click and only happen when I press Register I'd appreciate it.
Update
After further investigation this seems to be an issue with my alert box I'm displaying. If I comment out the alert box and press signin and let it error yet not display anything then click register which takes me to the view on the right, click sign in and get taken back to view on the left and then press Sign In display the alert box and when it closes it seems like it thinks the view controller that's being displayed in the one previous to the current one on the page, which is odd. This is my alert function:
func displayAlert(title: String, message: String) { // Display alert message to user. Passing in title and message which will be displayed.
if #available(iOS 8.0, *) {
let a = UIAlertController(title: title, message: message, preferredStyle: UIAlertControllerStyle.Alert)
a.addAction((UIAlertAction(title: "OK", style: .Default, handler: { (action) -> Void in
self.dismissViewControllerAnimated(true, completion: nil)
})))
self.presentViewController(a, animated: true, completion: nil)
} else {
// Fallback on earlier versions
}
}
You don't need to dismiss the alert view controller in the action. When the user selects an action, the alert controller is automatically dismissed.
Since you are calling dismissViewControllerAnimated you are going back to the previous view controller.
Right click on Sign-in button and check Connections Inspector, may be you have copy-pasted these two buttons so the action of both sign-in and register button would be same.

iOS: Which function to override when I need to do something after the view is visible to the user?

So the first thing my app does is get a list of movies from an API. I'm trying to handle if there's a network issue. Currently, in my viewDidLoad method, I call "updateApiInfo", which contains the following code:
if self.movies == nil {
print("stuff went wrong")
let alert = UIAlertController(title: "Network Error", message: "There was a nework error.\nYou must be connected to the internet to use Flicks.", preferredStyle: UIAlertControllerStyle.Alert)
alert.addAction(UIAlertAction(title: "Exit", style: UIAlertActionStyle.Default, handler: {(UIAlertAction) in
UIControl().sendAction(Selector("suspend"), to: UIApplication.sharedApplication(), forEvent: nil)
}))
self.presentViewController(alert, animated: true, completion: nil)
}
When viewDidLoad calls updateApiInfo, I get this message:
Warning: Attempt to present <UIAlertController: 0x7fad10e3ad80> on <Flicks.MoviesViewController: 0x7fad10e35cc0> whose view is not in the window hierarchy!
When I call updateApiInfo just from the user refreshing, the error pops up as expected.
I'm assuming that viewDidLoad only gets called before the view gets displayed to the user, which I guess is causing this error. Is there a method I can stick this code in for after the view is displayed to the user, so the problem can presumably get fixed?
You need to use viewDidAppear: to make sure the view is already in the window hierarchy.
There's a pretty good explanation of the order in which the methods are called here:
What is the process of a UIViewController birth (which method follows which)?

Swift UIAlertController file rename action

I am using Swift 2. This question relates to iOS9.
In brief:-
If the file rename button is clicked and the file name is still invalid then do I need to present the alert again or is there a smarter way of handling this?
In full:-
Saving a file imported from iCloud, I am presenting a UIAlertController called alertController if a file with the same name (.lastPathComponent) already exists in /Documents.
The UIAlertController has two actions titled Cancel and Rename and a .addTextFieldWithConfigurationHandler. If the file name already exists then the user is prompted to either cancel or rename.
This question relates to validating the new name and re-presenting (or not dismissing) the UIAlertController until the file name is valid:
If the user clicks the rename button and the file name is still the same, or is the same as another file which already exists, then I want the UIAlertController to be shown again. Or better still it would not be dismissed until the file name is valid.
The way I have done this (the only way I have figured out) is by adding a func named presentAlertController which presents the UIAlertController. This func is called from the rename handler when the button is clicked if the file name already exists (or has not been changed). (The same as simply presenting the UIAlertController again from the action).
My question:-
My code does what I want but can anyone suggest a neater and less clumsy way of achieving this outcome - without having to present the UIAlertController again?
Here is the relevant code. Please note that this whole section is within the completion handler of another function - hence the need for various references to self and why the UIAlertController code is within a dispatch_async(dispatch_get_main_queue()) (has to be called from the main queue).
//...
if checkFileExists(saveURL) { // saveURL: NSURL
dispatch_async(dispatch_get_main_queue()) {
let message = "File named `\(saveURL.lastPathComponent!)` already exists. Please import using a new name or else cancel."
let alertController = UIAlertController(title: "", message: message, preferredStyle: UIAlertControllerStyle.Alert)
alertController.addTextFieldWithConfigurationHandler { textField -> Void in
textField.text = saveURL.lastPathComponent! // it presents a text field containing the file name which needs to be changed
}
func presentAlertController() {
self.presentViewController(alertController, animated: true) {}
}
alertController.addAction(UIAlertAction(title: "Cancel", style: .Cancel, handler: nil))
alertController.addAction(UIAlertAction(title: "Rename", style: .Default) { action -> Void in
saveURL = saveURL.URLByDeletingLastPathComponent!.URLByAppendingPathComponent((alertController.textFields?.first!.text)!)
if checkFileExists(saveURL) {
presentAlertController() // currently it is calling a function to present the UIAlertController again if the file still exists when the button is clicked
} else {
saveXML(saveURL, dataObject: self.myThing)
self.fileTableView.reloadData()
}
})
presentAlertController() // this will be the first time that this called
}
}
//...
So you can add a target to your text field that will be called when the text field is edited, and in that function you can check if the user has typed a valid name. The problem you'll have with that is you'll need access to alertController so you can disable the "Rename" button. You can accomplish that by making a property at the top of your view controller like so:
var alertController: UIAlertController!
then revise the code your posted like so:
//...
if checkFileExists(saveURL) { // saveURL: NSURL
dispatch_async(dispatch_get_main_queue()) {
let message = "File named `\(saveURL.lastPathComponent!)` already exists. Please import using a new name or else cancel."
//just assigning here, not redeclaring (get rid of "let")
alertController = UIAlertController(title: "", message: message, preferredStyle: UIAlertControllerStyle.Alert)
alertController.addTextFieldWithConfigurationHandler { textField -> Void in
textField.text = saveURL.lastPathComponent! // it presents a text field containing the file name which needs to be changed
//Add the target here, calls on checkString
textField.addTarget(self, action: "checkString:", forControlEvents: UIControlEvents.EditingChanged)
}
func presentAlertController() {
self.presentViewController(alertController, animated: true) {}
}
alertController.addAction(UIAlertAction(title: "Cancel", style: .Cancel, handler: nil))
alertController.addAction(UIAlertAction(title: "Rename", style: .Default) { action -> Void in
saveURL = saveURL.URLByDeletingLastPathComponent!.URLByAppendingPathComponent((alertController.textFields?.first!.text)!)
if checkFileExists(saveURL) {
presentAlertController() // currently it is calling a function to present the UIAlertController again if the file still exists when the button is clicked
} else {
saveXML(saveURL, dataObject: self.myThing)
self.fileTableView.reloadData()
}
})
//Disable the "Rename" button to start, remove this line if you don't want that to happen
(alertController.actions as! [UIAlertAction])[1].enabled = false
presentAlertController() // this will be the first time that this called
}
}
//...
Then you'll have to make a checkString function (obviously you can rename this however you like as long as you also change the selector on the line where you add the target to the text field). Here's a little bit of code to give you an idea, but you'll have to write your own stuff here.
func checkString(sender: UITextField) {
//Pretty safe assumption you don't want an empty string as a name
if sender.text == "" {
(alertController.actions as! [UIAlertAction])[1].enabled = false
}
//As soon as the user types something valid, the "Rename" button gets enabled
else {
(alertController.actions as! [UIAlertAction])[1].enabled = true
}
}
This code has been tested, but not very rigorously, so comment if you have problems or if it works.

Loop user input from text boxes in Swift

I have two text boxes in my iOS to-do app, one for the name of the task and the other for the description. When the user leaves one or both of the text boxes blank, I want to alert the user about the problem and loop this until the two text boxes are no longer blank. Here is the code I have:
var validInput :Bool = false //for while
while (validInput == false) {
if (txtTask.text == "" || txtDesc.text == "") {
var alert = UIAlertController(title: "Error", message: "Task and description cannot be blank", preferredStyle: UIAlertControllerStyle.Alert)
alert.addAction(UIAlertAction(title: "Working!!", style: UIAlertActionStyle.Default, handler: nil))
self.presentViewController(alert, animated: true, completion: nil)
} else {
validInput == true
}
}
This code is inside an #IBAction function which runs when the user presses Done. My code runs in an infinite loop and it is pretty obvious why. How can I achieve what I would like?
I have an idea:
User leaves the text boxes blank and presses done.
An alert pops up warning the user.
Skip the rest of the function, and only run the function again when Done is pressed.
How could I a) put the above into code, or b) use a loop like I have above, properly?
The answer is: Don't do this !
You don't need to loop textFields to watch for value changes. The correct way to do this is using using the UITextField's delegate methods like
- textFieldDidBeginEditing: to know when user did begin editing,
- textField:shouldChangeCharactersInRange:replacementString: when the textField text value changes
- textFieldDidEndEditing: to know when the user end editing
etc...
As described on the docs:
https://developer.apple.com/library/ios/documentation/UIKit/Reference/UITextFieldDelegate_Protocol/
Using loops to do this kind of thing is a bad practice in this case. (And you will have to do a lot of stuff to not block the current thread, verify if there is already an Alert on the screen etc)
If you use a while loop in your "done" button, it'll be stuck in an infinite loop as you said. The user gets no chance to change anything because of this.
Instead you should use an if statement to check if those boxes are empty and give them a warning if it is, and do nothing.
if condition {
// Execute your code if both boxes are filled
} else {
// Show alert
}
If you insists on using a while loop, you'll have to let the user to enter the text in your alerts. Then your code would work.
Here no need of while loop. Just do it in a if else loop, because each time you press the Done button after filing text or leaving empty, your piece of code will be executed.
if (txtTask.text == "" || txtDesc.text == "") {
var alert = UIAlertController(title: "Error", message: "Task and description cannot be blank", preferredStyle: UIAlertControllerStyle.Alert)
alert.addAction(UIAlertAction(title: "Working!!", style: UIAlertActionStyle.Default, handler: nil))
self.presentViewController(alert, animated: true, completion: nil)
} else {
//Do something or print.
}
I assume this piece of code is in #IBAction method.
Keep it simple:
#IBOutlet var textA: UITextField!
#IBOutlet var textB: UITextField!
#IBAction func validateButton(sender: AnyObject) {
if (textA.text == "" || textB.text == "") {
println("ALERT: BLANK FIELDS")
} else {
println("Let's run some code since we're not blank")
}
}

Resources