How to Override Convenience Init in Uialertcontroller for Swift

How can I override convenience init in UIAlertController for swift?

you are not overriding any convenience init, it looks like you are creating a new one.

convenience init(title: String?, message: String?, preferredStyle: UIAlertControllerStyle, colorCode: String?){
self.init(title: title, message: message, preferredStyle: preferredStyle)
}

Is probably what you want, you just need to handle the color code

Looks like you are going to have to go a round about way:

First create a create extension

extension UIAlertController
{

class func create(title: String?, message: String?, preferredStyle: UIAlertControllerStyle) -> AnyObject
{
return UIAlertController(title: title, message: message, preferredStyle: preferredStyle);
}
}

Then in the ColorAlertViewController, you will create another function to create this object:

class func createWithColor(title: String?, message: String?, preferredStyle: UIAlertControllerStyle, colorCode: String?) -> AnyObject
{
var c = super.create(title, message: message, preferredStyle: preferredStyle);
//handle color code here
return c;
}

Now anywhere you want to create this object, just call

var colorAlertView = ColorAlertViewController.createWithColor("title Name", message: "the message", preferredStyle: .ActionSheet, colorCode: "the color");

of course this won't work inside the UI builder, you would have to create this via code.

Initialising a subclass of UIAlertController

You should not subclass UIAlertController.

Check this link:

https://developer.apple.com/documentation/uikit/uialertcontroller#//apple_ref/doc/uid/TP40014538-CH1-SW2

you can add methods using extensions but should not subclass it.

Convenience Init Override

The reason override is unneeded:

Conversely, if you write a subclass initializer that matches a superclass convenience initializer, that superclass convenience initializer can never be called directly by your subclass, as per the rules described above in Initializer Chaining. Therefore, your subclass is not (strictly speaking) providing an override of the superclass initializer. As a result, you do not write the override modifier when providing a matching implementation of a superclass convenience initializer.

But as written, it seems it should work — as far as I can tell this is a compiler bug. If you change the name of the array argument to ClassB's initializer to e.g. array2, then it works as expected. You should file a Radar!

Adding Convenience Initializers in Swift Subclass

My understanding of Initializer Inheritance is the same as yours, and I think we are both well aligned with what the book states. I don't think it's an interpretation issue or a misunderstanding of the stated rules. That said, I don't think you're doing anything wrong.

I tested the following in a Playground and it works as expected:

class RectShape: NSObject {
var size = CGSize(width: 0, height: 0)
convenience init(rectOfSize size: CGSize) {
self.init()
self.size = size
}
}

class SquareShape: RectShape {
convenience init(squareOfSize size: CGFloat) {
self.init(rectOfSize: CGSize(width: size, height: size))
}
}

RectShape inherits from NSObject and doesn't define any designated initializers. Thus, as per Rule 1, it inherits all of NSObject's designated initializers. The convenience initializer I provided in the implementation correctly delegates to a designated initializer, prior to doing the setup for the intance.

SquareShape inherits from RectShape, doesn't provide a designated initializer and, again, as per Rule 1, inherits all of SquareShape's designated initializers. As per Rule 2, it also inherits the convenience initializer defined in RectShape. Finally, the convenience initializer defined in SquareShape properly delegates across to the inherited convenience initializer, which in turn delegates to the inherited designated initializer.

So, given the fact you're doing nothing wrong and that my example works as expected, I am extrapolating the following hypothesis:

Since SKShapeNode is written in Objective-C, the rule which states that "every convenience initializer must call another initializer from the same class" is not enforced by the language. So, maybe the convenience initializer for SKShapeNode doesn't actually call a designated initializer. Hence, even though the subclass MyShapeNode inherits the convenience initializers as expected, they don't properly delegate to the inherited designated initializer.

But, again, it's only a hypothesis. All I can confirm is that the mechanics works as expected on the two classes I created myself.

Does Convenience Init need to have same number of parameters as Required Init

There is no correlation in number of parameters between designated (or even required) initializers and convenience initializers.

Convenience initializers are simply those that need to forward initialization to one of designated initializers. How many input parameters will it have is completely dependent on implementation and use-case. It may be more, less or equal.

It is hard to find a simple-enough example to demonstrate all of those but consider something like this:

class HighlightedWordContainer {

let words: [String]
var highlightedWord: String

init(words: [String], highlighted: String) {
self.words = words
self.highlightedWord = highlighted
}

init(words: [String], highlightedIndex: Int) {
self.words = words
self.highlightedWord = words[highlightedIndex]
}

convenience init(singleWord: String) {
self.init(words: [singleWord], highlighted: singleWord)
}

convenience init(word1: String, word2: String, word3: String, highlighted: String) {
self.init(words: [word1, word2, word3], highlighted: highlighted)
}

convenience init(wordsSeparatedByWhitespace: String, highlightedIndex: Int) {
let words = wordsSeparatedByWhitespace.components(separatedBy: .whitespaces)
self.init(words: words, highlightedIndex: highlightedIndex)
}

convenience init?(descriptor: [String: Any], keys: [String], selectedKey: String) {
let words: [String] = keys.compactMap { descriptor[$0] as? String }
guard words.isEmpty == false else { return nil }

guard let selectedWord = descriptor[selectedKey] as? String else { return nil }
guard let selectedWordIndex = words.firstIndex(of: selectedWord) else { return nil }

self.init(words: words, highlightedIndex: selectedWordIndex)
}

}

