iOS UiView layout issue - ios

My code is as follows:
let payBackView = UIView(frame: CGRect.sizeMake(x: 10, y: UIScreen.main.bounds.size.height - 86, width: 369, height: 52))
self.view.bringSubviewToFront(payBackView)
payBackView.layer.cornerRadius = 21
payBackView.layer.masksToBounds = true
payBackView.backgroundColor = .white
self.view.addSubview(payBackView)
iOS simulator iPhone 12Pro is what I want, but simulator iphone11 pro Max is abnormal.I want to know why and what should I do ?enter image description hereenter image description here

You really should be taking advantage of auto-layout / constraints.
Take a look at this:
let payBackView = UIView()
payBackView.layer.cornerRadius = 21
payBackView.layer.masksToBounds = true
payBackView.backgroundColor = .white
// we'll be using auto-layout constraints
payBackView.translatesAutoresizingMaskIntoConstraints = false
// add payBackView to view
self.view.addSubview(payBackView)
// respect safe-area
let g = self.view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
// we want 10-pts on each side
payBackView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 10.0),
payBackView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -10.0),
// aligned to the bottom
payBackView.bottomAnchor.constraint(equalTo: g.bottomAnchor),
// Height: 52-pts
payBackView.heightAnchor.constraint(equalToConstant: 52.0),
])

You are using a fixed width for payBackView and This is why your view on a bigger screen such as 11 pro max will be smaller. Try this:
let payBackView = UIView(frame: CGRect.sizeMake(x: 10, y: UIScreen.main.bounds.size.height - 86, width: UIScreen.main.bounds.size.width - 40, height: 52))

UIScreen is a bad idea some iPhone has a bezel and some don't. To make sure all your views stay at the visible "zone" you have to use constraints and relate them to "Safe Area". "Safe Area" is where all kinds of iPhones with or without a bezel acts the same.
You may lose the bezel part in your design.

Related

Swift iOS Layout Constraint. Using Constant that is proportional to view height programatically

I am laying out a view programatically in Swift for iOS, but struggling to get my constraint quite how I want it. This is what I currently have:
NSLayoutConstraint.activate([
Logo.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 30),
Logo.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -30),
Logo.heightAnchor.constraint(equalToConstant: 55),
Logo.topAnchor.constraint(equalTo: label.bottomAnchor, constant: 150),
])
This is fine on large screens but as the screen gets smaller I want to close the space between the Logo and the label. Currently this is set to a fixed constant of 150. What I would like to do is use a multiplier here that is based on the view height (or something similar) but I can not figure that out. How should I define the constraint to do this? Thanks!
You can try
/* Play with percent as you need also you can check current
device type iphone/ipad and set it accordingly */
let height = UIScreen.main.bounds.height * 0.25
logo.topAnchor.constraint(equalTo: label.bottomAnchor, constant: height),

How can I get UIView's (x, y) when using AutoLayout if initial frame was misconfigured?

