I have a custom designable view class that looks like this:
@IBDesignable
class AuthInputView: UIView {
static let nibName = "AuthInputView"
@IBOutlet weak var mainContainerView: UIView!
@IBOutlet weak var mainStackView: UIStackView!
@IBOutlet weak var titleLabel: UILabel!
@IBOutlet weak var textField: UITextField!
@IBOutlet weak var errorLabel: UILabel!
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
fromNib()
}
override init(frame: CGRect) {
super.init(frame: frame)
fromNib()
}
override func awakeFromNib() {
super.awakeFromNib()
}
}
and a corresponding nib called AuthInputView that has its File's Owner set to AuthInputView.
And I have have a view controller designed in storyboard that has a view, who's class is set to AuthInputView. When I run an application it renders fine, but when I look at it in a storyboard, it looks like this:
Designables are also up to date :
but as can be seen, a custom view is rendered in an incorrect position (top left corner).
The code I use to load from nib and to attach required constraints after a content of a nib is added to a specified view looks like this:
extension UIView {
@discardableResult
func fromNib<T : UIView>() -> T? {
guard let contentView = Bundle(for: type(of: self)).loadNibNamed(String(describing: type(of: self)), owner: self, options: nil)?.first as? T else {
return nil
}
self.addSubview(contentView)
contentView.translatesAutoresizingMaskIntoConstraints = false
contentView.layoutAttachAll(to: self)
return contentView
}
func layoutAttachAll(to childView:UIView)
{
var constraints = [NSLayoutConstraint]()
childView.translatesAutoresizingMaskIntoConstraints = false
constraints.append(NSLayoutConstraint(item: childView, attribute: .left, relatedBy: .equal, toItem: self, attribute: .left, multiplier: 1.0, constant: 0))
constraints.append(NSLayoutConstraint(item: childView, attribute: .right, relatedBy: .equal, toItem: self, attribute: .right, multiplier: 1.0, constant: 0))
constraints.append(NSLayoutConstraint(item: childView, attribute: .top, relatedBy: .equal, toItem: self, attribute: .top, multiplier: 1.0, constant: 0))
constraints.append(NSLayoutConstraint(item: childView, attribute: .bottom, relatedBy: .equal, toItem: self, attribute: .bottom, multiplier: 1.0, constant: 0))
childView.addConstraints(constraints)
}
}
what is causing this misplacement in a storyboard view?
While many people like to use "layout helper" functions, it's easy to get confused...
You are calling your layoutAttachAll
func with:
contentView.layoutAttachAll(to: self)
but in that function, you are doing this:
func layoutAttachAll(to childView:UIView)
{
var constraints = [NSLayoutConstraint]()
constraints.append(NSLayoutConstraint(item: childView, attribute: .left, relatedBy: .equal, toItem: self, attribute: .left, multiplier: 1.0, constant: 0))
...
but you've passed self
as childView
, so you're constraining self
to self
.
If you put your constraint code "inline":
func fromNib<T : UIView>() -> T? {
guard let contentView = Bundle(for: type(of: self)).loadNibNamed(String(describing: type(of: self)), owner: self, options: nil)?.first as? T else {
return nil
}
self.addSubview(contentView)
var constraints = [NSLayoutConstraint]()
contentView.translatesAutoresizingMaskIntoConstraints = false
constraints.append(NSLayoutConstraint(item: contentView, attribute: .left, relatedBy: .equal, toItem: self, attribute: .left, multiplier: 1.0, constant: 0))
constraints.append(NSLayoutConstraint(item: contentView, attribute: .right, relatedBy: .equal, toItem: self, attribute: .right, multiplier: 1.0, constant: 0))
constraints.append(NSLayoutConstraint(item: contentView, attribute: .top, relatedBy: .equal, toItem: self, attribute: .top, multiplier: 1.0, constant: 0))
constraints.append(NSLayoutConstraint(item: contentView, attribute: .bottom, relatedBy: .equal, toItem: self, attribute: .bottom, multiplier: 1.0, constant: 0))
self.addConstraints(constraints)
return contentView
}
you should no longer get the "misplaced" view.
If you really want to use your layoutAttachAll
function, you want to call it with:
self.layoutAttachAll(to: contentView)
and change the last line:
// adding to wrong view
//childView.addConstraints(constraints)
self.addConstraints(constraints)
Maybe worth noting, you can vastly simplify your "helper" extension to:
extension UIView {
@discardableResult
func fromNib<T : UIView>() -> T? {
guard let contentView = Bundle(for: type(of: self)).loadNibNamed(String(describing: type(of: self)), owner: self, options: nil)?.first as? T else {
return nil
}
self.addSubview(contentView)
contentView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
contentView.frame = bounds
return contentView
}
}