What is system spacing in a layout? - ios

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.

Related

Add button in bottom of view

Trying to add a UIView that fills the screen, that works fine. But I also want to add a UIButton to the bottom of the UIView, but somehow the UIButton doesn't show up:
let screenSize: CGRect = UIScreen.main.bounds
let myView = UIView(frame: CGRect(x: 0, y: 0, width: screenSize.width, height: screenSize.height))
myView.backgroundColor = .systemGray5.withAlphaComponent(0.5)
let button = UIButton()
button.frame = CGRect(x: 0, y: 0, width: screenSize.width, height: 40)
button.setTitle("Sleeping.. Tap here to wake up!", for: UIControl.State.normal)
button.translatesAutoresizingMaskIntoConstraints = false
button.frame.origin = CGPoint(x:0, y:self.view.frame.size.height - button.frame.size.height - 20)
myView.addSubview(button)
self.view.addSubview(myView)
EDIT from answer:
I get error message on line:
button.bottomAnchor.constraint(equalTo: myView.bottomAnchor, constant: 10).isActive = true
Error message:
libc++abi: terminating with uncaught exception of type NSException
*** Terminating app due to uncaught exception 'NSGenericException', reason: 'Unable to activate constraint with anchors <NSLayoutYAxisAnchor:0x600002658c40 "UIButton:0x7f8dce6146c0'Sleeping.. Tap here to wa...'.bottom"> and <NSLayoutYAxisAnchor:0x600002658a80 "UIView:0x7f8dce613080.bottom"> because they have no common ancestor. Does the constraint or its anchors reference items in different view hierarchies? That's illegal.'
terminating with uncaught exception of type NSException
CoreSimulator 857.7 - Device: iPhone 14 Pro (1B903119-D288-4E1C-B36B-F8B7A44AA0DE) - Runtime: iOS 16.0 (20A360) - DeviceType: iPhone 14 Pro
(Recorded stack frame)
You have to add constraints to your button. For general guides, you can take a look into this SO answer. But for your purpose, adding 2 anchor constraints like this should do the trick:
let screenSize: CGRect = UIScreen.main.bounds
let myView = UIView(frame: CGRect(x: 0, y: 0, width: screenSize.width, height: screenSize.height))
myView.backgroundColor = .systemGray5.withAlphaComponent(0.5)
let button = UIButton()
button.frame = CGRect(x: 0, y: 0, width: screenSize.width, height: 40)
button.setTitle("Sleeping.. Tap here to wake up!", for: UIControl.State.normal)
button.bottomAnchor.constraint(equalTo: myView.bottomAnchor, constant: 10).isActive = true
button.centerYAnchor.constraint(equalTo: myView.centerYAnchor).isActive = true
button.translatesAutoresizingMaskIntoConstraints = false
button.frame.origin = CGPoint(x:0, y:self.view.frame.size.height - button.frame.size.height - 20)
myView.addSubview(button)
self.view.addSubview(myView)
try this
button.bottomAnchor.constraint(equalTo: myView.bottomAnchor, constant: 10).isActive = true this code add in override func viewDidAppear(_ animated: Bool) or after
(about swift life cycle)
I hope this helps you.
It's probably because of the constrain, I would recommend you using constrain instead of using rect like that, since the xib or storyboard already have their own view, you don't need to create screenSize like that, my suggestion for you is that try to use constrain instead for every view or button that you want to create, it would be great, readable and maintainable if you write something like this
let containerView = UIView()
let actionButton = UIButton()
actionButton.setTitle("Sleeping.. Tap here to wake up!", for: .normal)
view.addSubview(containerView)
containerView.addSubview(actionButton)
containerView.translatesAutoresizingMaskIntoConstraints = false
actionButton.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
containerView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
containerView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),
containerView.topAnchor.constraint(equalTo: self.view.topAnchor),
containerView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor),
actionButton.bottomAnchor.constraint(equalTo: containerView.bottomAnchor, constant: -10.0),
actionButton.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: 10.0),
actionButton.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: -10.0),
actionButton.heightAnchor.constraint(equalToConstant: 50.0)
])
The code above actually show the same result like what you wrote, but in safer way.

