10

I'm tired of declaring entire classes as having the ability to handle UIAlertView clicks by making them extend UIAlertViewDelegate. It starts to feel messy and wrong when I have multiple possible UIAlertViews, and have to distinguish which was clicked in the handler.

What I really want is to create a single object that implements the UIAlertViewDelegate protocol, and give this one-off object to my UIAlertView when showing it.

I want something like this:

let confirmDelegate = UIAlertViewDelegate() {
    func alertView(alertView: UIAlertView!, clickedButtonAtIndex buttonIndex: Int) {
        // Handle the click for my alertView
    }
}

And then use it when showing the alert:

let alertView = UIAlertView(title: "Confirm", message: "Are you sure?", delegate: confirmDelegate, cancelButtonTitle: "No", otherButtonTitles: "Yes")
alertView.show()

Is this possible without declaring a new class?

I understand I could do something like this:

class ConfirmDelegate: UIAlertViewDelegate {
    func alertView(alertView: UIAlertView!, clickedButtonAtIndex buttonIndex: Int) {
        // ...
    }
}

And then instantiate a ConfirmDelegate(), but I'm just wondering if this is possible as one-liner class declaration and instantiation.

Sean Adkinson
  • 8,425
  • 3
  • 45
  • 64
  • 1
    I presume you're targeting iOS 7. But for anyone else running across this and targeting iOS 8. Don't forget about the new `UIAlertController` class that is block based. – Chris Wagner Aug 11 '14 at 16:59
  • 1
    Even if you could do it, what your code shows wouldn't be correct, because delegates of UIAlertView are not retained, and once your local reference disappears at the end of the function, the delegate will be deallocated. – newacct Aug 11 '14 at 18:20
  • @newacct I wasn't aware that delegates are weak references (that's what you are saying?). That could be the reason this behavior doesn't seem to exist. – Sean Adkinson Aug 11 '14 at 21:40
  • @ChrisWagner I need this to work on IOS 7, yes, but I'm still learning IOS development and didn't know about `UIAlertController`, thank you – Sean Adkinson Aug 11 '14 at 21:42
  • This is just about the only thing I've found that Android seems to do more neatly. Though the gradual switch from target/action to blocks seems to obviate that. – Tommy Aug 11 '14 at 22:19
  • If you're open to 3rd party libraries, [BlocksKit](https://github.com/zwaldowski/BlocksKit) has a nice block-based category on `UIAlertView` that implements this behavior. (They don't use anonymous inner classes, though.) – Aaron Brager Aug 11 '14 at 22:42
  • @SeanAdkinson: Yes, in general, all delegates in Cocoa are weak references, with some exceptions like NSURLConnection delegate. – newacct Aug 12 '14 at 00:39

2 Answers2

7

As @ChrisWagner states in his comment, you shouldn't need to do any of this in iOS8, at least for UIAlertView since there is a new UIAlertViewController that uses closures without any delegates. But from an academic point of view, this pattern is still interesting.

I wouldn't use anonymous class at all. I would just create a class that can be assigned as the delegate and accept closures to execute when something happens.

You could even upgrade this to accept a closure for each kind of action: onDismiss, onCancel, etc. Or you could even make this class spawn the alert view, setting itself as the delegate.

import UIKit

class AlertViewHandler: NSObject, UIAlertViewDelegate {
    typealias ButtonCallback = (buttonIndex: Int)->()
    var onClick: ButtonCallback?

    init(onClick: ButtonCallback?) {
        super.init()
        self.onClick = onClick
    }

    func alertView(alertView: UIAlertView!, clickedButtonAtIndex buttonIndex: Int) {
        onClick?(buttonIndex: buttonIndex)
    }
}


class ViewController: UIViewController {

    // apparently, UIAlertView does NOT retain it's delegate.
    // So we have to keep it around with this instance var
    // It'll be nice when all of UIKit is properly Swift-ified :(
    var alertHandler: AlertViewHandler?

    func doSoemthing() {
        alertHandler = AlertViewHandler({ (clickedIndex: Int) in
            println("clicked button \(clickedIndex)")
        })

        let alertView = UIAlertView(
            title: "Test",
            message: "OK",
            delegate: alertHandler!,
            cancelButtonTitle: "Cancel"
        )
    }
}

Passing around closures should alleviate the need for anonymous classes. At least for the most common cases.

njzk2
  • 38,969
  • 7
  • 69
  • 107
