Let's say I have a custom UIView subclass with a designated initializer:
class MyView: UIView {
init(custom: String) {
super.init(frame: .zero)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
As expected I cannot call MyView(frame: .zero) as it's not automatically inherited from UIView.
Then suppose I have a view builder class:
class Builder<V: UIView> {
func build() -> V? {
// Check if can call init(frame:) or not
if V.instancesRespond(to: #selector(V.init(frame:))) {
return V.init(frame: .zero) // <-- Crash here, because the check doesn't work
} else {
return nil
}
}
}
Here I checked first if the custom view V has init(frame:) or not, if yes, then call it.
However, it doesn't work, Builder<MyView>().build() will crash with:
Fatal error: Use of unimplemented initializer 'init(frame:)' for class '__lldb_expr_14.MyView'
V.instancesRespond(to: #selector(V.init(frame:))) always returns true, making this check useless. (Or did I use it incorrectly?)
Question: How do I check if generic view class V actually responds to init(frame:)?
Updated 1:
I also tried V.responds(to: #selector(V.init(frame:))) as @kamaldeep and @Pranav have pointed out. However, it always returns false no matter I override init(frame:) on MyView or not.
Updated 2: What I'm trying to do:
To be more clear, I'm building a framework that automatically initializes UIView from this enum:
enum ViewBuildMethod {
case nib
case nibName(String)
case frame(CGRect)
case custom(() -> UIView)
}
and that view to be used with the framework must adopt this protocol and specify how to build:
protocol SomeViewProtocol where Self: UIView {
// ... other funcs
static func buildMethod() -> ViewBuildMethod
}
The issue is that I want the override init(frame:) to be optional and allows a custom designated initializer (like in MyView). Then, emit fatalError with an error message when the init(frame:) is used (indirectly) on a view that hasn't overridden it yet. This indicates an illegal use of the framework (e.g., MyView's buildMethod returns .frame(.zero)), that can't be checked in compile time:
class MyView: UIView, SomeViewProtocol {
init(custom: String) {
super.init(frame: .zero)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
// ILLEGAL!!
static func buildMethod() -> ViewBulidMethod {
return .frame(.zero) // <-- Illegal, the framework will call V.init(frame: .zero) eventually
}
// LEGAL
// static func buildMethod() -> ViewBuildMethod {
// return .custom({ () -> UIView in
// return MyView(custom: "hello")
// })
// }
}
The error message could be sth like V.init(frame:) is called indirectly but V doesn't override this designated initializer. Please override init(frame:) to fix this issue.
I can let it crash like above, but it will be more clear if I can add some meaningful message before that happens.
Since Swift doesn't automatically inherit from UIView it will not be possible to check if your your class implement init(frame:) constructor.
I suppose instancesRespond(to:) return true, because it's checking if your class conform to this message with Message dispatching. However Swift uses Table dispatching for method declared in classes. So this checking will work with Objective-C but not with Swift
So, to achieve what you want, you can use protocol
create protocol Buildable which will have init(frame: CGRect) method
protocol Buildable {
init(frame: CGRect)
}
conform your class to this protocol
class MyView: UIView, Buildable {...}
now init(frame:) method will be required for your class. Change generic type of you Builder class to Buildable
Now your class could be like this
class Builder<V: Buildable> {
func build() -> V? {
return V(frame: .zero)
}
}
and now when you will be able to build your view Builder<MyView>().build(), and if you class will be not confirm to Buildable protocol you will get compile-time error:
class SecondView: UIView {
init(custom: String) {
super.init(frame: .zero)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
Builder<SecondView>().build()
will throw compile-time error:
error: type 'SecondView' does not conform to protocol 'Buildable'
Builder<SecondView>().build()
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With