1

I'm trying to break up my view controller content into smaller chunks of single views that I load from xib. I'm doing that by placing placeholder UIView objects into my Storyboard view controllers and use them to add the actual UIView subclass that I have laid out in a xib file as a subview at build time.

The process is pretty straightforward and was discussed before.

The problem, however, is that the AutoLayout constraints, defined in the xib file, are not working when I add the view.

To illustrate the problem, I've created a sample project: A ViewController has a colorView of type CustomColorView (subclass of UIView) that will be added to a plain UIView placeholder. The CustomColorViewclass then has a property of coloredView which should fill the out the whole space (defined by AutoLayout constraints to every side in the xib file).

ViewController

enter image description here

NibLoadable protocol:

public protocol NibLoadable {
    static var nibName: String { get }
}

public extension NibLoadable where Self: UIView {

    public static var nibName: String {
        return String(describing: Self.self)
    }

    public static var nib: UINib {
        let bundle = Bundle(for: Self.self)
        return UINib(nibName: Self.nibName, bundle: bundle)
    }

    func setupFromNib() {
        guard let loadedView = Self.nib.instantiate(withOwner: self, options: nil).first as? UIView else { fatalError("Error loading \(self) from nib") }
        loadedView.frame = bounds

        addSubview(loadedView)
        loadedView.translatesAutoresizingMaskIntoConstraints = false
        loadedView.leadingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.leadingAnchor, constant: 0).isActive = true
        loadedView.topAnchor.constraint(equalTo: self.safeAreaLayoutGuide.topAnchor, constant: 0).isActive = true
        loadedView.trailingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.trailingAnchor, constant: 0).isActive = true
        loadedView.bottomAnchor.constraint(equalTo: self.safeAreaLayoutGuide.bottomAnchor, constant: 0).isActive = true
    }
}

When I add my custom view to the plain UIView, I would assume that it applies all the defined constraints from the nib file but it doesn't. The print result should be the same for both view but they are different (coloredView still has it's size from the xib file).

Any ideas on what might go wrong? Thanks for your help!

The whole project is up on GitHub: XibAutoLayoutExample

JanApotheker
  • 1,746
  • 1
  • 17
  • 23

1 Answers1

1

Your view height is correct please add layer.borderColor and layer.borderWidth and check it

func setup() {
    print("CustomColorView bounds: \(bounds)")
    print("coloredView subview bounds: \(coloredView.bounds)")
    self.coloredView.layer.borderColor = UIColor.red.cgColor
    self.coloredView.layer.borderWidth = 1
}

Also if you call setup() in viewDidAppear will show the correct values that is because viewDidAppear is executed after viewDidLayoutSubviews so when your viewController ends its layout correctly your log show the right values

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    colorView.setup()
}

Log

CustomColorView bounds: (0.0, 0.0, 320.0, 227.0)
coloredView subview bounds: (0.0, 0.0, 320.0, 227.0)
Reinier Melian
  • 20,519
  • 3
  • 38
  • 55
  • I agree with you expect that `viewWillAppear` doesn't guarantee that you will have the correct values. You should put that under `viewDidLayoutSubviews` – E-Riddie Feb 16 '18 at 11:45
  • yes thats right but `viewDidAppear` is executed after `viewDidLayoutSubviews` as I think (correct me if I am wrong) so I use `viewDidAppear` not `viewWillAppear` @EridB – Reinier Melian Feb 16 '18 at 11:54
  • 1
    in fact if you put `colorView.setup()` in `viewWillAppear` will get the same wrong values @EridB – Reinier Melian Feb 16 '18 at 11:56
  • 1
    You are right, I read it wrong, I didn't notice it was `viewDidAppear`. On this context it is the right answer! P.S The best practice is to check `viewDidLayoutSubviews` in general as you might have views that you layout manually after the view has appeared! – E-Riddie Feb 16 '18 at 12:00
  • you are right, anyway I had updated my answer with a little further clarification @EridB – Reinier Melian Feb 16 '18 at 12:02
  • Yes, I get the right values in `viewDidLayoutSubviews` too but that does not feel right. I want the view to layout itself correctly without the need for the view controller to update the view's layout. – JanApotheker Feb 16 '18 at 13:25
  • the problem is that the `UIView` will adjust the layout once is displayed and not before @JanApotheker – Reinier Melian Feb 16 '18 at 13:41
  • Is there a way for the `UIView` to know when it is displayed? – JanApotheker Feb 16 '18 at 14:16