i am trying to place the image below the text i add
class SolicitudViewController: BaseViewController {
lazy var imagePrincipal : UIImageView = {
let imageView = UIImageView()
imageView.image = UIImage(named: "diseƱo")
imageView.contentMode = .scaleAspectFit
imageView.translatesAutoresizingMaskIntoConstraints = false
//imageView.heightAnchor.constraint(equalToConstant: 50).isActive = true
//imageView.bottomAnchor.constraint(equalToConstant: 100).isActive = true
return imageView
}()
lazy var stackView : UIStackView = {
let stack = UIStackView()
stack.axis = .vertical
stack.distribution = .fill
stack.translatesAutoresizingMaskIntoConstraints = false
stack.addArrangedSubview(imagePrincipal)
//stack.addArrangedSubview(lblsubTitulo)
//stack.addArrangedSubview(lineView)
stack.addArrangedSubview(imageEvaluando2)
stack.addArrangedSubview(imageEvaluando3)
return stack
}()
lazy var scrollView : UIScrollView = {
let scrollView = UIScrollView()
scrollView.translatesAutoresizingMaskIntoConstraints = false
scrollView.addSubview(stackView)
return scrollView
}()
override func viewDidLoad() {
super.viewDidLoad()
setSubtitle(subtitle: "test View")
}
I have tried to use this: stack.setCustomSpacing(30, after: imagePrincipal) but it positions the image on top, I want the image to be below the text
Exploring stackviews I've ran into a problem of incorrect representation if views inside of it. So, to make a long story short...
I've made a custom checkbox:
class CheckBox: UIView, CheckBoxProtocol {
required init(frame: CGRect, color: UIColor) {
super.init(frame: frame)
self.layer.borderWidth = 5
self.layer.borderColor = color.cgColor
self.addSubview(checkmark)
checkmark.tintColor = color
let gesture = UITapGestureRecognizer(target: self, action: #selector(toggle))
self.addGestureRecognizer(gesture)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
var isChecked = true
lazy var checkmark: UIImageView = {
let imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: bounds.width, height: bounds.height))
imageView.isHidden = false
imageView.contentMode = .scaleAspectFit
imageView.image = UIImage(systemName: "checkmark")
return imageView
}()
#objc func toggle() {
self.isChecked.toggle()
self.checkmark.isHidden = !self.isChecked
}
In the Controller, when I add this view to the subviews it looks fairly normal and works as it should work (check-uncheck)
However when I add checkbox to the stackview it looses its visible frame and its functionality (does not check-uncheck) - you can see it on the screenshot
screenshot
Here is the code from the ViewController:
class SettingsViewController: UIViewController {
override func loadView() {
super.loadView()
self.view.backgroundColor = .white
self.view.addSubview(stackView)
}
override func viewDidLoad() {
super.viewDidLoad()
}
lazy var stackView: UIStackView = {
let stackView = UIStackView(frame: CGRect(x: 150, y: 150, width: 0, height: 0))
stackView.axis = .horizontal
stackView.spacing = 50
stackView.alignment = .fill
stackView.distribution = .fillEqually
[redCheckbox,
greenCheckbox,
blackCheckbox,
greyCheckbox,
brownCheckbox,
yellowCheckbox,
purpleCheckbox,
orangeCheckbox].forEach {stackView.addArrangedSubview($0)}
return stackView
}()
private let frame = CGRect(x: 0, y: 0, width: 30, height: 30)
lazy var redCheckbox: CheckBox = {
let colorFactory = CardViewFactory()
let color = colorFactory.getViewColor(modelColor: CardColor.red)
let checkbox = CheckBox(frame: frame, color: color)
return checkbox
}()
lazy var greenCheckbox: CheckBox = {
let colorFactory = CardViewFactory()
let color = colorFactory.getViewColor(modelColor: CardColor.green)
let checkbox = CheckBox(frame: frame, color: color)
return checkbox
}()
lazy var blackCheckbox: CheckBox = {
let colorFactory = CardViewFactory()
let color = colorFactory.getViewColor(modelColor: CardColor.black)
let checkbox = CheckBox(frame: frame, color: color)
return checkbox
}()
lazy var greyCheckbox: CheckBox = {
let colorFactory = CardViewFactory()
let color = colorFactory.getViewColor(modelColor: CardColor.grey)
let checkbox = CheckBox(frame: frame, color: color)
return checkbox
}()
lazy var brownCheckbox: CheckBox = {
let colorFactory = CardViewFactory()
let color = colorFactory.getViewColor(modelColor: CardColor.brown)
let checkbox = CheckBox(frame: frame, color: color)
return checkbox
}()
lazy var yellowCheckbox: CheckBox = {
let colorFactory = CardViewFactory()
let color = colorFactory.getViewColor(modelColor: CardColor.yellow)
let checkbox = CheckBox(frame: frame, color: color)
return checkbox
}()
lazy var purpleCheckbox: CheckBox = {
let colorFactory = CardViewFactory()
let color = colorFactory.getViewColor(modelColor: CardColor.purple)
let checkbox = CheckBox(frame: frame, color: color)
return checkbox
}()
lazy var orangeCheckbox: CheckBox = {
let colorFactory = CardViewFactory()
let color = colorFactory.getViewColor(modelColor: CardColor.orange)
let checkbox = CheckBox(frame: frame, color: color)
return checkbox
}()
It's because we're working with the lazy property and its life cycle can be a little different. Let's set constraints after the view has loaded. What I would suggest to do:
For each checkbox, change the frame to zero:
lazy var orangeCheckbox: CheckBox = {
let colorFactory = CardViewFactory()
let color = colorFactory.getViewColor(modelColor: CardColor.orange)
let checkbox = CheckBox(frame: .zero, color: colorFactory)
checkbox.translatesAutoresizingMaskIntoConstraints = false
return checkbox
}()
Do the same to the stackView:
lazy var stackView: UIStackView = {
let stackView = UIStackView(frame: .zero)
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.axis = .horizontal
stackView.spacing = 5
stackView.alignment = .fill
stackView.distribution = .fillEqually
[redCheckbox,
greenCheckbox,
blackCheckbox,
greyCheckbox,
brownCheckbox,
yellowCheckbox,
purpleCheckbox,
orangeCheckbox].forEach {stackView.addArrangedSubview($0)}
return stackView
}()
Add some constraints on viewDidLoad:
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
view.addSubview(stackView)
stackView.centerYAnchor.constraint(equalTo: view.safeAreaLayoutGuide.centerYAnchor, constant: -100).isActive = true
stackView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 10).isActive = true
stackView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: 10).isActive = true
stackView.heightAnchor.constraint(equalToConstant: 40).isActive = true
[redCheckbox,
greenCheckbox,
blackCheckbox,
greyCheckbox,
brownCheckbox,
yellowCheckbox,
purpleCheckbox,
orangeCheckbox].forEach {
$0.heightAnchor.constraint(equalToConstant: 30).isActive = true
$0.widthAnchor.constraint(equalToConstant: 30).isActive = true
}
}
What you can do to the image inside the checkBox to work fine:
translatesAutoresizingMaskIntoConstraints = false
lazy var checkmark: UIImageView = {
let imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: 20, height: 20))
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.isHidden = false
imageView.contentMode = .scaleAspectFit
imageView.image = UIImage(systemName: "checkmark")
return imageView
}()
on your required init:
required init(frame: CGRect, color: UIColor) {
super.init(frame: frame)
self.layer.borderWidth = 5
self.layer.borderColor = color.cgColor
self.addSubview(checkmark)
checkmark.tintColor = color
checkmark.topAnchor.constraint(equalTo: topAnchor).isActive = true
checkmark.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
checkmark.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true
checkmark.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
let gesture = UITapGestureRecognizer(target: self, action: #selector(toggle))
self.addGestureRecognizer(gesture)
setNeedsDisplay()
}
To offer some additional info...
You can save yourself a lot of duplicate coding.
Take a look at this...
First, slight modifications to your Checkbox class:
class CheckBox: UIView {//, CheckBoxProtocol {
var checkmark: UIImageView = {
let imageView = UIImageView()
imageView.contentMode = .scaleAspectFit
imageView.image = UIImage(systemName: "checkmark")
return imageView
}()
var isChecked = true {
didSet {
checkmark.isHidden = !isChecked
}
}
required init(frame: CGRect, color: UIColor) {
super.init(frame: frame)
commonInit(color)
}
override init(frame: CGRect) {
super.init(frame: frame)
commonInit(.white)
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit(.white)
}
func commonInit(_ color: UIColor) {
self.layer.borderWidth = 5
self.layer.borderColor = color.cgColor
self.addSubview(checkmark)
checkmark.tintColor = color
checkmark.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
// constrain image view to all 4 sides
checkmark.topAnchor.constraint(equalTo: topAnchor),
checkmark.leadingAnchor.constraint(equalTo: leadingAnchor),
checkmark.trailingAnchor.constraint(equalTo: trailingAnchor),
checkmark.bottomAnchor.constraint(equalTo: bottomAnchor),
])
let gesture = UITapGestureRecognizer(target: self, action: #selector(toggle))
self.addGestureRecognizer(gesture)
}
#objc func toggle() {
self.isChecked.toggle()
}
}
We've used auto-layout to keep the image view the same size as the view itself.
And, by implementing the var isChecked block we have a more "automated" way of setting the image view's hidden state.
In addition, we can now get the "state" of the checkbox in the controller like this:
if thisCheckBox.isChecked {
// do something
}
Now the view controller... we'll define an array of colors, loop through them to create the CheckBox objects, and add them to the stack view:
class SettingsViewController: UIViewController {
var stackView: UIStackView = {
let stackView = UIStackView()
stackView.axis = .horizontal
// desired spacing
stackView.spacing = 12
stackView.alignment = .fill
stackView.distribution = .fillEqually
return stackView
}()
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = .white
self.view.addSubview(stackView)
stackView.translatesAutoresizingMaskIntoConstraints = false
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
// stack view Top constraint
stackView.topAnchor.constraint(equalTo: g.topAnchor, constant: 150.0),
// centered horizontally
stackView.centerXAnchor.constraint(equalTo: g.centerXAnchor),
// explicit Height
stackView.heightAnchor.constraint(equalToConstant: 30.0),
])
let colors: [UIColor] = [
.red, .green, .black, .gray,
.brown, .yellow, .purple, .orange,
]
// loop through the colors, creating a new
// CheckBox object for each color
// and add it to the stack view
colors.forEach { c in
let checkbox = CheckBox(frame: .zero, color: c)
// 1:1 aspect ratio
checkbox.widthAnchor.constraint(equalTo: checkbox.heightAnchor).isActive = true
stackView.addArrangedSubview(checkbox)
}
}
}
As you can see, we've eliminated the need for all of the individual
lazy var redCheckbox: CheckBox = { ...
lazy var greenCheckbox: CheckBox = { ...
// etc
code blocks.
I have a UIImageView, I'm trying to give it a fixed size, but it won't work, the size of the UIImageView just won't change.
this is my UIImageView:
private let profileImg: UIImageView = {
let img = UIImageView(image: UIImage(named: "profileplaceholder"))
img.contentMode = .scaleAspectFill
img.layer.borderWidth = 1.0
img.layer.borderColor = UIColor.appPurple.cgColor
img.layer.cornerRadius = img.frame.width / 2
img.clipsToBounds = true
img.frame.size.width = 80
img.frame.size.height = 80
return img
}()
I added the image to a UIStackView like that:
private lazy var mStack: UIStackView = {
let stack = UIStackView(arrangedSubviews: [self.profileImg, self.nickname, self.unfollowBtn])
stack.translatesAutoresizingMaskIntoConstraints = false
stack.distribution = .fill
stack.axis = .horizontal
stack.spacing = 1.0
return stack
}()
StackView will resize it's elements. You should give it constraint for fixed size:
private let profileImg: UIImageView = {
...
img.translatesAutoresizingMaskIntoConstraints = false
img.heightAnchor.constraint(equalToConstant: 80).isActive = true
img.widthAnchor.constraint(equalToConstant: 80).isActive = true
...
}()
I'm trying to lay out a StackView programatically, the effect I would like to achieve is
but instead I am getting
I do not understand why the loadingDotView is stretching to fill up all the space?
let loadingDotView: UIView = {
let ldv = UIView()
ldv.backgroundColor = .white
ldv.alpha = 0
ldv.frame = CGRect(x: 0, y: 0, width: 20, height: 20)
ldv.layer.cornerRadius = 10
ldv.layer.masksToBounds = true
ldv.translatesAutoresizingMaskIntoConstraints = false
return ldv
}()
let dotsStackView: UIStackView = {
let stackView = UIStackView()
stackView.axis = .horizontal
stackView.distribution = .equalSpacing
stackView.translatesAutoresizingMaskIntoConstraints = false
return stackView
}()
Setup code...
view.addSubview(dotsStackView)
NSLayoutConstraint.activate([
dotsStackView.heightAnchor.constraint(equalToConstant: 20),
dotsStackView.widthAnchor.constraint(equalToConstant: 100),
dotsStackView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
dotsStackView.centerYAnchor.constraint(equalTo: view.centerYAnchor)
])
dotsStackView.addArrangedSubview(loadingDotView)
dotsStackView.addArrangedSubview(loadingDotView)
dotsStackView.addArrangedSubview(loadingDotView)
This ( closure )
let loadingDotView: UIView = {
let ldv = UIView()
ldv.backgroundColor = .white
ldv.alpha = 0
ldv.frame = CGRect(x: 0, y: 0, width: 20, height: 20)
ldv.layer.cornerRadius = 10
ldv.layer.masksToBounds = true
ldv.translatesAutoresizingMaskIntoConstraints = false
return ldv
}()
returns same object every access so only one appears , make it ( computed property ) to create a new one every access
var loadingDotView: UIView {
let ldv = UIView()
ldv.backgroundColor = .white
ldv.alpha = 0
ldv.frame = CGRect(x: 0, y: 0, width: 20, height: 20)
ldv.layer.cornerRadius = 10
ldv.layer.masksToBounds = true
ldv.translatesAutoresizingMaskIntoConstraints = false
return ldv
}
And add
stackView.spacing = 20
stackView.distribution = .fillEqually
I am super new iOS development and StackViews in general and need help calculating dynamic height in instances where a stack view will not be shown. There are cases where certain elements will not be shown depending on what I get back from the server.
However, when I call removeArrangedSubview the element is removed but the height isn't adjusted dynamically. How can I fix this?
I would like to avoid the Interface Builder all together and just do this programmatically. I have been using layout anchors for constraints.
Here's my code. You can put in a playground to see it.
//: Playground - noun: a place where people can play
import UIKit
import Foundation
let view = UIView(frame: CGRect(x: 0, y: 0, width: 800, height: 140))
let firstStackView = UIStackView(frame: CGRectZero)
//firstStackView.heightAnchor.constraintGreaterThanOrEqualToConstant(40).active = true
firstStackView.axis = .Vertical
firstStackView.alignment = .Fill
firstStackView.distribution = .EqualSpacing
let titleStackView = UIStackView(frame: CGRectZero)
titleStackView.axis = .Horizontal
titleStackView.alignment = .Fill
titleStackView.distribution = .Fill
titleStackView.spacing = 3
firstStackView.addArrangedSubview(titleStackView)
let productStackView = UIStackView(frame: .zero)
productStackView.axis = .Horizontal
productStackView.alignment = .Leading
productStackView.distribution = .Fill
productStackView.spacing = 3
firstStackView.addArrangedSubview(productStackView)
//firstStackView.removeArrangedSubview(productStackView)
let secondStackView = UIStackView(frame: CGRectZero)
//secondStackView.heightAnchor.constraintEqualToConstant(30).active = true
secondStackView.axis = .Horizontal
secondStackView.distribution = .EqualSpacing
let title = UILabel(frame: CGRectZero)
title.text = "test1"
title.textColor = .blackColor()
//labelOne.backgroundColor = .blueColor()
let size = title.sizeThatFits(CGSizeZero)
print("\(size)")
title.widthAnchor.constraintEqualToConstant(size.width).active = true
//labelOne.heightAnchor.constraintEqualToConstant(30).active = true
titleStackView.addArrangedSubview(title)
let assigneeLabel = UILabel(frame: CGRectZero)
assigneeLabel.text = "test2"
assigneeLabel.textColor = .blackColor()
//labelTest.backgroundColor = .redColor()
assigneeLabel.textAlignment = .Left
//labelTest.heightAnchor.constraintEqualToConstant(30).active = true
titleStackView.addArrangedSubview(assigneeLabel)
let actions = UIButton(type: .Custom)
//buttonOne.backgroundColor = .redColor()
actions.setTitle("some button", forState: .Normal)
actions.setTitleColor(.blackColor(), forState: .Normal)
titleStackView.addArrangedSubview(actions)
let productOne = UILabel(frame: CGRectZero)
productOne.text = "something1"
productOne.numberOfLines = 0
let productLabelSize = productOne.sizeThatFits(CGSizeZero)
productOne.widthAnchor.constraintEqualToConstant(productLabelSize.width).active = true
productOne.textColor = .blackColor()
//labelTwo.backgroundColor = .blueColor()
productStackView.removeArrangedSubview(productOne)
//productStackView.addArrangedSubview(productOne)
let productTwo = UILabel(frame: CGRectZero)
productTwo.text = "something2"
productTwo.numberOfLines = 0
//productTwo.heightAnchor.constraintEqualToConstant(30).active = true
productTwo.textColor = .blackColor()
//labelTwo.backgroundColor = .blueColor()
productStackView.removeArrangedSubview(productTwo)
//productStackView.addArrangedSubview(productTwo)
let labelThree = UILabel(frame: CGRectZero)
labelThree.text = "sometime"
//labelThree.heightAnchor.constraintEqualToConstant(30).active = true
labelThree.textColor = .blackColor()
//labelThree.backgroundColor = .blueColor()
firstStackView.addArrangedSubview(labelThree)
let descriptionView = UILabel(frame: CGRectZero)
descriptionView.text = "some description about something"
descriptionView.textColor = .blackColor()
//descriptionView.backgroundColor = .redColor()
secondStackView.addArrangedSubview(descriptionView)
let tagsView = UILabel(frame: CGRectZero)
tagsView.text = "some more things"
tagsView.textColor = .blackColor()
secondStackView.addArrangedSubview(tagsView)
secondStackView.trailingAnchor.constraintEqualToAnchor(tagsView.trailingAnchor).active = true
let stackView = UIStackView(arrangedSubviews: [firstStackView, secondStackView])
stackView.layoutMargins = UIEdgeInsets(top: 0, left: 20, bottom: 0, right: 20)
stackView.layoutMarginsRelativeArrangement = true
stackView.axis = .Vertical
stackView.frame = view.bounds
stackView.distribution = .FillProportionally
view.addSubview(stackView)
Before element is removed:
After element is removed:
I would like that gap to be gone and the height adjusted dynamically.
You can add a constraint for the height of the view if you are using auto layout.
So like for initial setup, you could do something like:
class YourClass : UIViewController() {
var heightConstraint = NSLayoutConstraint()
func someMethod () {
// load your View
// get the height of view.
heightConstraint = yourView.heightAnchor.constraintEqualToConstant(height)
self.view.addConstraint(heightConstraint)
}
func deleteMemberStackView() {
/// After deleting the member, get the new height of the view and do this
self.view.removeConstraint(heightConstraint)
heightConstraint = yourView.heightAnchor.constraintEqualToConstant(height)
self.view.addConstraint(heightConstraint)
UIView.animateViewDuration(0.3, completion: {
self.view.layoutIfNeeded()
})
}
}