Perhaps it is a misuse of a UIView's frame property by my setup code, for auto laid-out views, wherein, because I assume the view's initial frame property values won't matter once AutoLayout constraints are added, that I can use the frame.origin to cache values that aren't really the view's origin in coordinate space.
What I mean by "misusing" the frame property is: I am setting the frame.origin to values to be parameter values in my constraint setup function calls, because I like like the succinctness and convenience for tweaking constraint offsets by the CGRect, in my code. But perhaps it's backfiring? Not sure why though...
For example:
let view = UIView(frame: CGRect(x: 10, y: 10, width: 100, height: 100))
NSLayoutConstraint.activate([
view.leadingAnchor.constraint(equalTo: leftSideView.trailingAnchor,
constant: view.frame.origin.x)
.
.
.
])
Initially it seems fine, views are placed as expected on the screen, but after views have been laid-out by AutoLayout, the frame.origin seems to reflect the original misappropriation by me... E.g. after AutoLayout frame.origin is still (10, 10)... anyway after AutoLayout, frame.origin are not the correct absolute values in the coordinate system one generally refers to the frame property to get.
After I do this:
view.setNeedLayout()
view.layoutIfNeeded()
When I print the frame.origin values, they aren't the view's actual position.
Why wouldn't they be?
So, how can I find the right values?
Perhaps I need to call one of UIView's superview coordinate translation functions?
UPDATE: The view that is not having its origin set is actually the leftSideView, which I configured using the same approach as the view I'm constraining. I did run setNeedsLayout()/layout, but now I'm thinking that AutoLayout doesn't have enough data yet to calculate the frame so I have a chicken-egg problem where I'm trying to add constraints based on constraints that are unresolved yet, which won't work.
You are asking the wrong view to layout. There is nothing much for view to layout, other than its own size. It doesn't know how to lay itself out relative to leftSideView, because leftSideView is not one of its subviews.
To layout view relative to leftSideView, find their common parent, and ask it to layout. Suppose view.superview is the common parent, do:
view.superview!.layoutIfNeeded()
Then view.frame will be the values you'd expect.
Alternatively, you can override viewDidLayoutSubviews in UIViewController or layoutSubviews in the common parent of the views you want to layout, to detect when layout has finished, without calling layoutIfNeeded. In there, frame will have the correct values too.
Minimal example:
class MyViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let v = UIView(frame: .zero)
view.addSubview(v)
v.backgroundColor = .blue
v.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
v.topAnchor.constraint(equalTo: view.topAnchor, constant: 10),
v.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 30),
v.widthAnchor.constraint(equalToConstant: 100),
v.heightAnchor.constraint(equalToConstant: 100)
])
let u = UIView(frame: .init(x: 10, y: 10, width: 100, height: 100))
view.addSubview(u)
u.backgroundColor = .red
u.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
u.topAnchor.constraint(equalTo: v.bottomAnchor, constant: 10),
u.leftAnchor.constraint(equalTo: v.leftAnchor),
u.widthAnchor.constraint(equalToConstant: 100),
u.heightAnchor.constraint(equalToConstant: 100)
])
// before any layout
print(u.frame)
// laying out u, doesn't change anything
u.layoutIfNeeded()
// note that the above could change the frame of u depending on the frame
// of u and its size constraints, but I've deliberately chosen these
// values and constraints so that it matches your behaviour
print(u.frame)
// laying out u, and v, making u.frame what you expect
view.layoutIfNeeded() // view is the common parent between u and v
print(u.frame)
}
}

StackView constraints with UIViews

I'm trying to setup a stackview in the middle of the screen with a padding of 20 to the left an right. Inside, I want to place two custom UIViews, but I don't quit understand how to do it. I tried giving the UIViews their respective Height's and Width's but I got nothing.
I believe the stack view has all the correct constraints. Here's the code:
func setupTeamViews() {
view.addSubview(teamsStackView)
teamsStackView.distribution = .fill
teamsStackView.axis = .vertical
teamsStackView.spacing = 20
teamsStackView.alignment = .fill
NSLayoutConstraint.activate([
teamsStackView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
teamsStackView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
teamsStackView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
teamsStackView.heightAnchor.constraint(equalToConstant: 420)
])
let height = CGFloat((teamsStackView.frame.height / 2) - 20)
let width = CGFloat((teamsStackView.frame.width - 20))
firstTeamView = ATTeamView(width: width, height: height)
firstTeamView.changeColor(color: .lightBlue)
firstTeamView.setTeamName(name: "Tobias")
firstTeamView.setNewPoints(points: "0")
secondTeamView = ATTeamView(width: width, height: height)
secondTeamView.changeColor(color: .white)
secondTeamView.setTeamName(name: "Valen")
secondTeamView.setNewPoints(points: "0")
teamsStackView.addArrangedSubview(firstTeamView)
teamsStackView.addArrangedSubview(secondTeamView)
}
How does a stackview work with UIview's? As far I understand, UIViews don't have intrinsicContentSize, but I don't know how to deal with that.
In stead of doing it programmatically you can add two container views attach them to your view controller, and then active them with the alpha.
This will make constraints easier and you can still program anything extra you would like
myView1.alpha = 1 // activate
myView2.alpha = 0 // deactivate
My code wasn't working because I've forgotten to set teamsStackView.translatesAutoresizingMaskIntoConstraints = false

Using NSLayoutAnchor to create constraints between two labels?