Here I created a class with 2 designated initializers. These two need to set all properties that are not already set by default. Which means they need to set both words and highlightedWord. A designated initializer may not delegate a call to another designated initializer so the following will not work:

init(words: [String], highlightedIndex: Int) {
self.init(words: words, highlighted: words[highlightedIndex])
}

And all convenience initializers do need to call any of the designated initializers and may also not directly set or use self properties UNTIL a designated constructor is being called. So:

convenience init(wordsSeparatedByWhitespace: String, highlightedIndex: Int) {
let words = wordsSeparatedByWhitespace.components(separatedBy: .whitespaces)
// print(highlightedWord) // NOT OK!
self.init(words: words, highlightedIndex: highlightedIndex)
print(highlightedWord)
}

And from the example I hope it makes clear that a convenience initializer can have more, fewer or same number of input parameters. It all just depends.

Some more plausible examples:

class Point {
let x: Int
let y: Int

init(x: Int, y: Int) { self.x = x; self.y = y }
convenience init(x: Int) { self.init(x: x, y: 0) }
convenience init(y: Int) { self.init(x: 0, y: y) }
convenience init(polar: (radius: Double, angle: Double)) { self.init(x: Int(cos(polar.angle)*polar.radius), y: Int(sin(polar.angle)*polar.radius)) }
}

class NumericValueAsString {
let stringValue: String // A value represented as "123.456"

init(stringValue: String) { self.stringValue = stringValue }
convenience init(value: Int) { self.init(stringValue: .init(value)) }
convenience init(integerPart: String, fractionPart: String) { self.init(stringValue: integerPart + "." + fractionPart) }
}

Also; required keyword has nothing to do with anything in this context. You can place it to any of the initializers (designated or convenience), to multiple of them or even all of them.

Adding additional info based on a comment:

your examples shows that you can have a convenience init that can pass
parameters back to the required init``. what about instances where I
there is no convenience init``` that can pass a value to parameter to
one which is in required init but not in the convenience init. Like my
speed parameter in my original question.

This really depends on the interface of your class which is not fully shown in your example. For instance, is speed publicly exposed?

If speed is public then what you are looking for is something along the lines

let itemInstance = MyItem()
itemInstance.timeStamp = timeStamp
itemInstance.position = position
itemInstance.distance = distance
itemInstance.speed = speed

or

let itemInstance = MyItem(timeStamp: timeStamp, position: position, distance: distance)
itemInstance.speed = speed

or even create a new convenience constructor

extension MyItem {
convenience init(timeStamp: FitTime? = nil,
position: Position? = nil,
distance: Measurement<UnitLength>? = nil
speed: Speed? = nil
) {
self.init(timeStamp: timeStamp, position: position, distance: distance)
self.speed = speed
}
}

let itemInstance = MyItem(timeStamp: timeStamp, position: position, distance: distance, speed: speed)

But if your speed property is privately defined than you can not assign to it anywhere but internally. You need to be able to modify your class so that it can accept this property somewhere. Either via constructor, via property or via a method. Until there is no access at all you can not expect to be able to modify it.

But if this is your own class I would turn things upside down if possible. Your main constructor should be the one that accepts all 4 parameters. But your convenience initializer should be required (Yes, convenience initializer may be required). So in that case you would have:

public required convenience init() {
self.init(timeStamp: self, position: self, distance: self, speed: self)
}

public init(timeStamp: FitTime? = nil,
position: Position? = nil,
distance: Measurement<UnitLength>? = nil,
speed: Speed?) {
super.init()

self.timeStamp = timeStamp
self.position = position
self.distance = distance
self.speed = speed
}
}

This specific case feels like it is missing something compared to your code but again, you did not provide full definition of your class and with it does or is supposed to do. For instance in the code you provided if one would call a convenience initializer then the following lines would execute in this order:

self.$timeStamp.owner = self // from self.init()
self.timeStamp = timeStamp

which looks like a problem to begin with.

So to wrap it up. There are very many ways you can achieve what you are asking for. But it all depends on what you are trying to build here.

I hope it at least puts you in the right track.

Initializer does not override a designated initializer from its superclass

My solution is a quick fix, but I think is easier than what Apple purposes on the the Release Notes. For more information search for 19775924 http://adcdownload.apple.com//Developer_Tools/Xcode_6.3_beta_3/Xcode_6.3_beta_3_Release_Notes.pdf here. What Apple says is that you create an Objective-C file and extend it (having to add it to the header files and all) and it's on "Known Issues in Xcode 6.3 beta 3", so I think is easy to do what I did:

This is how I fixed it for UIButton:

class CustomButton : UIButton {
init() {
super.init(frame: CGRectZero)
}

required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

And this is one of my ViewControllers (remove public if not needed):

public class GenericViewController: UIViewController {
public init() {
super.init(nibName: nil, bundle: nil)
}

required public init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

I don't use IB so I also have UIView, because I do separate the view from the viewController (remove public if not needed):

public class GenericMenuView: UIView {
public init() {
super.init(frame: CGRectZero)
}

public required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

I need this specially in views because I have a setupViews method that I override in all subclasses that is called on the init. And using AutoLayout I don't need any frames (so I don't override the init with the frame parameter).

So it seems you have to drop override. Oh! and be sure to not call self.init() or the class is never initialized (and it crashes after some internal timeout).



Related Topics



Leave a reply



Submit