user4806509
user4806509

Reputation: 3011

Combining constraints with variable and fixed heights on UIView in Swift

Update:

Thanks everyone for the unique approaches. Appreciate your insights.


Background:

I have written some code below in Xcode Swift 5 that creates four equal sized rectangles that resize depending on the device size and orientation.

However, the desired result is a rectangle with different heights, where the top rectangles are variable heights depending on the device size and orientation, and the bottom rectangles are with a constant height of 200px.


Question:

What changes to my code do I need to make to achieve variable heights on top and fixed heights on bottom?


Code:

import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        
        let frameTopLeft = UIView()
        frameTopLeft.backgroundColor = .systemRed
        view.addSubview(frameTopLeft)
        frameTopLeft.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            frameTopLeft.topAnchor.constraint(equalTo: view.topAnchor),
            frameTopLeft.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.5),
            frameTopLeft.leftAnchor.constraint(equalTo: view.leftAnchor),
            frameTopLeft.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.5)
        ])

        let frameTopRight = UIView()
        frameTopRight.backgroundColor = .systemBlue
        view.addSubview(frameTopRight)
        frameTopRight.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            frameTopRight.topAnchor.constraint(equalTo: view.topAnchor),
            frameTopRight.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.5),
            frameTopRight.rightAnchor.constraint(equalTo: view.rightAnchor),
            frameTopRight.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.5)
        ])

        let frameBottomLeft = UIView()
        frameBottomLeft.backgroundColor = .systemGreen
        view.addSubview(frameBottomLeft)
        frameBottomLeft.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            frameBottomLeft.bottomAnchor.constraint(equalTo: view.bottomAnchor),
            frameBottomLeft.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.5),
            frameBottomLeft.leftAnchor.constraint(equalTo: view.leftAnchor),
            frameBottomLeft.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.5)
        ])

        let frameBottomRight = UIView()
        frameBottomRight.backgroundColor = .systemYellow
        view.addSubview(frameBottomRight)
        frameBottomRight.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            frameBottomRight.bottomAnchor.constraint(equalTo: view.bottomAnchor),
            frameBottomRight.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.5),
            frameBottomRight.rightAnchor.constraint(equalTo: view.rightAnchor),
            frameBottomRight.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.5)
        ])

    }

}

Images:

Simulator output.

Image of two iPads with four equal sized rectangles

Desired output.

Image of two iPads with two variable sized rectangles and two fixed sized rectangles

Upvotes: 0

Views: 619

Answers (3)

Phil Dukhov
Phil Dukhov

Reputation: 87804

I'd do it like this:

let frameTopLeft = UIView()
frameTopLeft.backgroundColor = .systemRed
view.addSubview(frameTopLeft)
frameTopLeft.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
    frameTopLeft.topAnchor.constraint(equalTo: view.topAnchor),
    frameTopLeft.leftAnchor.constraint(equalTo: view.leftAnchor),
])

let frameTopRight = UIView()
frameTopRight.backgroundColor = .systemBlue
view.addSubview(frameTopRight)
frameTopRight.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
    frameTopRight.topAnchor.constraint(equalTo: view.topAnchor),
    frameTopRight.rightAnchor.constraint(equalTo: view.rightAnchor),
    frameTopRight.leftAnchor.constraint(equalTo: frameTopLeft.rightAnchor),
    frameTopRight.widthAnchor.constraint(equalTo: frameTopLeft.widthAnchor)
])

let frameBottomLeft = UIView()
frameBottomLeft.backgroundColor = .systemGreen
view.addSubview(frameBottomLeft)
frameBottomLeft.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
    frameBottomLeft.topAnchor.constraint(equalTo: frameTopLeft.bottomAnchor),
    frameBottomLeft.bottomAnchor.constraint(equalTo: view.bottomAnchor),
    frameBottomLeft.leftAnchor.constraint(equalTo: view.leftAnchor),
    frameBottomLeft.heightAnchor.constraint(equalToConstant: 200),
])

