1

I am trying to load a UIStackView from a xib that contains multiple labels into a parent UIStackView. The labels in the child stack get populated with values (or hidden) after the ViewController in the viewDidLoad method via a model object.

I expect that the parent stackview would recognize the change in intrinsic height of the child stackview, and thus move sibling views down. However, the next view (button) covers the content of the child stack. Why does the parent not recognize this change to a subview's height? I am not seeing any error messages, ambiguous constraints, or conflicts.

Here is how the error renders and how the views appear in the View Hierarchy. enter image description here

I have tried:

  • Adding a UIView wrapper around the child stack view to see if the parent would register the change in it's height.
  • Moved ALL the code into the parent view controller, avoiding xib loading and that does appear to work, at the cost of losing modularity and separation of concerns.
  • Added optional height constraints on the child stackview based on other article recommendations, resulting in the positions not flexing with the content, or not resolving the original issue.

Here is the storyboard layout Storyboard showing parent stack (master stack) and child (Info View)

Parent View Controller

class AcceptTermsViewController: RegistrationViewController {
    
    @IBOutlet weak var infoView: StackViewInBorderedView!
    @IBOutlet weak var termsView: TermsTextView!
    @IBOutlet weak var masterStack: UIStackView!
    
    var registration: Registration?

    override func viewDidLoad() {
        super.viewDidLoad()
        self.configView()
        self.setupNavBar()
    }

    func configView() {
        if let reg = registration {
            infoView.configView(reg)
        }
    }
}

Child StackView Code

class StackViewInBorderedView: UIStackView {
    // loaded from NIB
    private weak var view: UIView!
    @IBOutlet weak var nameLabel: UILabel!
    @IBOutlet weak var titleLabel: UILabel!
    @IBOutlet weak var emailLabel: UILabel!
    @IBOutlet weak var locationLabel: UILabel!
    @IBOutlet weak var postalLabel: UILabel!
    @IBOutlet weak var idLabel: UILabel!
    @IBOutlet weak var npiLabel: UILabel!
    
    
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        loadViewFromNib()
    }
    
    required init(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        loadViewFromNib()
    }
    
    fileprivate func loadViewFromNib() {
        self.view = Bundle (for: type(of: self)).loadNibNamed(
            "StackViewInBorderedView", owner: self, options: nil)! [0] as? UIView
        view.frame = bounds
        view.autoresizingMask = [.flexibleWidth]
        self.addSubview(view)
    }
    
    override func layoutSubviews() {
        super.layoutSubviews()
        self.layer.cornerRadius = 10
        let shadowLayer = CAShapeLayer()
        shadowLayer.path = UIBezierPath(roundedRect: self.bounds, cornerRadius: self.frame.height / 12).cgPath
        shadowLayer.fillColor = UIColor.white.cgColor
        shadowLayer.shadowColor = UIColor.gray.cgColor
        shadowLayer.shadowOffset = CGSize.zero
        shadowLayer.shadowOpacity = 1
        shadowLayer.shadowRadius = 3
        shadowLayer.masksToBounds = false
        self.layer.insertSublayer(shadowLayer, at: 0)
    }
    
    func configView(_ reg: Registration) {
        configLabels(reg)
        
        isLayoutMarginsRelativeArrangement = true
        directionalLayoutMargins = NSDirectionalEdgeInsets(top: 20, leading: 20, bottom: 20, trailing: 0)
        
    }
    
    func configLabels(_ reg: Registration) {
        
        // Name line
        if let degree = reg.degree {
            nameLabel.text = "\(reg.firstName!) \(reg.lastName!), \(degree)"
        } else {
            nameLabel.text = "\(reg.firstName!) \(reg.lastName!)"
        }
        
        // Title line
        if let title = reg.title {
            titleLabel.text = title
        } else {
            titleLabel.isHidden = true
        }
        
        // Email line
        if let email = reg.email {
            emailLabel.text = email
        } else {
            emailLabel.isHidden = true
        }
        
        // Location line
        if let city = reg.city, let state = reg.state, let postal = reg.postalCode,
            !city.isEmpty, !state.isEmpty, !postal.isEmpty {
            postalLabel.text = "\(city), \(state) \(postal)"
        } else if let postal = reg.postalCode {
            postalLabel.text = postal
        }
        
        // NPI line
        if let licenseId = reg.licenseId {
            switch reg.countryCode {
            case "BR":
                idLabel.text = "ID"
            default:
                idLabel.text = "NPI"
            }
            npiLabel.text = licenseId
        } else {
            self.idLabel.isHidden = true
            self.npiLabel.isHidden = true
        }
    }
}

Dávid Pásztor
  • 51,403
  • 9
  • 85
  • 116
Troy
  • 51
  • 7
  • I think you need to add top and bottom constraints to all childs in your MasterStack – ndreisg Aug 11 '20 at 13:33
  • I would think those constraints are add by the masterstack automatically via the distribution? I have it set to Equal Spacing. – Troy Aug 11 '20 at 13:47
  • I guess you're right, but just noticed that your MasterStack has no bottom constraint, I think this needed to calculate the intrinsic size – ndreisg Aug 11 '20 at 13:49
  • Good point. When adding a bottom constraint to the MasterStack, the views are stretched to fill the safe area. My goal may not be clear. I want retain constant spacing between the views in the master stack, and leave remaining whitespace below the button. – Troy Aug 11 '20 at 14:46

1 Answers1

1

A colleague of mine found the answer.

When loading the view from the nib, I needed to call self.addArrangedSubview instead of self.addSubview which provides the intended behavior.

Troy
  • 51
  • 7