5

Here is my code:

class CustomAlertAction: UIAlertAction {
    init(title : String) {
        super.init(title: title, style: UIAlertActionStyle.Default) { (action) -> Void in
        }
    }
}

But I got the following compiling error:

Must call a designated initializer of the superclass 'UIAlertAction'

I know the designated initializer of UIAlertAction is init(). But the init(title, style, handler) of UIAlert will not call its designated initializer init()?

Any idea? Thanks

P.S.: Based on the Apple's document:

A designated initializer must call a designated initializer from its immediate superclass.”

Does this mean it's not allowed to inherit UIAlertAction in Swift? It's no problem to do so in Objective-C.

The reason why I want to create a subclass of UIAlertAction is because I want to add a ReactiveCocoa command as an action.

nhgrif
  • 61,578
  • 25
  • 134
  • 173
Bagusflyer
  • 12,675
  • 21
  • 96
  • 179
  • 1
    Why do you think you need to subclass UIAlertAction? – matt Jul 14 '15 at 23:55
  • It's unfortunately, but you can do a workaround, see http://stackoverflow.com/a/25164687/95309 – Claus Jørgensen Jul 15 '15 at 00:02
  • 1
    "The reason why I want to create a subclass of UIAlertAction is because I want to add a ReactiveCocoa command as an action" And you can't do that with an extension? – matt Jul 15 '15 at 00:17
  • Who downvote my post? Isn't my question a valid question? I don't know WTF these guys are doing. Strongly suggest ask them to give the reason. – Bagusflyer Jul 15 '15 at 02:26
  • I ended up to create my own view controller. – Bagusflyer Jul 15 '15 at 03:41
  • Hmm. Interesting problem. The title and style properties are readOnly as well so you can't even call the designated initialiser and just set the properties separately. Seems like an oversight from apple. – villy393 Jul 15 '15 at 09:06

3 Answers3

9

The solution is realistically almost certainly to use class extensions instead.

extension UIAlertAction {
    convenience init(title: String) {
        self.init(title: title, style: .Default, handler: nil)
    }
}

Usage:

let okayAction = UIAlertAction(title: "Okay")

You can still subclass the UIAlertAction to add properties to it. The subclass can still use this convience initializer you extended off of UIAlertAction class.


Finally, for what it's worth, someone has already created a Reactive Cocoa UIAlertAction subclass. They just did it in Objective-C. Seeing how there's no harm in adding Objective-C to your project, you could just take this approach... or just install that pod...

nhgrif
  • 61,578
  • 25
  • 134
  • 173
  • The reason I want to create subclass instead of extension is because I'm going to use some stored properties. Anyway this could be a valid solution if there is no stored properties. Thanks. – Bagusflyer Jul 15 '15 at 01:22
  • 1
    @bagusflyer You can store properties using Objective-C runtime. Or you can subclass, but simply add your initializers in an extension (to `UIAlertAction`). – nhgrif Jul 15 '15 at 01:47
  • This is a smart suggestion. I'll do that. Using extension for init and subclass without init. Thanks. +1 – Bagusflyer Jul 15 '15 at 02:31
  • Still, it's not very elegant. For example, I want to add a viewmodel for a UIAlertController in its init. If I add it in the extension, the interface will be exposed publicly. Am I right? – Bagusflyer Jul 15 '15 at 02:37
  • @bagusflyer Take a look at my final paragraph. Is there a reason you can't use that pod or write your own Objective-C subclass? You could even write an Objective-C subclass that *only* implements the `init` method(s) and then write an extension for that subclass to add any other methods to it you want. – nhgrif Jul 15 '15 at 11:53
1

Like @nhgrif said, using an extension is the way to go. It's a complementary way to write expressiveness code.

Example:

/// App alert actions
extension UIAlertAction {
    static var cancel: UIAlertAction {
        return UIAlertAction(title: "Cancel", style: .Cancel, handler: nil)
    }
    class func sharePhoto(handler: ((UIAlertAction) -> Void)?) -> UIAlertAction {
        return UIAlertAction(title: "Share", style: .Default, handler: handler)
    }
}

Use it like

alertController.addAction(.cancel)

alertController.addAction(.sharePhoto({ action in
    print(action)
}))
Community
  • 1
  • 1
ricardopereira
  • 11,118
  • 5
  • 63
  • 81
0

I came across this post while researching the same problem. It's been a few years since this was posted, so probably not helpful to the OP at this point, but will be for anyone searching this issue.

After seeing this post, I did some further research. It turns out it is in fact possible to subclass UIAlertAction with a custom init at least with the version of Swift used at the time of this writing. I'll answer as if directed to the OP anyways.

You need to define your init as a convenience init, and the one you are currently calling as super.init is also a convenience init. Only designated initializers can be called on super classes (hence the error). I also wanted to reference the UIAlertController that the action is attached to in my use case, so I have an instance variable in play too. I'm also using my own UIAlertController subclass here.

class MyAlertAction: UIAlertAction {
    var alertController: MyAlertController? = nil

    convenience init(
        alertController: MyAlertController,
        title: String?,
        style: UIAlertAction.Style,
        handler: ((UIAlertAction) -> Void)?
    ) {
        // init must be called before instance variables and functions are referenced
        self.init(title: title, style: style, handler: handler)
        self.alertController = alertController
    }
}

The reason this works is because either no designated initializers have been defined in the subclass, or all designated initializers from the superclass have been implemented by the subclass. In these two cases, all of the convenience initializers from the superclass are inherited down (except any with matching signatures in the subclass). This is the only way to call a convenience initializer from a superclass because as mentioned before, super.init is limited to designated initializers.

Because there is no implementation of any designated initializers in my example, the subclass also inherits all designated initializers from the superclass. So by not defining any designated initializers, all of the initializers from the superclass are available as if they were defined in the subclass.

There is a whole write-up on initialization in the Swift book available at https://docs.swift.org/swift-book/LanguageGuide/Initialization.html. Of particular interest in this case are these four subsections:

  1. Initializer Delegation for Class Types: https://docs.swift.org/swift-book/LanguageGuide/Initialization.html#ID219
  2. Two-Phase Initialization: https://docs.swift.org/swift-book/LanguageGuide/Initialization.html#ID220
  3. Initializer Inheritance and Overriding: https://docs.swift.org/swift-book/LanguageGuide/Initialization.html#ID221
  4. Automatic Initializer Inheritance: https://docs.swift.org/swift-book/LanguageGuide/Initialization.html#ID222
theeternalsw0rd
  • 771
  • 5
  • 2