View height wrap child views content - ios

I want to add a image left of the super view and a custom label center of the superview. Also superview's height must wrap children. Also I am adding superview in to a stack(fill,fill distribution and alignment ). Code is below but imageview not showing. What is problem here?
let topView = UIView()
topView.translatesAutoresizingMaskIntoConstraints = false
topView.heightAnchor.constraint(equalToConstant: 30).isActive = true
topView.widthAnchor.constraint(equalToConstant: self.view.frame.size.width).isActive = true
let backImageView: UIImageView = UIImageView()
backImageView.isUserInteractionEnabled = true
backImageView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(backButtonClicked)))
backImageView.image = UIImage(named: "backButton")
backImageView.contentMode = .scaleAspectFit
backImageView.clipsToBounds = true
topView.addSubview(backImageView)
backImageView.leftAnchor.constraint(equalTo: topView.leftAnchor).isActive = true
backImageView.topAnchor.constraint(equalTo: topView.topAnchor).isActive = true
backImageView.bottomAnchor.constraint(equalTo: topView.bottomAnchor).isActive = true
backImageView.centerYAnchor.constraint(equalTo: topView.centerYAnchor).isActive = true
topView.addSubview(titleText)
titleText.centerXAnchor.constraint(equalTo: topView.centerXAnchor).isActive = true
titleText.centerYAnchor.constraint(equalTo: topView.centerYAnchor).isActive = true

I think your problem is in the backImageView Y constraint
backImageView.centerYAnchor.constraint(equalTo: topView.centerYAnchor).isActive = true
should be
backImageView.centerXAnchor.constraint(equalTo: topView.centerXAnchor).isActive = true
the Y is for vertical axix, so in your 4 contraints you have 3 vertical constrains, and 1 horizontal constraint. Replace it with a centerXAnchor and you should be good.

Related

Center stack view elements and not fill them

I am using a UIStackView as UITableView's BackGroundView property so when there was an error getting the collection that populates the tableView I can call a function that displays this stack view containing views that show a warning message and a retry button.
I tested doing a similar behaviour in an empty UIViewController so I could center the stackView and its children. The solution worked when I pinned the stack view to the superView's trailing and leading, centered it vertically and set it's top anchor to be greater or equal to the superView's top anchor and similarly it's bottom anchor is greater or equal to the superView's bottom anchor. I have also set the alignment to center and distribution to fill and all seemed to work properly.
Here are some screenshots:
I used this code in a UITableView's extension, but could only reproduce this behaviour. Are there any errors on this code?
func show(error: Bool, withMessage message : String? = nil, andRetryAction retry: (() -> Void)? = nil){
if error{
let iconLabel = UILabel()
iconLabel.GMDIcon = .gmdErrorOutline
iconLabel.textAlignment = .center
iconLabel.numberOfLines = 0
iconLabel.font = iconLabel.font.withSize(50)
iconLabel.textColor = Constants.Colors.ErrorColor
iconLabel.backgroundColor = .blue
let messageLabel = UILabel()
messageLabel.text = message ?? "Ocorreu um erro"
messageLabel.textColor = Constants.Colors.ErrorColor
messageLabel.numberOfLines = 0
messageLabel.textAlignment = .center
messageLabel.font = UIFont(name: "TrebuchetMS", size: 20)
messageLabel.backgroundColor = .green
var views: [UIView] = [iconLabel, messageLabel]
if let retry = retry{
let button = RaisedButton(title: "Tentar novamente")
button.pulseColor = Constants.Colors.PrimaryTextColor
button.backgroundColor = Constants.Colors.PrimaryColor
button.titleColor = .white
button.actionHandle(controlEvents: .touchUpInside, ForAction: retry)
button.contentEdgeInsets = UIEdgeInsetsMake(10,10,10,10)
views.append(button)
}
}else{
self.backgroundView = nil
}
}
let stack = UIStackView()
stack.spacing = 10
stack.axis = .vertical
stack.alignment = .center
stack.distribution = .fill
stack.translatesAutoresizingMaskIntoConstraints = false
for view in views{
view.translatesAutoresizingMaskIntoConstraints = false
stack.addArrangedSubview(view)
}
if self.tableFooterView == nil{
tableFooterView = UIView()
}
self.backgroundView = stack;
if #available(iOS 11, *) {
let guide = self.safeAreaLayoutGuide
stack.topAnchor.constraint(greaterThanOrEqualTo: guide.topAnchor).isActive = true
stack.bottomAnchor.constraint(greaterThanOrEqualTo: guide.bottomAnchor).isActive = true
stack.leadingAnchor.constraint(equalTo: guide.leadingAnchor).isActive = true
stack.trailingAnchor.constraint(equalTo: guide.trailingAnchor).isActive = true
stack.centerYAnchor.constraint(equalTo: guide.centerYAnchor).isActive = true
} else {
stack.topAnchor.constraint(greaterThanOrEqualTo: self.topAnchor).isActive = true
stack.bottomAnchor.constraint(greaterThanOrEqualTo: self.bottomAnchor).isActive = true
stack.leadingAnchor.constraint(equalTo: self.leadingAnchor).isActive = true
stack.trailingAnchor.constraint(equalTo: self.trailingAnchor).isActive = true
stack.centerYAnchor.constraint(equalTo: self.centerYAnchor).isActive = true
}
A stack view knows its height if its elements all have an intrinsic size (ie its the sum of their individual heights + the inter item spacing). In this case because you have a 2 y position constraints, you are implying a height, so your constraints are unsatisfiable. The only y axis constraint you need is center vertically. get rid of the top and bottom constraints. The system will then use the intrinsic size to compute the height of the stack view and center it vertically in the background view. Leave your x axis constraints as is.

