Thampuran
Thampuran

Reputation: 654

UIAlertController 'UIAlertAction' tag/userdata or anything in Swift

in my iOS actionsheet, I am showing Names from the JSON dictionary:

[
  { "Name": "Doctor for Disease AAA",
    "Doctor_id": "21"
  },
  { "Name": "Doctor for Disease BBB",
    "Doctor_id": "22"
  },
  { "Name": "Doctor for Disease AAA",
    "Doctor_id": "25"
  }
]

So, on button click delegate, I can get the button index and can fetch the corresponding 'Name' and 'Doctor_id'. This is working fine.

But now it seems like 'UIActionSheet' is deprecated, and I have to use 'UIAlertController'. As I have a large data, I am iterating through my array values and calling the alertcontroller handler (so a single function for all button click). But how can I get the button index from UIAlertController, so that I can fetch the 'Name' and 'Doctor_id' simultaneously.

Please help me.

Upvotes: 10

Views: 7914

Answers (6)

Kamen Dobrev
Kamen Dobrev

Reputation: 1371

Instead of using indices I find it easier and more expressive to use a custom UIAlertAction class

class MyAlertAction:UIAlertAction {
    var key:String = ""
    convenience init(key:String, title:String, handler:((UIAlertAction) -> ())?) {
        self.init(title: title, style: .default, handler: handler)
        self.key = key
    }
}

in your action handler you get the key like this:

let actionHandler: (UIAlertAction) -> () = { (action) in
    if let key = (action as? MyAlertAction)?.key {
         //use the key
    }
}

finally use your new class to add an action

alert.addAction(MyAlertAction(key:"doctor32", title: "Title", handler: actionHandler))

Upvotes: 1

EdFunke
EdFunke

Reputation: 605

You may call indexOf(element: Self.Generator.Element) on your collection of actions from your controller.

Something like:

let actionSheetController = UIAlertController(title: "Title", message: "Select Title", 
                                              preferredStyle: UIAlertControllerStyle.ActionSheet);

for entry in data {
   let myAction = UIAlertAction(title: "title: \(entry)", style: UIAlertActionStyle.Default) { (action) -> Void in
      if let alertIndex = actionSheetController.actions.indexOf(action) {
        print("actionIndex: \(alertIndex)")
      }
   }
   actionSheetController.addAction(myAction)
}

Upvotes: 4

user3615509
user3615509

Reputation: 201

You can solve the problem as following:

let arraySelect = ["NewYork", "Washington", "Seoul", "Tokyo", "Peking", "Sidney", ... ]

let alert = UIAlertController(title: nil, message: nil, preferredStyle: .Alert)

let closure = { (action: UIAlertAction!) -> Void in 
    let index = alert.actions.indexOf(action)
    if index != nil {
        NSLog("Index: \(index!)") 
    }
}

for var i = 0; i < arrayBibleVersions.count; i++ { alert.addAction(UIAlertAction(title: arrayBibleVersions[i][1], style: .Default, handler: closure)) }

alert.addAction(UIAlertAction(title: "cancel", style: .Cancel, handler: {(_) in }))

self.presentViewController(alert, animated: false, completion: nil)

Upvotes: 7

AliSoftware
AliSoftware

Reputation: 32681

You have multiple possibilities here.

You can use find to get the UIAlertAction index