Alex Wayne
  • 178,991
  • 47
  • 309
  • 337
  • Fancy syntax. That's definitely closer to what I'm looking for. On the `typealias` line, is that basically declaring a lambda that takes an `Int` and doesn't return anything? And what you are passing into the `AlertViewHandler` constructor, that's the syntax for an anonymous function that satisfies the lambda contract? My terminology and idioms may be off slightly. I'll have to read up on that syntax. – Sean Adkinson Aug 11 '14 at 21:47
  • 1
    Nice approach @AlexWayne, up vote to you! I am curious however if initializing the `AlertViewHandler` at assignment time like that would cause that instance to be deallocated after the runloop exits the `doSomething` scope? Wouldn't it need to be an instance variable to be retained properly? – Chris Wagner Aug 11 '14 at 21:52
  • As for deallocation, `alertView` should retain it's assigned delegate, and release it when it's done. So it should stick around. But if you want to reference the object in other ways, you are totally free to assign it to an instance variable. **EDIT** Appears I'm wrong about that! Might want to save it to an instance variable first. :( – Alex Wayne Aug 11 '14 at 21:55
  • There are also a fair number of libraries that implement this concept and should work with Swift. http://cocoapods.org/?q=alert+block – Brian Nickel Aug 11 '14 at 22:02
  • @AlexWayne maybe use object associations and just connect the object directly to the alert view? https://developer.apple.com/library/mac/documentation/Cocoa/Reference/ObjCRuntimeRef/Reference/reference.html#//apple_ref/c/func/objc_setAssociatedObject (EDIT: that's a reference to objc_setAssociatedObject; I've no idea why I imagined Apple's HTML would work properly) – Tommy Aug 11 '14 at 22:16
  • I don't know anything about that :( – Alex Wayne Aug 11 '14 at 22:40
  • @Tommy You could use an associated object but that could cause a retain cycle. It is probably safe to create a strong reference to the delegate while visible and then remove the reference after it disappears, similar to UIScrollView's behavior. – Brian Nickel Aug 12 '14 at 00:26
  • The questioner wants the alert view to own the proposed anonymous object; I assumed he was aware of potential retain cycle implications. – Tommy Aug 12 '14 at 01:06
  • @Tommy, no, not familiar with retain cycles. I assume that is akin to java's garbage collection, and perhaps in this situation, something wouldn't be able to be cleaned up. Is that what you and @BrianNickel are saying? I.E. if I set an object association from `UIAlertView` to it's delegate with `RETAIN`, I'm burning memory? Hopefully IOS can catch detached circular reference subtrees I assume. If I'm missing a key concept, please let me know (I come from Java, just learning IOS dev). – Sean Adkinson Aug 12 '14 at 20:54
  • @AlexWayne from what I tested, the delegate is not retained. Your solution works because the delegate is an instance variable, but if you declare it in a function, it is collected and not called (at least on iOS9) – njzk2 Feb 03 '16 at 14:41
2

Unfortunately as far as I understand it, no you cannot effectively create anonymous inner classes. The syntax you suggest would be really nice IMO though.

Here is my attempt at getting something close to what you want, it's no where near as clean though.

import UIKit

class AlertViewDelegate: NSObject, UIAlertViewDelegate {
    func alertView(alertView: UIAlertView!, clickedButtonAtIndex buttonIndex: Int) {

    }

    func alertView(alertView: UIAlertView!, didDismissWithButtonIndex buttonIndex: Int) {

    }

    func alertView(alertView: UIAlertView!, willDismissWithButtonIndex buttonIndex: Int) {

    }

    func alertViewCancel(alertView: UIAlertView!) {

    }

    func alertViewShouldEnableFirstOtherButton(alertView: UIAlertView!) -> Bool {
        return true
    }
}


class ViewController: UIViewController {
    var confirmDelegate: AlertViewDelegate?

    func doSoemthing() {
        confirmDelegate = {

            class ConfirmDelegate: AlertViewDelegate {
                override func alertView(alertView: UIAlertView!, clickedButtonAtIndex buttonIndex: Int) {
                    println("clicked button \(buttonIndex)")
                }
            }

            return ConfirmDelegate()
        }()

        let alertView = UIAlertView(title: "Test", message: "OK", delegate: confirmDelegate, cancelButtonTitle: "Cancel")
    }

}
Chris Wagner
  • 20,773
  • 8
  • 74
  • 95
  • I see, so you created a concrete class that implements the protocol, and then you can selectively override the functions you want. Unfortunately it is quite a bit more code, but may be a useful strategy in certain cases. – Sean Adkinson Aug 11 '14 at 21:48
  • Yeah, I'd personally prefer a solution that uses closures. Much like Alex's and many of the third party Objective-C wrappers on `UIAlertView` out there that make them block based. – Chris Wagner Aug 11 '14 at 21:54
  • I can't seem to compile an example like this - an inner class inside a block inside a function. Can you still compile this in Swift 1.0? – bandejapaisa Oct 15 '14 at 12:33