Swift: Programmatic Autolayout broken by UNavigationController - ios

I have inherited a UIKit app where my predecessor has written all the interface code by hand. I have a view controller, a simple login screen that works just fine, but when I add it to a UINavigationController it is oddly stretched. This doesn't make a lot of sense to me.
I feel like I must be missing some simple flag, or what do I need to do to make the Navcontroller play nice with this programmatic autolayout (which is hopefully my last ever)
// this a view controller extension
func apply(constraints: [NSLayoutConstraint]) {
translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate(constraints)
}
There's my handy dandy, seemingly sane utility for applying constraints. As an example, the nice little oval thing making my password textfield looks nice has these constraints set up.
// these constraints are being setup in the view controller
emailCapsuleView.apply(constraints: [
emailCapsuleView.topAnchor.constraint(equalTo: subBigTitle.bottomAnchor, constant: 24.0),
emailCapsuleView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
emailCapsuleView.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.8),
emailCapsuleView.heightAnchor.constraint(equalToConstant: Dimensions.inputFieldHeight)
])
Outside of a UINavigationController everything is fine,
but in a UINavigationController everythhing is super wide and broken (simulator shot)
The width is not the problem here - if I use a constant for the width, my subview is still off center despite having it's centerX sets to the view controller's view's center X.

Configure your navigation controller and after that try like this:
set your objects:
let emailTextfield: UITextField = {
let tf = UITextField()
tf.backgroundColor = .white
tf.layer.borderWidth = 1
tf.layer.borderColor = UIColor.lightGray.cgColor
tf.setPadding(left: 10, right: 10)// use my extension below
tf.attributedPlaceholder = NSAttributedString(string: "Email address", attributes: [.foregroundColor: UIColor.lightGray])
tf.layer.cornerRadius = 14
tf.clipsToBounds = true
tf.translatesAutoresizingMaskIntoConstraints = false
return tf
}()
let passTextfield: UITextField = {
let tf = UITextField()
tf.backgroundColor = .white
tf.layer.borderWidth = 1
tf.layer.borderColor = UIColor.lightGray.cgColor
tf.setPadding(left: 10, right: 10)// use my extension below
tf.attributedPlaceholder = NSAttributedString(string: "Password", attributes: [.foregroundColor: UIColor.lightGray])
tf.layer.cornerRadius = 14
tf.clipsToBounds = true
tf.translatesAutoresizingMaskIntoConstraints = false
return tf
}()
let loginButton: UIButton = {
let b = UIButton(type: .system)
b.backgroundColor = .black
b.setTitle("Save Image", for: .normal)
b.titleLabel?.font = .systemFont(ofSize: 16, weight: .semibold)
b.setTitleColor(.white, for: .normal)
b.layer.cornerRadius = 14
b.clipsToBounds = true
b.translatesAutoresizingMaskIntoConstraints = false
return b
}()
let subBigTitle: UILabel = {
let l = UILabel()
l.text = "Your Big Title"
l.font = .systemFont(ofSize: 30, weight: .regular)
l.textColor = .black
l.textAlignment = .center
l.translatesAutoresizingMaskIntoConstraints = false
return l
}()
Now in viewDidLoad set stackView, bigTitle label and constraints:
view.addSubview(subBigTitle)
subBigTitle.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
subBigTitle.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor).isActive = true
subBigTitle.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor).isActive = true
subBigTitle.heightAnchor.constraint(equalToConstant: 60).isActive = true
let stackView = UIStackView(arrangedSubviews: [emailTextfield, passTextfield, loginButton])
stackView.axis = .vertical
stackView.distribution = .fillEqually
stackView.spacing = 10
stackView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(stackView)
stackView.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.8).isActive = true
stackView.heightAnchor.constraint(equalToConstant: 170).isActive = true
stackView.topAnchor.constraint(equalTo: subBigTitle.bottomAnchor, constant: 24).isActive = true
stackView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
to add text padding to your textfield use my extension:
extension UITextField {
func setPadding(left: CGFloat, right: CGFloat){
let paddingView = UIView(frame: CGRect(x: 0, y: 0, width: left, height: self.frame.size.height))
self.leftView = paddingView
self.leftViewMode = .always
let paddingViewRight = UIView(frame: CGRect(x: 0, y: 0, width: right, height: self.frame.size.height))
self.rightView = paddingViewRight
self.rightViewMode = .always
}
}
and this is the result:

Related

Swift - Subviews frame.maxY reading incorrectly

