Add button in bottom of view - ios

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.

Related

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.

My textView bottomAnchor does not seem to work?

I have a textView and I have a line, I set the line's frame without contraints and set textView frame with constraints. Simply what I want is the textView to follow the line, so I put a bottomAnchor to textView equal to the topAnchor of the line. Yet when I animate the line the textView does not follow? What am I doing wrong?
var button = UIButton()
var testLine = UIView()
let textView = UITextView()
var textViewBottomAnchorConstraint: NSLayoutConstraint?
override func viewDidLoad() {
super.viewDidLoad()
testLine.backgroundColor = .black
testLine.frame = CGRect(x: 0, y: 335, width: UIScreen.main.bounds.width, height: 10)
view.addSubview(testLine)
view.addSubview(textView)
textView.frame = .zero//CGRect(x: CGFloat(integerLiteral: 16), y: CGFloat(integerLiteral: 300), width: CGFloat(integerLiteral: 282), height: CGFloat(integerLiteral: 35))
textView.backgroundColor = UIColor.yellow
textView.text = ""
textView.font = UIFont(name: "Arial Rounded MT Bold", size: 15)
textView.translatesAutoresizingMaskIntoConstraints = false
textView.isHidden = false
textView.translatesAutoresizingMaskIntoConstraints = false
// textView.bottomAnchor.constraint(equalTo: testLine.topAnchor, constant: 0).isActive = true
textView.leadingAnchor.constraint(equalTo: view.layoutMarginsGuide.leadingAnchor, constant: 20).isActive = true
textView.trailingAnchor.constraint(equalTo: view.layoutMarginsGuide.trailingAnchor, constant: -20).isActive = true
textView.heightAnchor.constraint(equalToConstant: 40).isActive = true
textViewBottomAnchorConstraint = textView.bottomAnchor.constraint(equalTo: testLine.topAnchor, constant: 0)
textViewBottomAnchorConstraint?.isActive = true
UIView.animate(withDuration: 2, delay: 2, options: .curveEaseIn, animations: {
self.testLine.transform = CGAffineTransform.identity.translatedBy(x: 0, y: 30)
}) { (true) in
self.view.layoutIfNeeded()
}
}
As #Vollan correctly said animating transform property is not the best option. Here is quote from Apple documentation: "In iOS 8.0 and later, the transform property does not affect Auto Layout. Auto layout calculates a view’s alignment rectangle based on its untransformed frame." Therefore animation of transform property doesn't change layout of textView. I recommend you to animate frame property instead of transform.
However, if you switch to frame animation it doesn't fix all your problems. If you keep your animation inside viewDidLoad method you may encounter very strange behavior. The reason is that in viewDidLoad the view itself is not yet laid out properly. Starting animation inside viewDidLoad may lead to unpredicted results.
At last you need adjust your animation block. Apple recommends to apply layoutIfNeeded inside the animation block. Or at least they used to recommend it then autolayout was introduced - watch this WWDC video (starting from 30th minute) for further details.
If you apply all recommendations above your code should look like this:
var button = UIButton()
var testLine = UIView()
let textView = UITextView()
var textViewBottomAnchorConstraint: NSLayoutConstraint?
var triggeredAnimation = false
override func viewDidLoad() {
super.viewDidLoad()
testLine.backgroundColor = .black
testLine.frame = CGRect(x: 0, y: 335, width: UIScreen.main.bounds.width, height: 10)
view.addSubview(testLine)
view.addSubview(textView)
textView.frame = .zero//CGRect(x: CGFloat(integerLiteral: 16), y: CGFloat(integerLiteral: 300), width: CGFloat(integerLiteral: 282), height: CGFloat(integerLiteral: 35))
textView.backgroundColor = UIColor.yellow
textView.text = ""
textView.font = UIFont(name: "Arial Rounded MT Bold", size: 15)
textView.translatesAutoresizingMaskIntoConstraints = false
textView.isHidden = false
textView.translatesAutoresizingMaskIntoConstraints = false
// textView.bottomAnchor.constraint(equalTo: testLine.topAnchor, constant: 0).isActive = true
textView.leadingAnchor.constraint(equalTo: view.layoutMarginsGuide.leadingAnchor, constant: 20).isActive = true
textView.trailingAnchor.constraint(equalTo: view.layoutMarginsGuide.trailingAnchor, constant: -20).isActive = true
textView.heightAnchor.constraint(equalToConstant: 40).isActive = true
textViewBottomAnchorConstraint = textView.bottomAnchor.constraint(equalTo: testLine.topAnchor, constant: 0)
textViewBottomAnchorConstraint?.isActive = true
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// viewDidAppear may be called several times during view controller lifecycle
// triggeredAnimation ensures that animation will be called just once
if self.triggeredAnimation {
return
}
self.triggeredAnimation = true
let oldFrame = self.testLine.frame
UIView.animate(withDuration: 2, delay: 2, options: .curveEaseIn, animations: {
self.testLine.frame = CGRect(x: oldFrame.minX, y: oldFrame.minY + 30, width: oldFrame.width,
height: oldFrame.height)
self.view.layoutIfNeeded()
})
}
Anchor points make references to others positions, meaning. It is still referensed to y = 355 as you transform it and not actually "move" it.
What i recommend is that you don't mix using frame-based layout and anchorpoints / layout constraints.

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

Make a View stick to bottom with UITableViewController

I want to make a UIView stick on the bottom while I am scrolling in my UITableView.
My idea was to set a UIView with the position of right above the navigation item. Set it's zPosition to 1.
The problem is, that the yPosition of my UITableView varies.
Any idea how to solve this?
Edit:
Providing Screenshots for visible vs. expected behaviour:
Visible:
This is when I scroll:
Expected:
As seen on Tinder Camera Symbol above Table:
Edit2:
This code is what I use to put the rectangle to the bottom.
It works until I swipe the UITableView - The rectangle also scrolls up.
let bounds = self.view.bounds
let yPosition = self.navigationController?.toolbar.frame.minY
print(yPosition)
let myView = UIView(frame: CGRect(x: 0, y: yPosition! - bounds.height/6, width: bounds.width, height: bounds.height/6))
myView.backgroundColor = myColor.rookie
myView.alpha = 0.8
myView.layer.zPosition = 1
myView.isUserInteractionEnabled = true
self.view.addSubview(myView)
there is a solution for this. you can do this by disabling the Auto Layout(button.translatesAutoresizingMaskIntoConstraints = false) property of the corresponding Button or any UIView for floating button:
Swift 4
button.translatesAutoresizingMaskIntoConstraints = false
if #available(iOS 11.0, *) {
button.rightAnchor.constraint(equalTo: tableView.safeAreaLayoutGuide.rightAnchor, constant: -10).isActive = true
button.bottomAnchor.constraint(equalTo: tableView.safeAreaLayoutGuide.bottomAnchor, constant: -10).isActive = true
} else {
button.rightAnchor.constraint(equalTo: tableView.layoutMarginsGuide.rightAnchor, constant: 0).isActive = true
button.bottomAnchor.constraint(equalTo: tableView.layoutMarginsGuide.bottomAnchor, constant: -10).isActive = true
}

Resources