UIScrollView doesn't respond to gestures

I have a UIScrollView which is a dashboard basically with relevant information for the user. It has a pager underneath and contains three UIViews at the moment.
I used to set the different views with a CGRect as the frame, setting the x offset to the width of the UIScrollView * the page number. This worked fine, but it was a hassle to make the UIScrollView disappear when scrolling in the UITableView underneath.
I am now using AutoLayout with constraints to display the information, which works fine. However, my UIScrollView is not scrolling anymore. It does not respond to any swipe gestures I perform. However, it does respond to taps in the pager, which shows me the offsets and constraints are right, I am just unable to scroll in it. Take a look:
My UIScrollView is made up like this:
var dashboardView: UIScrollView = {
let dashboardView = UIScrollView()
dashboardView.translatesAutoresizingMaskIntoConstraints = false
dashboardView.backgroundColor = UIColor.clear
dashboardView.layer.masksToBounds = true
dashboardView.layer.cornerRadius = 10
dashboardView.isPagingEnabled = true
dashboardView.showsHorizontalScrollIndicator = false
dashboardView.showsVerticalScrollIndicator = false
dashboardView.alwaysBounceHorizontal = false
dashboardView.alwaysBounceVertical = false
return dashboardView
}()
I then set the different views like so:
for index in 0..<3 {
let currentDash = UIView()
currentDash.backgroundColor = UIColor.lightGray
currentDash.layer.cornerRadius = 10
currentDash.layer.masksToBounds = false
currentDash.translatesAutoresizingMaskIntoConstraints = false
currentDash.isUserInteractionEnabled = false
dashboardView.addSubview(currentDash)
currentDash.topAnchor.constraint(equalTo: dashboardView.topAnchor).isActive = true
currentDash.bottomAnchor.constraint(equalTo: dashboardView.bottomAnchor).isActive = true
currentDash.leadingAnchor.constraint(equalTo: dashboardView.leadingAnchor, constant: dashboardWidth * CGFloat(index)).isActive = true
currentDash.trailingAnchor.constraint(equalTo: dashboardView.leadingAnchor, constant: (dashboardWidth * CGFloat(index)) + dashboardWidth).isActive = true
currentDash.widthAnchor.constraint(equalToConstant: dashboardWidth).isActive = true
currentDash.heightAnchor.constraint(equalToConstant: dashboardHeight).isActive = true
}
When I remove the constraints (AutoLayout) and set it with frames it works perfectly, like this:
dashFrame.origin.x = dashboardWidth * CGFloat(index)
dashFrame.size = CGSize(width: dashboardWidth, height: dashboardHeight)
let currentDash = UIView(frame: dashFrame)
But then, because of setting the frame, the view doesn't scroll up as I would, which is why I want to use AutoLayout.
Any ideas or suggestions as to what I am doing wrong? My ScrollViewDidScroll() method simply gets not called, because a simple print doesn't return anything in my console.
I have tried setting the isUserInteractionEnabled property to false on the currentDash UIView, which was no success. I have also tried removing all constraints separately, some together, etc. - but also this doesn't work - I need a topAnchor, bottomAnchor, heightAnchor and widthAnchor at least.
Oh, and, of course, I set the contentSize property:
dashboardView.contentSize = CGSize(width: dashboardWidth * CGFloat(dashboardPager.numberOfPages), height: dashboardHeight)
Any suggestions or hints towards the right answer would be deeply appreciated. Thanks!
EDIT: I also set constraints for the UIScrollView itself:
dashboardView.topAnchor.constraint(equalTo: dashboardBG.topAnchor).isActive = true
dashboardView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: normalSpacing).isActive = true
dashboardView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -normalSpacing).isActive = true
dashboardView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
dashboardView.heightAnchor.constraint(equalToConstant: dashboardHeight).isActive = true
dashboardView.delegate = self
The issue is here
currentDash.leadingAnchor.constraint(equalTo: dashboardView.leadingAnchor, constant: dashboardWidth * CGFloat(index)).isActive = true
currentDash.trailingAnchor.constraint(equalTo: dashboardView.leadingAnchor, constant: (dashboardWidth * CGFloat(index)) + dashboardWidth).isActive = true
1- Regarding leading of the views , the leading of the first view should be pinned to the scrollView , the leading of the second view pinned to the trailing of the previous and so on , so
if index == 0{
// make leading with scrolview leading
else
{
// make leading with prevView trailing
}
So make a var initiated with scrollView and change end of for loop
2- Regarding the trailing , only the last view trailing be pinned to the trailing of the scrollview
if index == 2 { // last
// make trailing with scrollView
}
I eventually figured it out thanks to Sh_Khan’s answer. I held on to the original code, but removed the trailingAnchor for all indexes and added it only to the last index.
for index in 0..<3 {
let currentDash = UIView()
currentDash.backgroundColor = UIColor.lightGray
currentDash.layer.cornerRadius = 10
currentDash.layer.masksToBounds = false
currentDash.translatesAutoresizingMaskIntoConstraints = false
currentDash.isUserInteractionEnabled = false
dashboardView.addSubview(currentDash)
currentDash.topAnchor.constraint(equalTo: dashboardView.topAnchor).isActive = true
currentDash.bottomAnchor.constraint(equalTo: dashboardView.bottomAnchor).isActive = true
currentDash.leadingAnchor.constraint(equalTo: dashboardView.leadingAnchor, constant: dashboardWidth * CGFloat(index)).isActive = true
currentDash.widthAnchor.constraint(equalToConstant: dashboardWidth).isActive = true
currentDash.heightAnchor.constraint(equalToConstant: dashboardHeight).isActive = true
if index == 2 {
currentDash.trailingAnchor.constraint(equalTo: dashboardView.trailingAnchor).isActive = true
}
}
Thanks for the help!

How to make auto-layout constraint dependent on multiple other anchors?

How can auto-layout be used to make a view's height equal to the sum of two other view's heights?
For example:
viewA.heightAnchor.constraint(equalTo: ...),
viewB.heightAnchor.constraint(equalTo: ...),
viewC.heightAnchor.constraint(equalTo: viewA.heightAnchor + viewB.heightAnchor)
Is there a solution that does not involve setting a constant value and recalculating it on every view bounds change?
You can, but only with the help of some helper views, I think. See this example playground I just now cooked up that achieves this:
import Foundation
import UIKit
import PlaygroundSupport
let vc = UIViewController()
vc.view = UIView()
vc.view.backgroundColor = .white
let viewA = UIView()
let viewB = UIView()
let viewC = UIView()
viewA.backgroundColor = .red
viewB.backgroundColor = .green
viewC.backgroundColor = .blue
viewA.translatesAutoresizingMaskIntoConstraints = false
viewB.translatesAutoresizingMaskIntoConstraints = false
viewC.translatesAutoresizingMaskIntoConstraints = false
vc.view.addSubview(viewA)
vc.view.addSubview(viewB)
vc.view.addSubview(viewC)
let helperViewA = UIView()
let helperViewB = UIView()
helperViewA.translatesAutoresizingMaskIntoConstraints = false
helperViewB.translatesAutoresizingMaskIntoConstraints = false
helperViewA.isHidden = true
helperViewB.isHidden = true
vc.view.addSubview(helperViewA)
vc.view.addSubview(helperViewB)
viewA.bottomAnchor.constraint(equalTo: vc.view.bottomAnchor).isActive = true
viewA.leadingAnchor.constraint(equalTo: vc.view.leadingAnchor).isActive = true
viewA.trailingAnchor.constraint(equalTo: viewB.leadingAnchor).isActive = true
viewA.heightAnchor.constraint(equalToConstant: 20.0).isActive = true
viewB.bottomAnchor.constraint(equalTo: vc.view.bottomAnchor).isActive = true
viewB.widthAnchor.constraint(equalTo: viewA.widthAnchor, multiplier: 1.0).isActive = true
viewB.trailingAnchor.constraint(equalTo: vc.view.trailingAnchor).isActive = true
viewB.heightAnchor.constraint(equalToConstant: 40.0).isActive = true
viewA.heightAnchor.constraint(equalTo: helperViewA.heightAnchor, multiplier: 1.0).isActive = true
viewB.heightAnchor.constraint(equalTo: helperViewB.heightAnchor, multiplier: 1.0).isActive = true
helperViewA.bottomAnchor.constraint(equalTo: helperViewB.topAnchor).isActive = true
viewC.widthAnchor.constraint(equalToConstant: 100).isActive = true
viewC.topAnchor.constraint(equalTo: helperViewA.topAnchor).isActive = true
viewC.bottomAnchor.constraint(equalTo: helperViewB.bottomAnchor).isActive = true
helperViewA.leadingAnchor.constraint(equalTo: viewC.leadingAnchor).isActive = true
helperViewA.trailingAnchor.constraint(equalTo: viewC.trailingAnchor).isActive = true
helperViewB.leadingAnchor.constraint(equalTo: viewC.leadingAnchor).isActive = true
helperViewB.trailingAnchor.constraint(equalTo: viewC.trailingAnchor).isActive = true
viewC.centerXAnchor.constraint(equalTo: vc.view.centerXAnchor).isActive = true
viewC.centerYAnchor.constraint(equalTo: vc.view.centerYAnchor).isActive = true
vc.view.frame.size = CGSize(width: 375, height: 667)
PlaygroundPage.current.liveView = vc.view
The idea is that you have two helper views stacked vertically on each other, connected by the top one's bottomAnchor and the bottom one's topAnchor. You then set each of these helper views' heights to be equal to your viewA and viewB heights. Then, your viewC can be attached to the top view's topAnchor and the bottom view's bottomAnchor, which gives the result of viewC being the height of viewA's height plus viewB's height.

Set X and Y values of UILabel using NSLayout

I am adding a UI Label and I am trying out programmatic UI code. I set up my label using this code:
let titleLabel: UILabel = {
let lb = UILabel()
lb.translatesAutoresizingMaskIntoConstraints = false
lb.textAlignment = .center
lb.numberOfLines = 1
lb.textColor = UIColor.black
lb.font=UIFont.systemFont(ofSize: 22)
lb.backgroundColor = UIColor.white
return lb
and then added constraints using this code:
func setupTitleLabel() {
titleLabel.translatesAutoresizingMaskIntoConstraints = false
titleLabel.heightAnchor.constraint(equalToConstant: 100).isActive = true
titleLabel.widthAnchor.constraint(equalToConstant: 200).isActive = true
titleLabel.centerXAnchor.constraint(equalTo: titleLabel.superview!.centerXAnchor).isActive = true
titleLabel.centerYAnchor.constraint(equalTo: titleLabel.superview!.centerYAnchor).isActive = true
}
I call this function later on. However, I am not sure how to change to X and Y values. Currently, it is set to the middle of the page for both X and Y, but how would I move it up to the top.
As I said in my comment, you are using titleLabel.superview!.centerYAnchor wrongly if you need pin your label to top of your label superview you need use titleLabel.superview!.topAnchor instead
replace this
titleLabel.centerYAnchor.constraint(equalTo: titleLabel.superview!.centerYAnchor).isActive = true
by this
titleLabel.centerYAnchor.constraint(equalTo: titleLabel.superview!.topAnchor).isActive = true
What I understand the case is that the label is in the centerX and centerY, and you want it to be at the top of the superView later.If so,I think you can use var centerYAnchor: NSLayoutConstraint?
centerYAnchor = titleLabel.centerYAnchor.constraint(equalTo: titleLabel.superview!.centerYAnchor)
centerYAnchor.isActive = true
And when you want to change the label at the top later,you can set centerYAnchor.isActive = false and set the label's topAnchor.
titleLabel.topAnchor.constraint(equalTo: titleLabel.superview!.topAnchor).isActive = true

UIStackview with differently aligned subviews

I'd like to align the Blue and Purple views to the center of the screen, and I'd like to align the green view to the left of the screen:
Here is my code:
view.backgroundColor = UIColor.orangeColor()
//Stackview:
let stackView = UIStackView()
stackView.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(stackView)
stackView.topAnchor.constraintEqualToAnchor(self.view.topAnchor).active = true
stackView.leftAnchor.constraintEqualToAnchor(self.view.leftAnchor).active = true
stackView.rightAnchor.constraintEqualToAnchor(self.view.rightAnchor).active = true
stackView.axis = UILayoutConstraintAxis.Vertical
stackView.alignment = .Center
//Blue view:
let blueBox = UIView()
stackView.addArrangedSubview(blueBox)
blueBox.backgroundColor = UIColor.blueColor()
blueBox.heightAnchor.constraintEqualToConstant(140).active = true
blueBox.widthAnchor.constraintEqualToConstant(140).active = true
//Inner stackview that contains the green view:
let greenBoxContainer = UIStackView()
let greenBox = UIView()
stackView.addArrangedSubview(greenBoxContainer)
greenBoxContainer.addArrangedSubview(greenBox)
greenBoxContainer.alignment = .Leading
//Green view:
greenBox.backgroundColor = UIColor.greenColor()
greenBox.widthAnchor.constraintEqualToConstant(120).active = true
greenBox.heightAnchor.constraintEqualToConstant(120).active = true
//Purple view:
let purpleView = UIView()
stackView.addArrangedSubview(purpleView)
purpleView.backgroundColor = UIColor.purpleColor()
purpleView.heightAnchor.constraintEqualToConstant(50.0).active = true
purpleView.widthAnchor.constraintEqualToConstant(50.0).active = true
To repeat, how can I align the left edge of the green view with the left edge of the screen?
I tried this:
greenBoxContainer.widthAnchor.constraintEqualToAnchor(stackView.widthAnchor).active = true
but it only stretches the green view through the length of the screen.
One way is to add a spacer view:
let greenBox = UIView()
stackView.addArrangedSubview(greenBoxContainer)
greenBoxContainer.addArrangedSubview(greenBox)
let spacer = UIView()
greenBoxContainer.addArrangedSubview(spacer)
spacer.rightAnchor.constraintEqualToAnchor(greenBoxContainer.rightAnchor).active = true
greenBoxContainer.widthAnchor.constraintEqualToAnchor(stackView.widthAnchor).active = true
greenBox.backgroundColor = UIColor.greenColor()
greenBox.widthAnchor.constraintEqualToConstant(120).active = true
greenBox.heightAnchor.constraintEqualToConstant(120).active = true
Note that I'm no longer setting greenBoxContainer's alignment, so it defaults to .Fill. Thus the right edge of spacer is flushed to the inner stackview's right edge, and it takes up all the width, leaving greenBox just enough room to satisfy its width constraint.
But this is a workaround, I'd like to be able to specify alignments without having to create spacer views..

Resources