How does the layout of iOS screen geometry work? - ios

I am using Xcode 8.2, Swift 3.
I am trying to programmatically create a UIViewController with some textual (and in the future, some graphic) content on the screen. Normally, this would be quite easy to do with the Storyboard but since this ViewController is programmatically created, I have to work like this.
Here is my code so far:
let detailViewController = UIViewController()
detailViewController.view.backgroundColor = UIColor.white
let screenSize = UIScreen.main.bounds
let screenWidth = screenSize.width
let screenHeight = screenSize.height
let titleLabel = UILabel(frame: CGRect(x: 20, y: 20, width: screenWidth, height: 20))
titleLabel.center = CGPoint(x: screenWidth / 2, y: 100)
titleLabel.backgroundColor = UIColor.red
titleLabel.textAlignment = .center
titleLabel.text = "Scan Results"
titleLabel.font = UIFont.boldSystemFont(ofSize: 14)
let myField: UITextView = UITextView (frame: CGRect(x: 50, y: 50, width: screenWidth, height: 300))
myField.center = CGPoint(x: screenWidth / 2, y: 250)
myField.backgroundColor = UIColor.green
myField.text = <really long string here>
detailViewController.view.addSubview(titleLabel)
detailViewController.view.addSubview(myField)
And this is what I see:
This raises a lot of questions for me as I am trying to understand how this layout works but can't seem to find any help that makes sense to me.
My screenWidth and screenHeight is 375 and 667 respectively. I am using an iPhone 7. I've positioned my titleLabel in the center but at y: 100. But where the label ended up clearly doesn't look like 1/6 of the way down given that the height of the screen is 667.
So how exactly does textual positioning work? I know that the top left is the origin but that's about it.
Also, I start my text field at x: 50, y: 50 with a width of screenWidth. So why is the text overflowing off the side of the page instead of wrapping around?
Much thanks for any help.

I think if you are setting the frame of the different views (via the constructor or otherwise), dont set the .center as well, it will affect its x and y positions hence why their positioning is off

You have to add constraints programmatically, for example:
NSLayoutConstraint(item: myField, attribute: NSLayoutAttribute.leading, relatedBy: NSLayoutRelation.equal, toItem: detailViewController, attribute: NSLayoutAttribute.leadingMargin, multiplier: 1.0, constant: 20.0).isActive = true
myField.widthAnchor.constraint(equalToConstant: 200).isActive = true
myField.heightAnchor.constraint(equalToConstant: 100).isActive = true
Check this answer for more information: https://stackoverflow.com/a/36263784/4077559. I think this is what you are looking for

Related

Whole content out of view

I have a Shuffle package added to my project (https://cocoapods.org/pods/Shuffle-iOS), the package works fine, but the problem is that even though I set cards width and height to my UIView, cards are out of UIView anyways, I tried changing the frame of my cards and set width and height to UIViews, but they are still out of UIView any solutions?
my UIView is mainView in code below
func card1(index: swipeCardData) -> SwipeCard {
let card = SwipeCard()
card.swipeDirections = [.left, .right, .up]
card.layer.cornerRadius = 12
card.layer.shadowOffset = CGSize.zero
card.layer.shadowOpacity = 1.0
card.layer.shadowRadius = 6.0
card.layer.masksToBounds = false
card.layer.borderWidth = 2
let view_bg = UIView(frame: CGRect(x: 16, y: 60, width: mainView.frame.size.width, height: mainView.frame.height)) // here is set cards width and frame to my UIView
card.content = view_bg
view_bg.layer.cornerRadius = 12
view_bg.clipsToBounds = true
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [self] in
let view_bg1 = UIView(frame: CGRect(x: 0, y: 0, width: view_bg.frame.size.width, height: view_bg.frame.size.height))
card.content?.addSubview(view_bg1)
let img_card_type = UIImageView(frame: CGRect.zero)
img_card_type.contentMode = .scaleAspectFit
img_card_type.translatesAutoresizingMaskIntoConstraints = false
img_card_type.isHidden = true
view_bg1.addSubview(img_card_type)
img_card_type.centerXAnchor.constraint(equalToSystemSpacingAfter: view_bg1.centerXAnchor, multiplier: 1).isActive = true
img_card_type.topAnchor.constraint(equalTo: view_bg1.topAnchor, constant: 0).isActive = true
img_card_type.widthAnchor.constraint(equalToConstant: 100).isActive = true
pictures for better understanding :
as you can see on the screenshot above the card content is out of mainView which is in the background(gray box)
the end result below
The reason is you are not providing height for image view and telling to expand according to aspect ratio of image. set a max height for image view. to better UX centre imageview in both axis and a fixed either height or width and a maximum for other width or height.
img_card_type.centerXAnchor.constraint(equalToSystemSpacingAfter: view_bg1.centerXAnchor, multiplier: 1).isActive = true
img_card_type.centerYAnchor.constraint(equalToSystemSpacingAfter: view_bg1.centerYAnchor, multiplier: 1).isActive = true
img_card_type.widthAnchor.constraint(equalToConstant: 100).isActive = true
img_card_type.heightAnchor.constraint(lessThanOrEqualToConstant: 100).isActive = true

Why adding frame doesn't show a UIView when initializing with lazy var in Swift

I'm working on a Swift project and there is one thing I'm not clear about making UIs programmatically.
I tried to display a simple UIView on the screen.
lazy var container: UIView = {
let view = UIView(frame: CGRect(x: 0, y: 0, width: 30, height: 30))
view.backgroundColor = UIColor.systemImageGray()
view.layer.cornerRadius = view.layer.bounds.width / 2
view.clipsToBounds = true
return view
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(container)
setupConstraints()
}
func setupConstraints() {
container.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
container.topAnchor.constraint(equalTo: self.topAnchor, constant: 14),
container.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -14),
container.widthAnchor.constraint(equalToConstant: 30),
container.heightAnchor.constraint(equalToConstant: 30)
])
}
The code above works fine, but since I set the with and height twice, I feel it's redundant, like UIView(frame: CGRect(x: 0, y: 0, width: 30, height: 30)) and set the width and height constraints in setupConstraints.
Since I set the width and height in UIView's frame, I thought I don't need to set the width and height constraints in the setupConstraints, but it doesn't show the view unless I add the width and height constraints again. So in this case, why I cannot set the width and height in UIView(frame: CGRect(x: 0, y: 0, width: 30, height: 30)) and I also have to add the width/height constraints again?
frame is useful when you are not using the Autolayout engine to place your views.
When you do:
container.translatesAutoresizingMaskIntoConstraints = false
You are explicitly telling the engine to ignore the frame & that you are responsible for applying a new set of constraints.
And hence you eventually do:
NSLayoutConstraint.activate([
container.topAnchor.constraint(equalTo: self.topAnchor, constant: 14),
container.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -14),
container.widthAnchor.constraint(equalToConstant: 30),
container.heightAnchor.constraint(equalToConstant: 30)
])
Which sets the positioning & dynamic sizing as per Autolayout's expectations.
translatesAutoresizingMaskIntoConstraints
A Boolean value that determines whether the view’s autoresizing mask
is translated into Auto Layout constraints.
Ref: https://developer.apple.com/documentation/uikit/uiview/1622572-translatesautoresizingmaskintoco

