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