I have a basic sign up screen set up programmatically with the UI elements inside a view that is itself inside a scroll view.
The last UI element in the screen is a register button. I set up a keyboard notification observer with the Will Show and Will Hide notifications.
I am running this code on iPod touch 7th gen simulator.
My problem is when trying to read the maxY value of the sign up button and compare it to the keyboard minY it prints wrong numbers.
The keyboard is clearly blocking the register button which mean the button's maxY value will be greater the the keyboard minY value.
However the values printed shows that there is something wrong with the reading of the register button frame.
Here is my code:
import UIKit
class RegisterVC: UIViewController {
private let scrollView: UIScrollView = {
let scroll = UIScrollView()
scroll.clipsToBounds = true
scroll.isScrollEnabled = true
scroll.translatesAutoresizingMaskIntoConstraints = false
scroll.showsVerticalScrollIndicator = false
return scroll
}()
private let scrollInnerView: UIView = {
let innerView = UIView()
innerView.translatesAutoresizingMaskIntoConstraints = false
return innerView
}()
private let profilePic: UIImageView = {
let imageView = UIImageView()
imageView.image = UIImage(systemName: "person.circle")
imageView.contentMode = .scaleAspectFit
imageView.tintColor = .gray
imageView.translatesAutoresizingMaskIntoConstraints = false
return imageView
}()
private let usernameField: UITextField = {
let field = UITextField()
field.autocapitalizationType = .none
field.autocorrectionType = .no
field.returnKeyType = .next
field.layer.cornerRadius = 12
field.layer.borderWidth = 1
field.layer.borderColor = UIColor.lightGray.cgColor
field.placeholder = "Username..."
field.leftView = UIView(frame: CGRect(x: 0, y: 0, width: 5, height: 0))
field.leftViewMode = .always
field.backgroundColor = .white
field.keyboardType = .default
field.isHighlighted = false
field.textAlignment = .left
field.translatesAutoresizingMaskIntoConstraints = false
return field
}()
private let emailField: UITextField = {
let field = UITextField()
field.autocapitalizationType = .none
field.autocorrectionType = .no
field.returnKeyType = .next
field.layer.cornerRadius = 12
field.layer.borderWidth = 1
field.layer.borderColor = UIColor.lightGray.cgColor
field.placeholder = "Email Address..."
field.leftView = UIView(frame: CGRect(x: 0, y: 0, width: 5, height: 0))
field.leftViewMode = .always
field.backgroundColor = .white
field.keyboardType = .default
field.textAlignment = .left
field.translatesAutoresizingMaskIntoConstraints = false
return field
}()
private let passwordField: UITextField = {
let field = UITextField()
field.autocapitalizationType = .none
field.autocorrectionType = .no
field.returnKeyType = .done
field.layer.cornerRadius = 12
field.layer.borderWidth = 1
field.layer.borderColor = UIColor.lightGray.cgColor
field.placeholder = "Password..."
field.leftView = UIView(frame: CGRect(x: 0, y: 0, width: 5, height: 0))
field.leftViewMode = .always
field.backgroundColor = .white
field.isSecureTextEntry = true
field.textAlignment = .left
field.keyboardType = .default
field.translatesAutoresizingMaskIntoConstraints = false
return field
}()
private let registerButton: UIButton = {
let button = UIButton()
button.setTitle("Create Account", for: .normal)
button.backgroundColor = .systemGreen
button.setTitleColor(.white, for: .normal)
button.layer.cornerRadius = 12
button.layer.masksToBounds = true
button.titleLabel?.font = .systemFont(ofSize: 20, weight: .bold)
button.translatesAutoresizingMaskIntoConstraints = false
return button
}()
override func viewDidLoad() {
super.viewDidLoad()
title = "Create Account"
view.backgroundColor = .white
view.addSubview(scrollView)
scrollView.addSubview(scrollInnerView)
scrollInnerView.addSubview(profilePic)
scrollInnerView.addSubview(usernameField)
scrollInnerView.addSubview(emailField)
scrollInnerView.addSubview(passwordField)
scrollInnerView.addSubview(registerButton)
usernameField.delegate = self
emailField.delegate = self
passwordField.delegate = self
profilePic.isUserInteractionEnabled = true
registerButton.addTarget(self,
action: #selector(registerButtonTapped),
for: .touchUpInside)
setUpKeyboard()
setUpConstraints()
}
private func setUpConstraints() {
// Scroll View Constraints
scrollView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
scrollView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor).isActive = true
// Scroll Inner View Constraints
scrollInnerView.topAnchor.constraint(equalTo: scrollView.topAnchor).isActive = true
scrollInnerView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor).isActive = true
scrollInnerView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor).isActive = true
scrollInnerView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor).isActive = true
scrollInnerView.widthAnchor.constraint(equalTo: scrollView.widthAnchor).isActive = true
scrollInnerView.heightAnchor.constraint(equalTo: scrollView.heightAnchor, constant: 1).isActive = true
// Profile Picture Constraints
profilePic.widthAnchor.constraint(equalTo: scrollInnerView.widthAnchor, multiplier: 1/3).isActive = true
profilePic.heightAnchor.constraint(equalTo: scrollInnerView.widthAnchor, multiplier: 1/3).isActive = true
profilePic.centerXAnchor.constraint(equalTo: scrollInnerView.centerXAnchor).isActive = true
profilePic.topAnchor.constraint(equalTo: scrollInnerView.topAnchor, constant: 10).isActive = true
// User Name Field Constraints
usernameField.widthAnchor.constraint(equalTo: scrollInnerView.widthAnchor, constant: -60).isActive = true
usernameField.heightAnchor.constraint(equalToConstant: 45).isActive = true
usernameField.topAnchor.constraint(equalTo: profilePic.bottomAnchor, constant: 10).isActive = true
usernameField.centerXAnchor.constraint(equalTo: profilePic.centerXAnchor).isActive = true
// Email Field Constraints
emailField.widthAnchor.constraint(equalTo: usernameField.widthAnchor).isActive = true
emailField.heightAnchor.constraint(equalTo: usernameField.heightAnchor).isActive = true
emailField.topAnchor.constraint(equalTo: usernameField.bottomAnchor, constant: 10).isActive = true
emailField.centerXAnchor.constraint(equalTo: usernameField.centerXAnchor).isActive = true
// Password Field Constraints
passwordField.widthAnchor.constraint(equalTo: emailField.widthAnchor).isActive = true
passwordField.heightAnchor.constraint(equalTo: emailField.heightAnchor).isActive = true
passwordField.topAnchor.constraint(equalTo: emailField.bottomAnchor, constant: 10).isActive = true
passwordField.centerXAnchor.constraint(equalTo: emailField.centerXAnchor).isActive = true
// Register Button Constraints
registerButton.widthAnchor.constraint(equalTo: passwordField.widthAnchor).isActive = true
registerButton.heightAnchor.constraint(equalTo: passwordField.heightAnchor).isActive = true
registerButton.topAnchor.constraint(equalTo: passwordField.bottomAnchor, constant: 20).isActive = true
registerButton.centerXAnchor.constraint(equalTo: passwordField.centerXAnchor).isActive = true
}
private func setUpKeyboard() {
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShowNotification), name: UIResponder.keyboardWillShowNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHideNotification), name: UIResponder.keyboardWillHideNotification, object: nil)
}
#objc private func keyboardWillShowNotification(_ notification: NSNotification) {
guard let keyboardSize = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue
else {
return
}
print(keyboardSize.minY)
print(registerButton.frame.maxY)
}
}
It's because the keyboard frame and the button frame are in two different coordinate systems. You cannot compare them directly. You need to convert the button frame to window coordinates before comparing them. Or else convert the keyboard frame to the button frame coordinates (the button's superview).
Actually what I typically do is convert the keyboard frame to the internal coordinates of the target view and compare that to the target view's bounds. For example:
// n is the notification
let d = n.userInfo!
var r = d[UIResponder.keyboardFrameEndUserInfoKey] as! CGRect
r = self.slidingView.convert(r, from:nil) // <- this is the key move!
let h = self.slidingView.bounds.intersection(r).height
That tells me whether the keyboard would cover the sliding view, and if so, by how much.