What is system spacing in a layout?

I'm having difficulty understanding what "system spacing" or "standard spacing" between views are.
import UIKit
import PlaygroundSupport
let rootView = UIView(frame: CGRect(x: 100, y: 100, width: 500, height: 500))
rootView.backgroundColor = .white
let containerView = UIView(frame: CGRect(origin: .zero, size: .init(width: 300, height: 200)))
containerView.backgroundColor = .yellow
rootView.addSubview(containerView)
let button = UIButton()
button.setTitle("Button", for: .normal)
button.backgroundColor = .red
containerView.addSubview(button)
button.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
button.widthAnchor.constraint(equalToConstant: 100),
button.leadingAnchor.constraint(equalToSystemSpacingAfter: containerView.leadingAnchor, multiplier: 1)
])
PlaygroundPage.current.needsIndefiniteExecution = true
PlaygroundPage.current.liveView = rootView
I noticed that there is a difference if I substituted:
button.leadingAnchor.constraint(equalTo: containerView.leadingAnchor)
for equalToSystemSpacingAfter in the position of the button. Also, how does equalToSystemSpacingAfter accommodate changes in the text size?
Answer to first question:
difference between constraint(equalTo: & .constraint(equalToSystemSpacingAfter:
Answer to second question:
Also, how does equalToSystemSpacingAfter accommodate changes in the text size?
Since you are using fixed width of 100 points, so if the button title grows in length, it will be simply truncated. To solve the issue, you should avoid setting exact width of it.

How to change size of button in Swift?

How to change height and width of UIButton, if it's constraint? This is for you easy question. But I don't understand. This is part of code where I change size
button.frame = CGRect(x: button.frame.origin.x, y: button.frame.origin.y, width: 30.0, height: 30.0)
button.widthAnchor.constraint(equalToConstant: 30.0).isActive = true
button.heightAnchor.constraint(equalToConstant: 30.0).isActive = true
If you have constraints you need to store a reference to each constraint and update the constant like this:
let button = UIButton()
let widthConstraint = button.widthAnchor.constraint(equalToConstant: 30.0)
let heightConstraint = button.heightAnchor.constraint(equalToConstant: 30.0)
NSLayoutConstraint.activate([widthConstraint, heightConstraint])
//change button size to 50x50
widthConstraint.constant = 50
heightConstraint.constant = 50
Best,
Carsten

Fixed Height Constraints for UIStackview's subviews

I have a UIScrollView that has a UIStackView subview, call it stack. I want to have a dynamic number of subviews in stack, but these subviews will have different heights. How can I achieve this? Please note that I'm trying to shy away from Storyboard..
let scrollView = UIScrollView(frame: CGRect(x: 20, y: 20, width: view.frame.width - 20, height: view.frame.height - 20))
scrollView.center = view.center
self.view.addSubview(scrollView)
let stack = UIStackView(frame: CGRect(x: 20, y: 20, width: scrollView.frame.width - 40, height: scrollView.frame.height - 40))
stack.axis = .vertical
stack.alignment = .fill
stack.distribution = .fillProportionally
stack.spacing = 20
scrollView.addSubview(stack)
let label1 = UILabel()
label1.text = "This is going to be very, very long. I don't know how to make this guy longer. Words and words and more words. // Do any additional setup after loading the view, typically from a nib."
label1.numberOfLines = 0
label1.sizeToFit()
// Add more labels similar to label1
I tried adding a constraint to each of my UILabel subview, but it doesn't really work..
let x = NSLayoutConstraint(item: label1, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: label1.frame.size.height)
label1.addConstraint(x)
Please help! Thanks.
Try changing the stack view's distribution mode :
stack.distribution = .fillProportionally
(I'm not sure if that's the correct one, if it's not just try all the different distribution modes :) )

Resources