I'm creating a view programmatically and need to set constraints between two labels. I recently just discovered NSLayoutAnchor and feel it would be a good choice to use it but I'm unsure how to create constraints between two different things (ie labels, imageViews, etc). I know a general setup will look something like this:
let codedLabel:UILabel = UILabel()
codedLabel.frame = CGRect(x: 100, y: 100, width: 200, height: 200)
codedLabel.textAlignment = .center
codedLabel.text = alertText
codedLabel.numberOfLines=1
codedLabel.textColor=UIColor.red
codedLabel.font=UIFont.systemFont(ofSize: 22)
codedLabel.backgroundColor=UIColor.lightGray
self.contentView.addSubview(codedLabel)
codedLabel.translatesAutoresizingMaskIntoConstraints = false
codedLabel.heightAnchor.constraint(equalToConstant: 200).isActive = true
codedLabel.widthAnchor.constraint(equalToConstant: 200).isActive = true
codedLabel.centerXAnchor.constraint(equalTo: codedLabel.superview!.centerXAnchor).isActive = true
codedLabel.centerYAnchor.constraint(equalTo: codedLabel.superview!.centerYAnchor).isActive = true
How would you set up constraints in between two labels?
Let's say you have two UIViews you wish to lay out horizontally in one of two ways:
View #1 is 20 points from the leading edge of the superview's margin or safe area and View #2 is another 20 points from view #1. (Think of a left-justified row of buttons.)
Both views are to be centered, with equal spacing before/between/after each view. (Think of two buttons spaced equally apart and centered.)
For example #1 the code would be:
// #1
let view1 = UIView()
view1.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(view1)
let view2 = UIView()
view2.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(view2)
// #2
let margins = view.layoutMarginsGuide
view.addLayoutGuide(margins)
view1.leadingAnchor.constraint(equalTo: margins.leadingAnchor, constant: 20).isActive = true
// #3
view2.leadingAnchor.constraint(equalTo: view1.trailingAnchor, constant: 20).isActive = true
// #4
view1.widthAnchor.constraint(equalToConstant: 100).isActive = true
view2.widthAnchor.constraint(equalToConstant: 100).isActive = true
Comment #1: Note that you do not need to give each view a frame, but you do need to set the auto-resizing mask to false. This is a mistake many new coders forget.
Comment #2: All UIViewController main views have a layoutMarginGuide that yield standard margins both vertically and horizontally (and in iOS 11, particularly for iPhone X, there is a new safeAreaLayoutGuide for vertical alignment). I've set the leading edge of view1 to be the leading edge of the margin with an additional constant of 20 points from it.
Comment #3: Just like I related view1 to the margin, I'm relating view2 to view1, but this time the leading edge ofview2is 20 points from theview1` trailing edge.
Comment #4: The last thing you need to do for horizontal placement is to give each view a width. In this case I wanted both to be 100 points.
Example #2 pretty much uses the same code as example #1, so I'll note the key differences only:
let view1 = UIView()
view1.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(view1)
let view2 = UIView()
view2.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(view2)
let margins = view.layoutMarginsGuide
view.addLayoutGuide(margins)
// #1
let spacer1 = UILayoutGuide()
view.addLayoutGuide(spacer1)
let spacer2 = UILayoutGuide()
view.addLayoutGuide(spacer2)
let spacer3 = UILayoutGuide()
view.addLayoutGuide(spacer3)
// #2
spacer.leadingAnchor.constraint(equalTo: margins.leadingAnchor).isActive = true
view1.leadingAnchor.constraint(equalTo: spacer1.trailingAnchor).isActive = true
spacer2.leadingAnchor.constraint(equalTo: view1.trailingAnchor).isActive = true
view2.leadingAnchor.constraint(equalTo: spacer2.trailingAnchor).isActive = true
spacer3.leadingAnchor.constraint(equalTo: view2.trailingAnchor).isActive = true
spacer3.trailingAnchor.constraint(equalTo: margins.trailingAnchor).isActive = true
// #3
view1.widthAnchor.constraint(equalToConstant: 100).isActive = true
view2.widthAnchor.constraint(equalToConstant: 100).isActive = true
spacer1.widthAnchor.constraint(equalTo: spacer2.widthAnchor).isActive = true
spacer2.widthAnchor.constraint(equalTo: spacer3.widthAnchor).isActive = true
Comment #1: In iOS 9 Apple introduced the concept of UILayoutGuides. Before this, to create "spacers" you had to actually create an invisible UIView and add it as a subview (with all the overhead associated with it). Layout guides "act" like views but do not have that overhead.
Comment #2: The horizontal sequence if "margin...spacer1...view1...spacer2...view2...spacer3...margin". Note that I'm not using any constants, as I wish to let the layout engine give equal spacing.
Comment #3: While I am giving width values for both views, I am not with the spacers. Instead, I am declaring their widths to be equal.
Please note that I've only worked with horizontal constraints. For auto layout to work, you also need to declare vertical constraints too. Most of the concepts are the same... instead of leading/trailing, centerX, and width anchors you have top/bottom, centerY, and height ones. BUT! Starting with iOS 11 you now have a safeAreaLayoutGuide that you should be using instead of layoutMarginsGuide. This only applies to vertical! That's why I separated things. Here's the code I use to work with vertical alignment:
let layoutGuideTop = UILayoutGuide()
let layoutGuideBottom = UILayoutGuide()
view.addLayoutGuide(layoutGuideTop)
view.addLayoutGuide(layoutGuideBottom)
if #available(iOS 11, *) {
let guide = view.safeAreaLayoutGuide
layoutGuideTop.topAnchor.constraintEqualToSystemSpacingBelow(guide.topAnchor, multiplier: 1.0).isActive = true
layoutGuideBottom.bottomAnchor.constraintEqualToSystemSpacingBelow(guide.bottomAnchor, multiplier: 1.0).isActive = true
} else {
layoutGuideTop.topAnchor.constraint(equalTo: topLayoutGuide.bottomAnchor).isActive = true
layoutGuideBottom.bottomAnchor.constraint(equalTo: bottomLayoutGuide.topAnchor).isActive = true
}
Now you have layoutGuideTop and layoutGuideBottom that should work regardless of what iOS version is running.