Swift - UIView moves when changing tab controller tabs

Inside the viewDidAppear I have a function that contains this code, in order to make a UIView:
let contentView = UIView()
func addSleepingView() {
contentView.backgroundColor = .systemYellow.withAlphaComponent(0.25)
view.addSubview(contentView)
contentView.translatesAutoresizingMaskIntoConstraints = false
contentView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
contentView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
// Anchor your view right above the tabBar
contentView.bottomAnchor.constraint(equalTo: (tabBarController?.tabBar.topAnchor)!).isActive = true
contentView.heightAnchor.constraint(equalToConstant: 50).isActive = true
let label = UILabel()
label.text = "Test"
label.frame = CGRect(x: 0, y: 0, width: 25, height: 34.0)
label.lineBreakMode = .byWordWrapping
label.numberOfLines = 0
label.textAlignment = .center
label.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(label)
label.centerXAnchor.constraint(equalTo: contentView.centerXAnchor).isActive = true
label.centerYAnchor.constraint(equalTo: contentView.centerYAnchor).isActive = true
let button = UIButton()
button.setImage(UIImage(systemName: "arrow.clockwise", withConfiguration: UIImage.SymbolConfiguration(scale: .large)), for: UIControl.State.normal)
button.tintColor = .systemGray
button.frame = CGRect(x: self.view.bounds.width-42, y: 8, width: 34, height: 34.0)
button.isUserInteractionEnabled = true
button.addTarget(self, action: #selector(wakeupFunction), for: .touchUpInside)
contentView.addSubview(button)
let button2 = UIButton()
button2.setImage(UIImage(systemName: "exclamationmark.triangle", withConfiguration: UIImage.SymbolConfiguration(scale: .large)), for: UIControl.State.normal)
button2.tintColor = .label
button2.frame = CGRect(x: 8, y: 8, width: 34, height: 34.0)
button2.isUserInteractionEnabled = false
contentView.addSubview(button2)
contentView.bringSubviewToFront(button)
}
This is what it looks like:
Now this is exactly how I want it. The problem comes when I change tab. For example go to the last tab, and back to the first tab again. Then it looks like this:
What am I doing wrong here?
You can just add your code of activating constraints inside the DispatchQueue.main block
Updated Line you can change and it will start working as you're expectation.
// Anchor your view right above the tabBar
DispatchQueue.main.async
{
self.contentView.bottomAnchor.constraint(equalTo: (self.tabBarController?.tabBar.topAnchor)!).isActive = true
self.contentView.heightAnchor.constraint(equalToConstant: 50).isActive = true
}

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:

ViewController Layout Help Needed

I'm using XCode to build a UI for a countdown timer/metronome app. After struggling with layout constraints, I discovered vertical and horizontal stack views, which I thought would make things much easier.
However, the outermost stackview's boundaries appear to be outside the view of the device. I removed all the layout constraints, but that didn't make any difference.
Here's a partial screenshot of the project in XCode:
StackView boundaries
Sorry if this question has been asked before - I looked but haven't found anything.
I'm a professional software developer for the last 30 years or so. The languages I work with on a daily basis for the last 20 years or so are Java, C++, and Fortran. So dealing with UI layout in IOS is frustrating, to say the least!
Thanks in advance for any help!
Edit: Here's a screenshot of the uppermost stack view's size inspector: size inspector
I know your frustration well because it was mine too when I start, and I decided to give you a hand. Make all UI programmatically like this:
in your Controller class built your objects programmatically:
let buttonPlay: UIButton = {
let button = UIButton(type: .system)
button.backgroundColor = UIColor.yourColor
let image = UIImage(systemName: "play.fill")
button.setImage(image, for: .normal)
button.tintColor = .black
button.heightAnchor.constraint(equalToConstant: 50).isActive = true
button.layer.cornerRadius = 25
button.clipsToBounds = true
return button
}()
let buttonPause: UIButton = {
let button = UIButton(type: .system)
button.backgroundColor = UIColor.yourColor
let image = UIImage(systemName: "pause.fill")
button.setImage(image, for: .normal)
button.tintColor = .black
button.heightAnchor.constraint(equalToConstant: 50).isActive = true
button.layer.cornerRadius = 25
button.clipsToBounds = true
return button
}()
let buttonStop: UIButton = {
let button = UIButton(type: .system)
button.backgroundColor = UIColor.yourColor
let image = UIImage(systemName: "stop.fill")
button.setImage(image, for: .normal)
button.tintColor = .black
button.heightAnchor.constraint(equalToConstant: 50).isActive = true
button.layer.cornerRadius = 25
button.clipsToBounds = true
return button
}()
let minutesLabel: UILabel = {
let label = UILabel()
label.backgroundColor = .white
label.text = "MM"
label.textColor = .gray
label.textAlignment = .center
label.font = .systemFont(ofSize: 20, weight: .semibold)
return label
}()
let secondsLabel: UILabel = {
let label = UILabel()
label.backgroundColor = .white
label.text = "SS"
label.textColor = .gray
label.textAlignment = .center
label.font = .systemFont(ofSize: 20, weight: .semibold)
return label
}()
let separatorLabel: UILabel = {
let label = UILabel()
label.backgroundColor = .clear
label.text = ":"
label.textColor = .black
label.textAlignment = .center
label.font = .systemFont(ofSize: 20, weight: .semibold)
return label
}()
let titleLabel: UILabel = {
let label = UILabel()
label.backgroundColor = UIColor.yourColor
label.text = "Title label"
label.textColor = .black
label.textAlignment = .center
label.font = .systemFont(ofSize: 20, weight: .semibold)
label.translatesAutoresizingMaskIntoConstraints = false // this distactive automatic constraints
return label
}()
let timeLabel: UILabel = {
let label = UILabel()
label.backgroundColor = .clear
label.text = "Tempo:"
label.textColor = .black
label.font = .systemFont(ofSize: 20, weight: .regular)
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
let imageViewTime: UIImageView = {
let image = UIImage(named: "yourImage")?.withRenderingMode(.alwaysTemplate)
let imageView = UIImageView(image: image)
imageView.tintColor = .white
imageView.backgroundColor = .purple
imageView.contentMode = .scaleAspectFit
imageView.translatesAutoresizingMaskIntoConstraints = false
return imageView
}()
let slider: UISlider = {
let s = UISlider()
s.maximumValue = 0
s.maximumValue = 100
s.isContinuous = true
s.tintColor = .black
s.addTarget(self, action: #selector(sliderInAction), for: .valueChanged) // call the function below to show slider value
s.translatesAutoresizingMaskIntoConstraints = false
return s
}()
let sliderLabel: UILabel = {
let label = UILabel()
label.backgroundColor = .clear
label.text = "2"
label.textAlignment = .center
label.textColor = .black
label.font = .systemFont(ofSize: 16, weight: .regular)
label.widthAnchor.constraint(equalToConstant: 30).isActive = true
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
let containerSlider: UIView = {
let v = UIView()
v.backgroundColor = UIColor.yourColor
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
In viewDidLoad set your background color and call the function to setup the programmatically constraints:
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor.yourColor
setupConstraints()
}
now set the constraints with autolayout:
fileprivate func setupConstraints() {
let stackViewTime = UIStackView(arrangedSubviews: [minutesLabel, separatorLabel, secondsLabel])
stackViewTime.distribution = .fillProportionally
stackViewTime.spacing = 4
stackViewTime.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(stackViewTime)
stackViewTime.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 10).isActive = true
stackViewTime.heightAnchor.constraint(equalToConstant: 50).isActive = true
stackViewTime.widthAnchor.constraint(equalToConstant: 148).isActive = true
stackViewTime.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
let stackViewPlayer = UIStackView(arrangedSubviews: [buttonPlay, buttonPause, buttonStop])
stackViewPlayer.distribution = .fillEqually
stackViewPlayer.spacing = 10
stackViewPlayer.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(stackViewPlayer)
stackViewPlayer.widthAnchor.constraint(equalToConstant: 170).isActive = true
stackViewPlayer.heightAnchor.constraint(equalToConstant: 50).isActive = true
stackViewPlayer.centerXAnchor.constraint(equalTo: stackViewTime.centerXAnchor).isActive = true
stackViewPlayer.topAnchor.constraint(equalTo: stackViewTime.bottomAnchor, constant: 20).isActive = true
view.addSubview(imageViewTime)
imageViewTime.topAnchor.constraint(equalTo: stackViewPlayer.bottomAnchor, constant: 20).isActive = true
imageViewTime.heightAnchor.constraint(equalToConstant: 80).isActive = true
imageViewTime.widthAnchor.constraint(equalToConstant: 60).isActive = true
imageViewTime.centerXAnchor.constraint(equalTo: stackViewTime.centerXAnchor).isActive = true
view.addSubview(titleLabel)
titleLabel.topAnchor.constraint(equalTo: imageViewTime.bottomAnchor, constant: 20).isActive = true
titleLabel.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 30).isActive = true
titleLabel.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -30).isActive = true
titleLabel.heightAnchor.constraint(equalToConstant: 50).isActive = true
view.addSubview(timeLabel)
timeLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 10).isActive = true
timeLabel.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 10).isActive = true
timeLabel.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -10).isActive = true
timeLabel.heightAnchor.constraint(equalToConstant: 50).isActive = true
let stackSlider = UIStackView(arrangedSubviews: [sliderLabel, slider])
stackSlider.distribution = .fill
stackSlider.spacing = 10
stackSlider.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(containerSlider)
containerSlider.topAnchor.constraint(equalTo: timeLabel.bottomAnchor, constant: 0).isActive = true
containerSlider.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 10).isActive = true
containerSlider.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -10).isActive = true
containerSlider.heightAnchor.constraint(equalToConstant: 30).isActive = true
containerSlider.addSubview(stackSlider)
stackSlider.topAnchor.constraint(equalTo: containerSlider.topAnchor).isActive = true
stackSlider.bottomAnchor.constraint(equalTo: containerSlider.bottomAnchor).isActive = true
stackSlider.leadingAnchor.constraint(equalTo: containerSlider.leadingAnchor, constant: 10).isActive = true
stackSlider.trailingAnchor.constraint(equalTo: containerSlider.trailingAnchor, constant: -10).isActive = true
}
the last step set the function to show the slider value in your slider label when you move it:
#objc fileprivate func sliderInAction() {
sliderLabel.text = "\(Int(slider.value))"
}
This is the result:
Play with this code, an experienced developer like you should take a short time to write the code.

