TL:DR Paste this into Swift playground:
import UIKit
public protocol NibLoadable {
static var nibName: String { get }
}
extension NibLoadable where Self: UIView {
public static var nibName: String {
return String(describing: self)
}
func printName(){
print(Self.nibName)
}
}
public class Shoes: UIView, NibLoadable {
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
printName()
}
override init(frame: CGRect) {
super.init(frame: frame)
printName()
}
}
public class Cake: Shoes {
}
let cake = Cake() // this line prints 'Shoes'
How do i make it print Cake
, not Shoes
?
Full explanation:
I have found the method to get xib files loaded into storyboards like so: https://stackoverflow.com/a/47295926/2057955
This works amazingly, however i have to put boilerplate NibLoadable
code into each view like so:
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupFromNib()
}
override init(frame: CGRect) {
super.init(frame: frame)
setupFromNib()
}
this is obviously pretty annoying when you have some 20-30 views in this manner.
So i am trying to setup a base class that would take care of this, and then have everything inherit it.
Unfortunately and no matter what i tried, the String(describing: Self.self)
always returns the name of my base class, NOT the name of the derived class.
I get this exception in the console logs when i open my Storyboard in XCode:
[MT] IBSceneUpdate: [scene update, delegate=] Error Domain=IBMessageChannelErrorDomain Code=3 "Failed to communicate with helper tool" UserInfo={NSLocalizedFailureReason=The agent raised a "NSInternalInconsistencyException" exception: Could not load NIB in bundle: 'NSBundle (loaded)' with name 'BaseNibLoadable.Type', IBMessageSendChannelException=Could not load NIB in bundle: 'NSBundle (loaded)' with name 'BaseNibLoadable.Type', NSLocalizedDescription=Failed to communicate with helper tool}
Example:
@IBDesignable
class BaseNibLoadable: UIView, NibLoadable {
// MARK: - NibLoadable
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupFromNib()
}
override init(frame: CGRect) {
super.init(frame: frame)
setupFromNib()
}
}
@IBDesignable
class SomeHeader: BaseNibLoadable {
@IBOutlet fileprivate weak var headerLabel: UILabel!
@IBInspectable var header: String? {
didSet {
self.headerLabel.text = header
}
}
}
Obviously my main bundle (same bundle for both) includes the SomeHeader.xib
file.
So what i am looking for is to somehow get the name of the actual type being instantiated in the nibName
static method.
UPDATE As requested, here is the original answer i linked at the top of the question (rather, just the code from it as it is quite long otherwise... please follow the link):
public protocol NibLoadable {
static var nibName: String { get }
}
public extension NibLoadable where Self: UIView {
public static var nibName: String {
return String(describing: Self.self) // defaults to the name of the class implementing this protocol.
}
public static var nib: UINib {
let bundle = Bundle(for: Self.self)
return UINib(nibName: Self.nibName, bundle: bundle)
}
func setupFromNib() {
guard let view = Self.nib.instantiate(withOwner: self, options: nil).first as? UIView else { fatalError("Error loading \(self) from nib") }
addSubview(view)
view.translatesAutoresizingMaskIntoConstraints = false
view.leadingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.leadingAnchor, constant: 0).isActive = true
view.topAnchor.constraint(equalTo: self.safeAreaLayoutGuide.topAnchor, constant: 0).isActive = true
view.trailingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.trailingAnchor, constant: 0).isActive = true
view.bottomAnchor.constraint(equalTo: self.safeAreaLayoutGuide.bottomAnchor, constant: 0).isActive = true
}
}
Update 2
I do know that the subclass is definitely in the stack at that point because if i log the Thread.callStackSymbols
the actual concrete view class name is there.