SetImage() method removes titleLabel for UIButton

My wish is to make centered image(left) and next to it(right) the label.
Without setting an image, there was a perfectly centered titleLabel:
btnWhatsapp.titleLabel?.adjustsFontSizeToFitWidth = true
btnWhatsapp.setTitle("WhatsApp", for: .normal)
Then I added this code to add an image:
btnWhatsapp.setImage(UIImage(named: "phoneIcon"), for: .normal)
btnWhatsapp.imageView?.layer.transform = CATransform3DMakeScale(0.5, 0.6, 0.5)
btnWhatsapp.imageView?.contentMode = .scaleAspectFit
, and this iswhat I got then:
, so the title disappeared.
Maybe the problem is that image uses more space than its actual size(the size shouldnt take more widht and height than the icon size). I saw this when changed images background(should be this much grey color):
btnWhatsapp.imageView?.backgroundColor = .gray
I tried to use the imageEdgeInsets but it is very hard to calculate it to fit perfectly on every iPhone.
This is the Attributes inspector of the button:
You can't set title and image at once by default, nor position them as you describe.
If you need to have a UIButton, I'd recommend to make a UIView (or possibly horizontal UIStackView) with UIImage and UILabel inside, position them with autolayout, then you can add this view to the UIButton as a subview.
let button = UIButton(type: .custom)
button.frame = viewFrame // This is the desired frame of your custom UIView or UIStackView
button.addSubview(customView)
You will be able to position the views easily for all sizes with this approach, but you will probably want to use autolayout in real word app, instead of hardcoded frames.
Example:
override func viewDidLoad() {
super.viewDidLoad()
let image = UIImageView(frame: CGRect(x: 0, y: 0, width: 50, height: 50))
let label = UILabel(frame: CGRect(x: 0, y: 0, width: 50, height: 50))
label.text = "text"
let stack = UIStackView(arrangedSubviews: [image, label])
stack.frame = CGRect(x: 0, y: 0, width: 100, height: 50)
stack.distribution = .fillEqually
let button = UIButton()
button.frame = CGRect(x: 0, y: 0, width: 100, height: 50)
button.addSubview(stack)
view.addSubview(button)
self.view.addSubview(button)
}
Set your button under your controller class like this:
let imageButton: UIButton = {
let b = UIButton(type: .custom)
b.backgroundColor = #colorLiteral(red: 0.3411764801, green: 0.6235294342, blue: 0.1686274558, alpha: 1)
b.layer.cornerRadius = 12
b.clipsToBounds = true
b.translatesAutoresizingMaskIntoConstraints = false
let imageV = UIImageView()
imageV.image = UIImage(named: "yourImage")?.withRenderingMode(.alwaysTemplate)
imageV.tintColor = .white
imageV.contentMode = .scaleAspectFill
imageV.translatesAutoresizingMaskIntoConstraints = false
imageV.widthAnchor.constraint(equalToConstant: 30).isActive = true
let label = UILabel()
label.text = "WhatsApp"
label.textColor = .white
label.font = .systemFont(ofSize: 16, weight: .regular)
let stack = UIStackView(arrangedSubviews: [imageV, label])
stack.distribution = .fill
stack.spacing = 4
stack.axis = .horizontal
stack.translatesAutoresizingMaskIntoConstraints = false
b.addSubview(stack)
stack.heightAnchor.constraint(equalToConstant: 30).isActive = true
stack.widthAnchor.constraint(equalToConstant: 120).isActive = true
stack.centerXAnchor.constraint(equalTo: b.centerXAnchor).isActive = true
stack.centerYAnchor.constraint(equalTo: b.centerYAnchor).isActive = true
return b
}()
Now in viewDidLoad add button and set constraints in your view (in my case on top)
view.addSubview(imageButton)
imageButton.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20).isActive = true
imageButton.heightAnchor.constraint(equalToConstant: 50).isActive = true
imageButton.widthAnchor.constraint(equalToConstant: 200).isActive = true
imageButton.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
This is the result:

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