Setting background color for customView has no effect

I have a customView which I display by after a button-tap. My problem is that setting .backgroundColor has no effect and it is just a clear background.
CustomView:
let wishWishView: WishStackView = {
let v = WishStackView()
v.backgroundColor = .cyan
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
AddViewButton:
#objc func addWishButtonTapped(){
self.view.addSubview(self.wishWishView)
wishWishView.centerYAnchor.constraint(equalTo: self.view.centerYAnchor).isActive = true
wishWishView.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true
}
WishWishView is just a simple UIView with a StackView inside of it.
My guess would be that it is a UIStackView, which does not support having a background color.
You can read more about why this happens, and possible workarounds here: https://stackoverflow.com/a/34868367/3992237
Your code is Totally wrong, first you set your view like this:
let wishWishView: UIView = {
let v = UIView()
v.backgroundColor = .cyan
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
now set your stackView, in my example I put in 2 label in vertical axis:
let stackView: UIStackView = {
let label1 = UILabel()
label1.text = "Label 1"
label1.textColor = .red
label1.font = UIFont.systemFont(ofSize: 16)
let label2 = UILabel()
label2.text = "Label 2"
label2.textColor = .black
label2.font = UIFont.systemFont(ofSize: 16)
let sV = UIStackView(arrangedSubviews: [label1, label2])
sV.axis = .vertical
sV.distribution = .fillEqually
sV.translatesAutoresizingMaskIntoConstraints = false
return sV
}()
after that set your button:
let buttonAddView: UIButton = {
let button = UIButton(type: .system)
button.setTitle("Add View", for: .normal)
button.setTitleColor(.white, for: .normal)
button.backgroundColor = .red
button.addTarget(self, action: #selector(addWishButtonTapped), for: .touchUpInside)
button.translatesAutoresizingMaskIntoConstraints = false
return button
}()
in viewDidLoad add button and set constraints
view.addSubview(buttonAddView)
buttonAddView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20).isActive = true
buttonAddView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -20).isActive = true
buttonAddView.heightAnchor.constraint(equalToConstant: 50).isActive = true
buttonAddView.widthAnchor.constraint(equalToConstant: 120).isActive = true
now write the function that add the view with stack view inside when the button is tapped with right constraints, in your function you call only the position of the view but not the size of it. Write your function like this:
#objc func addWishButtonTapped(){
view.addSubview(self.wishWishView)
wishWishView.centerYAnchor.constraint(equalTo: self.view.centerYAnchor).isActive = true
wishWishView.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true
wishWishView.heightAnchor.constraint(equalToConstant: 100).isActive = true
wishWishView.widthAnchor.constraint(equalToConstant: 200).isActive = true
wishWishView.addSubview(stackView)
stackView.topAnchor.constraint(equalTo: wishWishView.topAnchor).isActive = true
stackView.bottomAnchor.constraint(equalTo: wishWishView.bottomAnchor).isActive = true
stackView.leadingAnchor.constraint(equalTo: wishWishView.leadingAnchor, constant: 10).isActive = true
stackView.trailingAnchor.constraint(equalTo: wishWishView.trailingAnchor, constant: -10).isActive = true
}
Now simply change background of the wishWishView to automatically set stackView background, and that's it...

Resources