let frameBottomRight = UIView()
frameBottomRight.backgroundColor = .systemYellow
view.addSubview(frameBottomRight)
frameBottomRight.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
    frameBottomRight.topAnchor.constraint(equalTo: frameTopRight.bottomAnchor),
    frameBottomRight.bottomAnchor.constraint(equalTo: view.bottomAnchor),
    frameBottomRight.rightAnchor.constraint(equalTo: view.rightAnchor),
    frameBottomRight.leftAnchor.constraint(equalTo: frameBottomLeft.rightAnchor),
    frameBottomRight.heightAnchor.constraint(equalTo: frameBottomLeft.heightAnchor),
    frameBottomRight.widthAnchor.constraint(equalTo: frameBottomLeft.widthAnchor),
])

The main diff from others is getting rid of constants as much as possible.

  1. Your bottom views should be 200? Add single 200 constraint, and make second view height be equal to the first one.
  2. Instead of making width equal 50% of parent, you can make left width be equal right one

It makes you code easier to maintain, decreasing places to edit.

Upvotes: 1

DonMag
DonMag

Reputation: 77462

You want to give the Bottom views constant Height constraints of 200, and then constrain the Bottoms of the top views to the Tops of the Bottom views.

You may find it helpful to group related code together, and add comments as you go (to make it a little clearer what you believe the code should do).

So, try it like this:

override func viewDidLoad() {
    super.viewDidLoad()

    // create 4 views
    let frameTopLeft = UIView()
    let frameTopRight = UIView()
    let frameBottomLeft = UIView()
    let frameBottomRight = UIView()

    // set background colors
    frameTopLeft.backgroundColor = .systemRed
    frameTopRight.backgroundColor = .systemBlue
    frameBottomLeft.backgroundColor = .systemGreen
    frameBottomRight.backgroundColor = .systemYellow

    // set Autoresizing and add to view
    [frameTopLeft, frameTopRight, frameBottomLeft, frameBottomRight].forEach { v in
        v.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(v)
    }

    NSLayoutConstraint.activate([
        
        // top-left view constrained Top / Left / 50% Width
        frameTopLeft.topAnchor.constraint(equalTo: view.topAnchor),
        frameTopLeft.leftAnchor.constraint(equalTo: view.leftAnchor),
        frameTopLeft.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.5),

        // top-right view constrained Top / Right / 50% Width
        frameTopRight.topAnchor.constraint(equalTo: view.topAnchor),
        frameTopRight.rightAnchor.constraint(equalTo: view.rightAnchor),
        frameTopRight.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.5),

        // bottom-left view constrained Bottom / Left / 50% Width
        frameBottomLeft.bottomAnchor.constraint(equalTo: view.bottomAnchor),
        frameBottomLeft.leftAnchor.constraint(equalTo: view.leftAnchor),
        frameBottomLeft.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.5),

        // bottom-right view constrained Bottom / Right / 50% Width
        frameBottomRight.bottomAnchor.constraint(equalTo: view.bottomAnchor),
        frameBottomRight.rightAnchor.constraint(equalTo: view.rightAnchor),
        frameBottomRight.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.5),

        // bottom views, constant Height of 200-pts
        frameBottomLeft.heightAnchor.constraint(equalToConstant: 200.0),
        frameBottomRight.heightAnchor.constraint(equalToConstant: 200.0),
        
        // constrain bottoms of top views to tops of bottom views
        frameTopLeft.bottomAnchor.constraint(equalTo: frameBottomLeft.topAnchor),
        frameTopRight.bottomAnchor.constraint(equalTo: frameBottomRight.topAnchor),
        
    ])

}

Upvotes: 1

Raja Kishan
Raja Kishan

Reputation: 18914

Use StackView for better code and understanding.