how to customize a button's size in swift 3?

I want to resize a button as a square and here's my code:
let button = UIButton()
button.backgroundColor = UIColor.red
button.translatesAutoresizingMaskIntoConstraints = false
//method one
//button.widthAnchor.constraint(equalToConstant:44.0).isActive = true
//button.heightAnchor.constraint(equalToConstant: 44.0).isActive = true
//method two
button.frame.size = CGSize(width: 20.0, height: 20.0)
button.addTarget(self, action: #selector(ratingButtonTapped(button: )), for: .touchUpInside)
addArrangedSubview(button)
I have tried both method however none of them seem to work out fine with an error showing some mistakes in method one and nothing shown in method two. when i run the code, button is fulfilled in the container. what is wrong here?
Use addSubview() instead of addArrangedSubview().
I write here the playground with the same solution I posted in comments, in case someone encounters the same problem:
Swift 3.0:
//: Playground - noun: a place where people can play
import UIKit
import PlaygroundSupport
let view = UIView(frame: CGRect(x: 0, y: 0, width: 300, height: 400))
view.backgroundColor = UIColor.blue
let button = UIButton()
button.backgroundColor = UIColor.red
button.frame.size = CGSize(width: 40.0, height: 20.0)
button.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(button) // This does the trick
PlaygroundPage.current.liveView = view
Why not create the button and initialise it with the given size?
let btn = UIButton(frame: CGRect(x: 0, y: 0, width: 20, height: 20))
And then change the x and y accordingly

Initialize a button with a title from a Label in swift

I want to initialize a button's title from a Label. I have this code:
let smallSquare = CGSize(width: 30, height: 30)
let button = UIButton(frame: CGRect(origin: CGPointZero, size: smallSquare))
But, I do not know how I can initialize a title with my label:
let label = UILabel(frame: CGRectMake(0, 0, 200, 21))
label.center = CGPointMake(160, 284)
label.textAlignment = NSTextAlignment.Center
label.text = "I'am a test label"
Normally, I use this property to add a title with a string:
button.setTitle("Button Title",for: .normal)
Is it possible to simply put my Label in my button's title?
Thanks in advance
You can add you custom label as subview to your button like Mike Alter mentioned in the comments like this (Note: Code is in Swift 3, but should be easy to adopt to Swift 2.*. Hints are in the code comments):
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
let smallSquare = CGSize(width: 30, height: 30)
let button = UIButton(frame: CGRect(origin: .zero, size: smallSquare))
button.translatesAutoresizingMaskIntoConstraints = false
button.backgroundColor = .red
// add the button to your view
view.addSubview(button)
// set constraints of your button
button.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
button.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
let label = UILabel(frame: CGRect(x: 0, y: 0, width: 200, height: 21))
label.translatesAutoresizingMaskIntoConstraints = false
label.center = CGPoint(x: 160, y: 284)
label.textAlignment = .center
label.text = "I'm a test label"
// add the label to your button
button.addSubview(label)
// set constraints of your label
label.centerXAnchor.constraint(equalTo: button.centerXAnchor).isActive = true
label.centerYAnchor.constraint(equalTo: button.centerYAnchor).isActive = true
}
}
Result with your custom values looks like this (Just added the red background of the button so you see the frame of the button compared to the label frame):
Your label has a text property. You used it to set a value and you can also use it to get the value. The text is optional, so you need to unwrap.
let labelText = label.text ?? ""
button.setTitle(labelText, for: .normal)
if let textFromLabel = yourCustomLabel.text {
yourButton.setTitle(textFromLabel, .normal)
}
is what I'll suggest you to do

Resources