Search code examples
iosswiftautolayoutstoryboard

In Swift, programmatically creating UIView and adding controls to it and using auto layout, causes the controls to appear on the view's parent


I am trying to write a simple composite component for iOS in Swift 3. It consists of a UILabel followed by an UITextField laid out horizontally followed by a line under them. But What happens is the UILabel disappears, UITextField appears on the parent view and line also disappears.

My design in sketch

What it actually looks like in the Storyboard

My component's constraints in the view controller

My intention was to use Auto Layout, anchor the label to top and leading anchors of the view, anchor the textfield to top of the view and trailing anchor of the label with a constant, so they would appear side by side.

I did do a lot of research on this, one site that looked pretty close to what I wanted was https://www.raywenderlich.com/125718/coding-auto-layout, and I think I am following more or less the same approach.

I am doing something obviously wrong, but can't figure out what. Any help is much appreciated, I have been at this for a few days now.

import UIKit

@IBDesignable
class OTextEdit: UIView {

    @IBInspectable var LabelText: String = "Label"
    @IBInspectable var SecureText: Bool = false
    @IBInspectable var Color: UIColor = UIColor.black
    @IBInspectable var Text: String = "" {
        didSet {
            edit.text = Text
        }
    }

    fileprivate let label = UILabel(frame: CGRect(x: 0, y: 0, width: 200, height: 35))
    fileprivate let edit = UITextField(frame: CGRect(x: 210, y: 0, width: 200, height: 35))
    fileprivate let line: UIView = UIView()

    override var intrinsicContentSize: CGSize {
        return CGSize(width: 300, height: 100)
    }

    func setup() {
        label.text = LabelText
        label.textColor = Color
        label.font = UIFont(name: "Avenir Next Condensed", size: 24)
        edit.font = UIFont(name: "Avenir Next Condensed", size: 24)
        edit.borderStyle = .roundedRect
        edit.isSecureTextEntry = SecureText
        line.backgroundColor = UIColor.white

        self.addSubview(label)
        self.addSubview(edit)
        self.addSubview(line)
    }

    override func willMove(toSuperview newSuperview: UIView?) {
        super.willMove(toSuperview: newSuperview)
        setup()
        setupConstaints()
    }

    func setupConstaints() {
        label.translatesAutoresizingMaskIntoConstraints = false
        edit.translatesAutoresizingMaskIntoConstraints = false
        line.translatesAutoresizingMaskIntoConstraints = false
        label.leadingAnchor.constraint(equalTo: leadingAnchor, constant: -10).isActive = true
        label.topAnchor.constraint(equalTo: topAnchor)
        edit.leadingAnchor.constraint(equalTo: label.leadingAnchor, constant: 10).isActive = true
        edit.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -20).isActive = true
        edit.topAnchor.constraint(equalTo: self.topAnchor)
        line.heightAnchor.constraint(equalToConstant: 2.0).isActive = true
        line.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 10).isActive = true
        line.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -20).isActive = true
        line.topAnchor.constraint(equalTo: label.bottomAnchor, constant: 1.0).isActive = true
    }
}

Solution

  • You haven't got a series of constraints top to bottom, so auto layout can't determine the content size of your object. You have tried to set this via the initrinsicContentSize but you shouldn't need to do this.

    You also need to set a horizontal hugging priority for your label to let auto layout know that you want the text field to expand:

    I removed your override of intrinsicContentSize and changed your constraints to:

    • Constrain the bottom of the label to the top of the line
    • Constrain the bottom of the line to the bottom of the superview
    • Constrain the baseline of the label to the baseline of the text field
    • Remove the constraint between the top of the text field and the superview
    • Set the horizontal hugging priority of the label.

     func setupConstraints() {
        label.translatesAutoresizingMaskIntoConstraints = false
        edit.translatesAutoresizingMaskIntoConstraints = false
        line.translatesAutoresizingMaskIntoConstraints = false
    
        label.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 10).isActive = true
        label.setContentHuggingPriority(.defaultHigh, for: .horizontal)
        label.topAnchor.constraint(equalTo: topAnchor)
        label.bottomAnchor.constraint(equalTo: line.topAnchor, constant: -8).isActive = true
    
        edit.leadingAnchor.constraint(equalTo: label.trailingAnchor, constant: 10).isActive = true
        edit.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -20).isActive = true
        edit.firstBaselineAnchor.constraint(equalTo: label.firstBaselineAnchor).isActive = true
    
        line.heightAnchor.constraint(equalToConstant: 2.0).isActive = true
        line.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 10).isActive = true
        line.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -20).isActive = true
        line.topAnchor.constraint(equalTo: label.bottomAnchor, constant: 1.0).isActive = true
        line.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
    }
    

    I think it is pretty close to what you are after.