syedfa
syedfa

Reputation: 2809

Need to size StackView proportionately to my device size

I am building my screen in code which contains a stackView that holds my textfields. This stackView looks great on an iPhone 8/8Plus/X device, but too large on an SE screen. I realize that I need to size my stackView proportionately to the device size, but am not sure how programmatically. All the tutorials that I've seen show how to do it in IB.

Here is the code I'm working with:

        let textFieldStackView = UIStackView(arrangedSubviews: [nameTextField, emailTextField, passwordTextField])
        textFieldStackView.translatesAutoresizingMaskIntoConstraints = false
        textFieldStackView.distribution = .fillEqually
        textFieldStackView.axis = .vertical
        textFieldStackView.spacing = 10

        view.addSubview(textFieldStackView)

        textFieldStackView.topAnchor.constraint(equalTo: logoImageView.bottomAnchor, constant: 100).isActive = true
        textFieldStackView.heightAnchor.constraint(equalToConstant: 150).isActive = true
        textFieldStackView.widthAnchor.constraint(equalToConstant: 350).isActive = true
        textFieldStackView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true

It's not just the width, but the height as well which I need to grow accordingly. How would I do this in code?

Upvotes: 0

Views: 1878

Answers (4)

Sergey Fedorov
Sergey Fedorov

Reputation: 219

If you need to clip your view to edges of you screen you should use safeArea for top/bottom constraints and leading and trailing margin for leading and trailing constraints. This way your view will look great on any device you need.
EG:

yourView.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor).isActive = true
yourView.bottomAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.bottomAnchor).isActive = true
yourView.leadingAnchor.constraint(equalTo: self.view.layoutMarginsGuide.leadingAnchor).isActive = true
yourView.trailingAnchor.constraint(equalTo: self.view.layoutMarginsGuide.trailingAnchor).isActive = true

It's ios 11 code. For support of iOS 10 layout use for top and bottom anchors of controller use self.topLayoutGuide.bottomAnchor and self.bottomLayoutGuide.topAnchor.
It's much correct to use autolayout in autolayout, not UISCreenSizes as in top comment. https://useyourloaf.com/blog/safe-area-layout-guide/ learn this guide, it's little helps.

EDIT:
If you want to have your view in the center and change its size depending on screen size and same time having equal spacing leading and trailing the best way i see is to use supporting views on the left and right ofyour view, which will have same widths. Adjusting compression resistance on center (your) view you will make it to become thinner of wider.
Something like this:
Example

EDIT2:
Based on your comment and in code:

yourView.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true
yourView.centerYAnchor.constraint(equalTo: self.view.centerYAnchor).isActive = true
yourView.leadingAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.leadingAnchor, constant: 10).isActive = true
yourView.trailingAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.trailingAnchor, constant: 10).isActive = true
yourView.bottomAnchor.constraintGreaterThanOrEqualToSystemSpacingBelow(self.view.safeAreaLayoutGuide.bottomAnchor, multiplier: 1.0).isActive = true
yourView.topAnchor.constraintGreaterThanOrEqualToSystemSpacingBelow(self.view.safeAreaLayoutGuide.topAnchor, multiplier: 1.0).isActive = true
yourView.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
yourView.setContentCompressionResistancePriority(.defaultLow, for: .vertical)

Upvotes: 1

Brandon
Brandon

Reputation: 23500

You can scale the values from one device screen (6S in the example below) to another..

func ScaleWidth(CGFloat value) -> CGFloat {
    return round(value * (UIScreen.main.bounds.size.width / CGFloat(375.0)));
}

func ScaleHeight(CGFloat value) -> CGFloat {
    return round(value * (UIScreen.main.bounds.size.height / CGFloat(667.0)));
}



textFieldStackView.topAnchor.constraint(equalTo: logoImageView.bottomAnchor, constant: ScaleHeight(100.0)).isActive = true
textFieldStackView.heightAnchor.constraint(equalToConstant: ScaleHeight(150.0)).isActive = true
textFieldStackView.widthAnchor.constraint(equalToConstant: ScaleWidth(350.0)).isActive = true
textFieldStackView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true

Another option is to use the constraint multiplier:

func AspectWidth() -> CGFloat {
    return round(UIScreen.main.bounds.size.width / CGFloat(375.0));
}

func AspectHeight() -> CGFloat {
    return round(UIScreen.main.bounds.size.height / CGFloat(667.0));
}



textFieldStackView.topAnchor.constraint(equalTo: logoImageView.bottomAnchor, constant: 100.0, multiplier: AspectHeight()).isActive = true
textFieldStackView.heightAnchor.constraint(equalToConstant: 150.0, multiplier: AspectHeight()).isActive = true
textFieldStackView.widthAnchor.constraint(equalToConstant: 350.0, multiplier: AspectWidth()).isActive = true
textFieldStackView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true

Upvotes: 1

Artem Garmash
Artem Garmash

Reputation: 164

I can't comment on an answer of kiwisip, so I have to write it here - using UIScreen.main.frame isn't a good solution, it's better to rely on size of view, in which you add your stack view as subview. In your example, you have to replace widthAnchor constraint with something like this:

textFieldStackView.widthAnchor.constraint(equalTo: view.widthAnchor, constant: 0).isActive = true

On the other hand - using superview sizes divided by constant instead of just using constant values can be a good idea, but it depends on your layout.

Upvotes: 0

kiwisip
kiwisip

Reputation: 447

The easiest solution (but not the prettiest one) is to use UIScreen.main.frame to calculate the constants of your constraints. UIScreen.main.frame represents the frame of the whole screen and is different on each device (320x568 on iPhone SE, 375x667 on iPhone 8).

Your code could look like this if you would like the stack view to look the same on each device:

    let textFieldStackView = UIStackView(arrangedSubviews: [nameTextField, emailTextField, passwordTextField])
    textFieldStackView.translatesAutoresizingMaskIntoConstraints = false
    textFieldStackView.distribution = .fillEqually
    textFieldStackView.axis = .vertical
    textFieldStackView.spacing = UIScreen.main.frame.height / 66.7 // will be 10 on iPhone 8

    view.addSubview(textFieldStackView)

    textFieldStackView.topAnchor.constraint(equalTo: logoImageView.bottomAnchor, constant: UIScreen.main.frame.height / 6.67).isActive = true
    textFieldStackView.heightAnchor.constraint(equalToConstant: UIScreen.main.frame.height / 4.45).isActive = true
    textFieldStackView.widthAnchor.constraint(equalToConstant: UIScreen.main.frame.width / 1.07).isActive = true
    textFieldStackView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true

Upvotes: 0

Related Questions