find let you find the index of an object in an array. You can use it to find the index of the action (that is passed as the parameter of the UIAlertAction's handler, which is the UIAlertAction itself) in the alert.actions array of all actions.

let alert = UIAlertController(title: "Doctors", message: "Choose a doctor", preferredStyle: .ActionSheet)
let closure = { (action: UIAlertAction!) -> Void in
    let index = find(alert.actions as! [UIAlertAction], action)
    println("Index: \(index)")
}
alert.addAction(UIAlertAction(title: "Doc1", style: .Default, handler: closure))
alert.addAction(UIAlertAction(title: "Doc2", style: .Default, handler: closure))
alert.addAction(UIAlertAction(title: "Doc3", style: .Default, handler: closure))
alert.addAction(UIAlertAction(title: "Doc4", style: .Default, handler: closure))
alert.addAction(UIAlertAction(title: "Cancel", style: .Cancel) { _ in
    println("User cancelled.")
})
self.presentViewController(alert, animated: true) {}

You can create a closure… that returns a closure

Create a closure that takes a parameter of your choice (here an Int) and return a closure that captures that parameter so you can use it

 let alert = UIAlertController(title: "Doctors", message: "Choose a doctor", preferredStyle: .ActionSheet)
 let closure = { (index: Int) in
     { (action: UIAlertAction!) -> Void in
         println("Index: \(index)")
     }
 }
 alert.addAction(UIAlertAction(title: "Doc1", style: .Default, handler: closure(0)))
 alert.addAction(UIAlertAction(title: "Doc2", style: .Default, handler: closure(1)))
 alert.addAction(UIAlertAction(title: "Doc3", style: .Default, handler: closure(2)))
 alert.addAction(UIAlertAction(title: "Doc4", style: .Default, handler: closure(3)))
 alert.addAction(UIAlertAction(title: "Cancel", style: .Cancel) { _ in
     println("User cancelled.")
 })
 self.presentViewController(alert, animated: true) {}

This way you have a function (closure) that generate closures for your UIAlertAction handlers, all with the same body except that they capture a different object (a different Int here).

What is really great with this solution is that you can capture anything. You can even capture an hypothetic Doctor object that represent your doctor, or directly the doctor ID, etc.!

Use a loop

But generally you will add your actions using a for loop, so why not take advantage of that, plus take advantage of closure and the fact that they capture variables, to make a nice function that will directly tell your the selected doctor's ID?

func testMyAlert() {
    let doctors = [
        ["Name": "Doctor for Disease AAA", "Doctor_id": "21"],
        ["Name": "Doctor for Disease BBB", "Doctor_id": "22"],
        ["Name": "Doctor for Disease AAA", "Doctor_id": "25"]
    ]

    chooseDoctor(doctors) { selectedDocID in
        if let docID = selectedDocID {
            println("User selected doctor with ID \(docID)")
        } else {
            println("User cancelled, no doctor selected")
        }
    }
}

func chooseDoctor(doctors: Array<[String:String]>, completion: Int?->Void) {
    let alert = UIAlertController(title: "Doctors", message: "Choose a doctor", preferredStyle: .ActionSheet)
    for doc in doctors {
        let action = UIAlertAction(title: doc["Name"]!, style: UIAlertActionStyle.Default) { _ in
            // On selecting this action, get the doctor's ID, convert it to an Int, and return that.
            completion(doc["Doctor_id"]?.toInt())
        }
        alert.addAction(action)
    }
    alert.addAction(UIAlertAction(title: "Cancel", style: UIAlertActionStyle.Cancel) { _ in completion(nil) } )
    self.presentViewController(alert, animated: true) {}

}

Upvotes: 17

Duncan C
Duncan C

Reputation: 131471

You add buttons to a UIAlertController by creating one or more UIAlertAction objects and attaching it to the alert controller. Each alert action has a title (used to create it's button) and a closure that gets called when the user taps that button.

Are you saying you want to use the same closure for all of your buttons? I see a number of options

  1. You could write unique closures for each alert action where the closure simply invokes a shared function and passes in a button number.

  2. Alternatively, a UIAlertAction's closure gets passed in the title of the button that was tapped. You could use that to decide which button was tapped. (This is fragile because the button titles might be localized, and are subject to change for UI reasons. It's bad when UI changes break code.)

  3. As a final option you could use associative storage to attach an object to each UIAlertAction and use that to figure out which button was tapped.

My suggestion is option 1. It's simple and clean.

Upvotes: 0

kandelvijaya
kandelvijaya

Reputation: 1585

I suggest you pass the closure to call when you need it. Its on demand. Maybe something like this would help.

var alert = UIAlertController(title: "Title", message: "do this", preferredStyle: UIAlertControllerStyle.ActionSheet)
    var action = UIAlertAction(title: "Doc1", style: UIAlertActionStyle.Default) { (action) -> Void in
        //do some work here
    }

But again, i guess you are not showing more than 4 items on the action sheet because it bad for design. closure might be a good idea. Suggest me if i get it wrong, i'd love to help you out. Cheers.

Upvotes: 0

Related Questions