0

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:

enter image description here

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
    }
}
//...
simons
  • 2,374
  • 2
  • 18
  • 20

1 Answers1

2

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.

The Beanstalk
  • 798
  • 1
  • 5
  • 20
  • Voted you up. Thanks for taking the time to provide a good answer. 2 problems, I think, in this context: For every key press `UIControlEvents.EditingChanged` and its target are called. That means - `checkFileExists(saveURL)` for every keypress - which seems messy. Also means changing the scope of `saveURL` - because the target function needs, I think, to be outside of the completion handler. FYI - for anyone reading this later, today the current syntax is `textField.addTarget(self, action: Selector("checkString:"), forControlEvents: UIControlEvents.EditingChanged)` - could change again! – simons Sep 09 '15 at 21:30