Sizing of Live Views in Swift Playground

I'm having trouble figuring out how to lay out views in Swift Playgrounds for iPad, though this may also be relevant to Mac users.
The following code should create a view with a red square (also a view) that is near the edges of its' super view, but not touching them.
let v = UIView(frame: CGRect(x: 0, y: 0, width: 500, height: 500))
let sqv = UIView(frame: CGRect(x: 400, y: 400, width: 50, height: 50))
sqv.backgroundColor = .red
v.addSubview(sqv)
PlaygroundPage.current.liveView = v
The result is not what you'd expect:
I suspect I know what is going on here; live views are at a fixed size that is larger than the display area. Some characteristics of the view are ignored when it is acting as the live view. However, I can't find where this is mentioned in the documentation, which vexes me. More importantly, how do I deal with this? I would like to be able to layout simple UIs that change to fit the current size of the live view. I don't know how to address this issue without trial & error and hardcoding, which are two things I would really like to avoid.
I suspect I know what is going on here; live views are at a fixed size that is larger than the display area.
Actually it's more like the other way around. An iPad screen is 1024 points wide (in landscape orientation). The right-hand pane (where it shows your live view) is 512 points wide. The playground forces your root view (v) to fill that pane, inset by 40 points on the left, right, and top (and more on the bottom). So your root view's width is forced to 432 ( = 512 - 2 * 40), less than the 500 you specified.
Views created in code (like yours) have translatesAutoresizingMaskIntoConstraints = true, and a resizing mask of 0, which means don't adjust the view's frame at all when its parent is resized. So the playground resizes your root view to width 432, but your root view doesn't move or resize its subview (sqv).
The easiest fix is to set the autoresizing mask of the subview to express your intent that it remain near the right and bottom edges of the root view. That means it should have flexible top and left margins:
let v = UIView(frame: CGRect(x: 0, y: 0, width: 500, height: 500))
let sqv = UIView(frame: CGRect(x: 400, y: 400, width: 50, height: 50))
sqv.autoresizingMask = [.flexibleLeftMargin, .flexibleTopMargin]
sqv.backgroundColor = .red
v.addSubview(sqv)
PlaygroundPage.current.liveView = v
Result:
let sqv = UIView(frame: CGRect(x: UIScreen.mainScreen().bounds.width-50-1, y:400, width: 50, height: 50))
The above code places your subview 1 point away from the right of the main view. Try changing the value 1 after 50 in x to desired value.

Resources