Norway


I’ve recently started diving into SWIFT/iOS development again and am now looking into a custom UIControl. For layout and flexibility this custom view is built using a StackView.

What I want to achieve …

… is basically to simply have the same functionality with the StackView that I usually have when I drag it directly into the storyboard – where it resizes to fit no problem. This essentially the same as self-sizing tableview cells.

Showcase

I can reduce my CustomView to a simple example: It’s a StackView with three labels in it, Alignment set to Center, Distribution Equal Centering. The StackView would be pinned to left, right and layout guides (or trailing, leading and ). This I can easily re-create with a CustomView, loaded from XIB translate into a CustomView with the same parameters – I attached both as screenshot.

Screenshot of Simple StackView

Screenshot of CustomView

Loading the XIB File and setting it up.

import UIKit

@IBDesignable
class CustomView: UIView {

    // loaded from NIB
    private weak var view: UIView!

    convenience init() {
        self.init(frame: CGRect())
    }

    override init(frame: CGRect) {
        super.init(frame: frame)
        self.loadNib()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        self.loadNib()
    }

    override func layoutSubviews() {
        super.layoutSubviews()

        // we need to adjust the frame of the subview to no longer match the size used
        // in the XIB file BUT the actual frame we got assinged from the superview
        self.view.frame = bounds
    }

    private func loadNib ()  {
        self.view = Bundle (for: type(of: self)).loadNibNamed(
            "CustomView", owner: self, options: nil)! [0] as! UIView
        self.addSubview(self.view)
    }
}

What you can see here is that I simply load the XIB file, override layout Subviews to adapt the frame to the actual view and that’s it.

Questions

So my questions are related to how this is usually approached. The labels in my StackView have an intrinsic content size, i.e., usually the StackView simply adapts to the height of the labels without complaining – IF you design it in the storyboard. It doesn’t though, if you put all of it in a XIB and leave the layout as it is – the IB will complain about an unknown height if the view is pinned three ways as in the screenshot (top, leading, trailing).

I read the Auto-Layout Guide and watched the WWDC videos “Mysteries of AutoLayout” but the topic is still quite new to me and I don’t think I got the right approach yet. So this is where I need help

(1) Should I set constraints and disable translatesAutoresizingMaskIntoConstraints ?

What I first could and should do is to set translatesAutoresizingMaskIntoConstraints false such that I don’t get auto-generated constraints and define my own constraints. So I could change loadNib into this:

private func loadNib ()  {
    self.view = Bundle (for: type(of: self)).loadNibNamed(
        "CustomView", owner: self, options: nil)! [0] as! UIView
    self.view.translatesAutoresizingMaskIntoConstraints = false
    self.addSubview(self.view)

    self.addConstraint(NSLayoutConstraint (
        item: self
        , attribute: .bottom
        , relatedBy: .equal
        , toItem: self.view
        , attribute: .bottom
        , multiplier: 1.0
        , constant: 0))

    self.addConstraint(NSLayoutConstraint (
        item: self
        , attribute: .top
        , relatedBy: .equal
        , toItem: self.view
        , attribute: .top
        , multiplier: 1.0
        , constant: 0))

    self.addConstraint(NSLayoutConstraint (
        item: self.view
        , attribute: .leading
        , relatedBy: .equal
        , toItem: self
        , attribute: .leading
        , multiplier: 1.0
        , constant: 0))

    self.addConstraint(NSLayoutConstraint (
        item: self.view
        , attribute: .trailing
        , relatedBy: .equal
        , toItem: self
        , attribute: .trailing
        , multiplier: 1.0
        , constant: 0))
}

Basically I did stretch the view to fit the full width of my UIView in the Storyboard and restrained it’s top and bottom to top and bottom of the StackView.

I had tons of problems with XCode 8 crashing on me when using this one so I’m not quite sure what to make of it.

(2) Or should I override intrinsicContentSize ?

I could also simply override intrinsicContentSize by checking the height of all my arrangedSubviews and take the one that is the highest, set my view height to it:

override var intrinsicContentSize: CGSize {
    var size = CGSize.zero

    for (_ , aSubView) in (self.view as! UIStackView).arrangedSubviews.enumerated() {
        let subViewHeight = aSubView.intrinsicContentSize.height
        if  subViewHeight > size.height {
            size.height = aSubView.intrinsicContentSize.height
        }
    }

    // add some padding
    size.height += 0
    size.width = UIViewNoIntrinsicMetric
    return size
}

While this surely gives me more flexibility I’d also have to invalidate the intrinsic content size, check the Dynamic Type and lots of other stuff I’d rather avoid.

(3) Am I even loading the XIB in the “proposed” way ?

Or is there a better way, e.g. by using UINib.instantiate? I really couldn’t find a best-practice way to do that.

(4) Why do I have to do this in the first place?

Really, why? I only moved the StackView into a CustomView. So why does the StackView stop adapting itself now, why do I have to set the top and bottom constraints?

What was helping the most so far is that -phrase in https://stackoverflow.com/a/24441368 :

“In general, auto layout is performed in a top-down fashion. In other
words, a parent view layout is performed first, and then any child
view layouts are performed.”

.. but I’m really not so sure about it.

(5) And why do I have to add the constraints in code?

Assuming I disable translatesAutoresizingMaskIntoConstraints then I can’t set constraints like for tableview cells in IB? It’ll always translate into something like label.top = top and thus the label would be stretched instead of the StackView inheriting the size/height. Also I can’t really pin the StackView to it’s container view in the IB for the XIB file.

Hope someone can help me out there. Cheers!



Source link

No tags for this post.

LEAVE A REPLY

Please enter your comment!
Please enter your name here