class ViewController: UIViewController {
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let topStack = UIStackView()
        topStack.axis = .horizontal
        topStack.distribution = .fillEqually
        
        let frameTopLeft = UIView()
        frameTopLeft.backgroundColor = .systemRed
        topStack.addArrangedSubview(frameTopLeft)
        
        
        let frameTopRight = UIView()
        frameTopRight.backgroundColor = .systemBlue
        topStack.addArrangedSubview(frameTopRight)
        
        
        let bottomStack = UIStackView()
        bottomStack.axis = .horizontal
        bottomStack.distribution = .fillEqually
        
        let frameBottomLeft = UIView()
        frameBottomLeft.backgroundColor = .systemGreen
        bottomStack.addArrangedSubview(frameBottomLeft)
        
        
        let frameBottomRight = UIView()
        frameBottomRight.backgroundColor = .systemYellow
        bottomStack.addArrangedSubview(frameBottomRight)
        
        frameBottomRight.translatesAutoresizingMaskIntoConstraints = false
        frameBottomRight.heightAnchor.constraint(equalToConstant: 200).isActive = true
        
        let mainStack = UIStackView()
        mainStack.axis = .vertical
        mainStack.addArrangedSubview(topStack)
        mainStack.addArrangedSubview(bottomStack)
        
        self.view.addSubview(mainStack)
        
        mainStack.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            mainStack.topAnchor.constraint(equalTo: view.topAnchor),
            mainStack.heightAnchor.constraint(equalTo: view.heightAnchor),
            mainStack.leftAnchor.constraint(equalTo: view.leftAnchor),
            mainStack.widthAnchor.constraint(equalTo: view.widthAnchor)
        ])
    }
    
}

Or you can give relative constraints to each bottom view.

class ViewController: UIViewController {
    
    override func viewDidLoad() {
        super.viewDidLoad()
        let frameTopLeft = UIView()
        frameTopLeft.backgroundColor = .systemRed
        view.addSubview(frameTopLeft)
        frameTopLeft.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            frameTopLeft.topAnchor.constraint(equalTo: view.topAnchor),
            //            frameTopLeft.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.5), << Here
            frameTopLeft.leftAnchor.constraint(equalTo: view.leftAnchor),
            frameTopLeft.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.5)
        ])
        
        let frameTopRight = UIView()
        frameTopRight.backgroundColor = .systemBlue
        view.addSubview(frameTopRight)
        frameTopRight.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            frameTopRight.topAnchor.constraint(equalTo: view.topAnchor),
            //            frameTopRight.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.5), << Here
            frameTopRight.rightAnchor.constraint(equalTo: view.rightAnchor),
            frameTopRight.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.5)
        ])
        
        let frameBottomLeft = UIView()
        frameBottomLeft.backgroundColor = .systemGreen
        view.addSubview(frameBottomLeft)
        frameBottomLeft.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            frameBottomLeft.topAnchor.constraint(equalTo: frameTopLeft.bottomAnchor), // << Here
            frameBottomLeft.bottomAnchor.constraint(equalTo: view.bottomAnchor),
            frameBottomLeft.heightAnchor.constraint(equalToConstant: 200), // << Here
            frameBottomLeft.leftAnchor.constraint(equalTo: view.leftAnchor),
            frameBottomLeft.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.5)
        ])
        
        let frameBottomRight = UIView()
        frameBottomRight.backgroundColor = .systemYellow
        view.addSubview(frameBottomRight)
        frameBottomRight.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            frameBottomRight.topAnchor.constraint(equalTo: frameTopRight.bottomAnchor), // << Here
            frameBottomRight.bottomAnchor.constraint(equalTo: view.bottomAnchor),
            frameBottomRight.heightAnchor.constraint(equalToConstant: 200), // << Here
            frameBottomRight.rightAnchor.constraint(equalTo: view.rightAnchor),
            frameBottomRight.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.5)
        ])
    }
}

Upvotes: 1

Related Questions