I have a hard time understanding design choices when Apple introduces new convenience initializers in their API. I always thought that convenience init is a way to initialize an object with fewer or more suitable params. So, designated init should be able to do all the staff that can do convenience inits.
But Apple APIs don't follow that principle. There are convenience inits with unique capabilities, that can't be used without the usage of that init.
Examples
UIButton init(type: UIButton.ButtonType)
UITextView init(usingTextLayoutManager: Bool)
Now, I have a problem - if I want to subclass those classes, I can't create a default init and call that convenience init. Of course I can create a static method or another convenience init that will return the object. By the way, the latter can lead to endless recursion if it overrides designated init of the base class. But there will be problems if I want to assign let constants, also I would be forced to change an interface of my class to change this internal implementation detail.
Of course you can argue that subclassing is not good, but subclassing is a part of Swift and UIKit and those classes (UIButton, UITextView) are not final and are subclassed in many cases, including some popular third-parties. So I don't see what is the Apple's logic to create such constraints as providing us with this 'unique' convenience inits.
Those are not convenience initialisers in obj-c:
+ (instancetype)buttonWithType:(UIButtonType)buttonType;
+ (instancetype)textViewUsingTextLayoutManager:(BOOL)usingTextLayoutManager;
They are converted into convenience initialisers when converted to swift.
Whether this is good or bad practice to use static functions instead of initialisers aside (it is bad), you just have to cope with it.
Possible workaround:
class MyButton: UIButton {
private var type: UIButton.ButtonType = UIButton.ButtonType.custom
convenience init(buttonType: UIButton.ButtonType) {
self.init(frame: .zero)
type = buttonType
}
override var buttonType: UIButton.ButtonType {
type
}
}
MyButton(buttonType: .custom)
class MyTextView: UITextView {
required override init(frame: CGRect, textContainer: NSTextContainer?) {
super.init(frame: frame, textContainer: textContainer)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
convenience init(frame: CGRect, useTextKit2: Bool) {
var container: NSTextContainer?
if useTextKit2 {
let storage = NSTextStorage()
let manager = NSLayoutManager()
container = NSTextContainer()
storage.addLayoutManager(manager)
manager.addTextContainer(container!)
}
self.init(frame: .zero, textContainer: container)
}
}
MyTextView(frame: ..., useTextKit2: true)
Yes, I understand, also in this case static method and convenience init are more or less of the same nature (with the same restrictions).
It was just strange that such APIs dd not just exist, but also keep coming (textLayoutManager init was added just in 2021 I think). I thought there're some hidden idea behind this.
Looks like whoever is doing it doesn't care about Swift subclass usage
Subclassing from Obj-C is not a problem:
[MyButton buttonWithType: UIButtonTypeSystem]; // MyButton
[MyTextView textViewUsingTextLayoutManager:true]; // MyTextView
This brings up a question of its own: wouldn't it be better for swift to import those declarations as normal (designated / required) initialisers?