Programmatically made constraints are not working - ios

As I want to move away from xib and make my layout programmatically, I found that using the same exact constraints doesn't work as I would expect.
I want to make this UITableViewCell
It's a quite simple cell with a small icon to its right as well as an Activity Indicator so I can toggle which one I want to see. They are inside a View and to their left is a label
Those are my constraints in the outline view
And it works perfectly. However when I'm removing the XIB and doing all of the code myself, nothing works anymore
So here's my code:
class StandardRow: UITableViewCell {
private var initialWidth: CGFloat = 20
public var fetching: Bool = false {
didSet {
if (fetching) {
activityIndicator?.startAnimating()
} else {
activityIndicator?.stopAnimating()
}
changeImageWidth()
}
}
public var rightImage: UIImage? = nil {
didSet {
rightImageView?.image = rightImage
changeImageWidth()
}
}
private func changeImageWidth() {
if (activityIndicator?.isAnimating) ?? false || rightImage != nil {
imageWidth?.constant = initialWidth
} else {
imageWidth?.constant = 0
}
}
override func prepareForReuse() {
valueLabel?.text = ""
imageView?.image = nil
rightImage = nil
fetching = false
textLabel?.text = ""
accessoryType = .none
}
//Views
private var imageContainer = UIView()
private var rightImageView = UIImageView()
private var activityIndicator: UIActivityIndicatorView? = UIActivityIndicatorView()
public var valueLabel: UILabel? = UILabel()
private var imageWidth: NSLayoutConstraint? = nil
override init(style: UITableViewCell.CellStyle = .default, reuseIdentifier: String? = nil) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
buildView()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
buildView()
}
func buildView() {
contentView.addSubview(valueLabel!)
imageContainer.addSubview(rightImageView)
imageContainer.addSubview(activityIndicator!)
contentView.addSubview(imageContainer)
imageContainer.backgroundColor = .red
}
override func layoutSubviews() {
super.layoutSubviews()
//IMAGE CONTAINER CONSTRAINTS
imageWidth = NSLayoutConstraint(item: imageContainer, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: initialWidth)
imageWidth?.priority = UILayoutPriority(rawValue: 999)
imageWidth?.isActive = true
let bottomImageContainerConstraint = NSLayoutConstraint(item: imageContainer, attribute: .bottom, relatedBy: .equal, toItem: contentView, attribute: .bottom, multiplier: 1, constant: 0)
bottomImageContainerConstraint.isActive = true
bottomImageContainerConstraint.priority = UILayoutPriority(rawValue: 999)
let topImageContainerConstraint = NSLayoutConstraint(item: imageContainer, attribute: .top, relatedBy: .equal, toItem: contentView, attribute: .top, multiplier: 1, constant: 0)
topImageContainerConstraint.isActive = true
topImageContainerConstraint.priority = UILayoutPriority(rawValue: 999)
let trailingImageContainerConstraint = NSLayoutConstraint(item: imageContainer, attribute: .trailing, relatedBy: .equal, toItem: contentView, attribute: .trailing, multiplier: 1, constant: 5)
trailingImageContainerConstraint.priority = UILayoutPriority(rawValue: 999)
trailingImageContainerConstraint.isActive = true
let centerYImageContainerConstraint = NSLayoutConstraint(item: imageContainer, attribute: .centerY, relatedBy: .equal, toItem: contentView, attribute: .centerY, multiplier: 1, constant: 0)
centerYImageContainerConstraint.isActive = true
centerYImageContainerConstraint.priority = UILayoutPriority(rawValue: 999)
//VALUE LABEL CONSTRAINTS
let trailingValueLabelConstraint = NSLayoutConstraint(item: valueLabel!, attribute: .trailing, relatedBy: .equal, toItem: imageContainer, attribute: .leading, multiplier: 1, constant: 5)
trailingValueLabelConstraint.isActive = true
trailingValueLabelConstraint.priority = UILayoutPriority(rawValue: 999)
let centerYValueLabelConstraint = NSLayoutConstraint(item: valueLabel!, attribute: .centerY, relatedBy: .equal, toItem: contentView, attribute: .centerY, multiplier: 1, constant: 0)
centerYValueLabelConstraint.isActive = true
centerYValueLabelConstraint.priority = UILayoutPriority(rawValue: 999)
//ACTIVITY INDICATOR CONSTRAINGS
NSLayoutConstraint(item: activityIndicator!, attribute: .trailing, relatedBy: .equal, toItem: imageContainer, attribute: .trailing, multiplier: 1, constant: 0).isActive = true
NSLayoutConstraint(item: activityIndicator!, attribute: .leading, relatedBy: .equal, toItem: imageContainer, attribute: .leading, multiplier: 1, constant: 11).isActive = false
NSLayoutConstraint(item: activityIndicator!, attribute: .bottom, relatedBy: .equal, toItem: imageContainer, attribute: .bottom, multiplier: 1, constant: 11).isActive = false
NSLayoutConstraint(item: activityIndicator!, attribute: .top, relatedBy: .equal, toItem: imageContainer, attribute: .top, multiplier: 1, constant: 0).isActive = true
NSLayoutConstraint(item: activityIndicator!, attribute: .centerY, relatedBy: .equal, toItem: imageContainer, attribute: .centerY, multiplier: 1, constant: 0).isActive = true
//RIGHT IMAGE VIEW CONSTRAINTS
NSLayoutConstraint(item: rightImageView, attribute: .trailing, relatedBy: .equal, toItem: activityIndicator!, attribute: .trailing, multiplier: 1, constant: 0).isActive = true
NSLayoutConstraint(item: rightImageView, attribute: .leading, relatedBy: .equal, toItem: rightImageView, attribute: .leading, multiplier: 1, constant: 0).isActive = true
NSLayoutConstraint(item: rightImageView, attribute: .bottom, relatedBy: .equal, toItem: activityIndicator!, attribute: .bottom, multiplier: 1, constant: 0).isActive = true
NSLayoutConstraint(item: rightImageView, attribute: .top, relatedBy: .equal, toItem: activityIndicator!, attribute: .top, multiplier: 1, constant: 0).isActive = true
NSLayoutConstraint(item: rightImageView, attribute: .centerY, relatedBy: .equal, toItem: activityIndicator!, attribute: .centerY, multiplier: 1, constant: 0).isActive = true
//changeImageWidth()
}}
So I have a few ideas to where it can come from, firstly being "translatesAutoresizingMaskIntoConstraints" set to true by default, but when I'm setting it to false in the superview then my cell doesn't show anymore and in the contentView, Xcode tells me I shouldn't do that because of an undefined behaviour
I'm also using Reveal to debug my UI and then I found those peculiar values:
Which is not what I want, Reveal is reporting that those constraints are translating the autoresizing mask of the view to autolayout so it would confirm the previous theory. I did set the priority to 999 to some of the constraints because otherwise they would be broken.
I'm actually at a dead end and I think I'm missing something but I can't pinpoint what as I don't have enough experience with non-interface builder constraints

Try Anchors, it's much easier.
Example
var redView = UIView()
redView.backgroundColor = .red
anyView.addsubView(redView)
redView.translatesAutoresizingMaskIntoConstraints = false
redView.centerXAnchor.constraint(equalTo: self.parentView.centerXAnchor).isActive = true
redView.centerYAnchor.constraint(equalTo: self.parentView.centerYAnchor).isActive = true
redView.heightAnchor.constraint(equalToConstant: 100).isActive = true
redView.widthAnchor.constraint(equalToConstant: 100).isActive = true

You can add the same method to your UIView extension
func constrainToEdges(_ subview: UIView, top: CGFloat = 0, bottom: CGFloat = 0, leading: CGFloat = 0, trailing: CGFloat = 0) {
subview.translatesAutoresizingMaskIntoConstraints = false
let topContraint = NSLayoutConstraint(
item: subview,
attribute: .top,
relatedBy: .equal,
toItem: self,
attribute: .top,
multiplier: 1.0,
constant: top)
let bottomConstraint = NSLayoutConstraint(
item: subview,
attribute: .bottom,
relatedBy: .equal,
toItem: self,
attribute: .bottom,
multiplier: 1.0,
constant: bottom)
let leadingContraint = NSLayoutConstraint(
item: subview,
attribute: .leading,
relatedBy: .equal,
toItem: self,
attribute: .leading,
multiplier: 1.0,
constant: leading)
let trailingContraint = NSLayoutConstraint(
item: subview,
attribute: .trailing,
relatedBy: .equal,
toItem: self,
attribute: .trailing,
multiplier: 1.0,
constant: trailing)
addConstraints([
topContraint,
bottomConstraint,
leadingContraint,
trailingContraint])
}

I recommend using this framework for building constraint based layouts programmatically, it makes the process straightforward and faster. Take the setup for the contentView of this cell for example:
contentView.addSubview(descriptionLabel)
contentView.addSubview(amountLabel)
contentView.addSubview(dateLabel)
contentView.addSubview(bottomRightLabel)
constrain(descriptionLabel, amountLabel, dateLabel, bottomRightLabel) { desc, amount, date, bottomRight in
desc.top == desc.superview!.top + 16
desc.left == desc.superview!.left + 16
desc.right <= amount.left + 12
desc.bottom == date.top - 12
amount.centerY == desc.centerY
amount.right == amount.superview!.right - 12
date.left == date.superview!.left + 16
date.right <= bottomRight.left - 12
date.bottom == date.superview!.bottom - 16
bottomRight.centerY == date.centerY
bottomRight.right == bottomRight.superview!.right - 12
}

Related

Applying a shadow to a subview loaded from nib

I am loading a custom view class from the nib at viewWillAppear. After loading the nib, I add it as a subview, then apply some constraints, then update the constraints. At that point I attempt to add a shadow to this view, but no shadow is displaying....
Just for some context, the ViewController has scrollview that contains a contentView. All of the view's content goes inside this contentView.
Here is my code:
ViewController
class MyViewController: UIViewController {
#IBOutlet weak var contentView: UIView!
var matches: [Match] = []
var matchViews: [MatchDetailsView] = []
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
setMatches()
}
func setMatches() {
// Match One
let matchOneDetailsView = MatchDetailsView.instanceFromNib(match: matches.count > 0 ? matches[0] : nil, delegate: self, tag: 1)
contentView.addSubview(matchOneDetailsView)
matchOneDetailsView.translatesAutoresizingMaskIntoConstraints = false
let matchOneLeadingConstraint = NSLayoutConstraint(item: matchOneDetailsView, attribute: .leading, relatedBy: .equal, toItem: contentView, attribute: .leading, multiplier: 1, constant: 16)
let matchOneTrailingConstraint = NSLayoutConstraint(item: matchOneDetailsView, attribute: .trailing, relatedBy: .equal, toItem: contentView, attribute: .trailing, multiplier: 1, constant: -16)
let matchOneTopConstraint = NSLayoutConstraint(item: matchOneDetailsView, attribute: .top, relatedBy: .equal, toItem: contentView, attribute: .top, multiplier: 1, constant: 32)
// Match Two
let matchTwoDetailsView = MatchDetailsView.instanceFromNib(match: matches.count > 1 ? matches[1] : nil, delegate: self, tag: 2)
contentView.addSubview(matchTwoDetailsView)
matchTwoDetailsView.translatesAutoresizingMaskIntoConstraints = false
let matchTwoLeadingConstraint = NSLayoutConstraint(item: matchTwoDetailsView, attribute: .leading, relatedBy: .equal, toItem: contentView, attribute: .leading, multiplier: 1, constant: 16)
let matchTwoTrailingConstraint = NSLayoutConstraint(item: matchTwoDetailsView, attribute: .trailing, relatedBy: .equal, toItem: contentView, attribute: .trailing, multiplier: 1, constant: -16)
let matchTwoTopConstraint = NSLayoutConstraint(item: matchTwoDetailsView, attribute: .top, relatedBy: .equal, toItem: matchOneDetailsView, attribute: .bottom, multiplier: 1, constant: 32)
// Match Three
let matchThreeDetailsView = MatchDetailsView.instanceFromNib(match: matches.count > 2 ? matches[2] : nil, delegate: self, tag: 3)
contentView.addSubview(matchThreeDetailsView)
matchThreeDetailsView.translatesAutoresizingMaskIntoConstraints = false
let matchThreeLeadingConstraint = NSLayoutConstraint(item: matchThreeDetailsView, attribute: .leading, relatedBy: .equal, toItem: contentView, attribute: .leading, multiplier: 1, constant: 16)
let matchThreeTrailingConstraint = NSLayoutConstraint(item: matchThreeDetailsView, attribute: .trailing, relatedBy: .equal, toItem: contentView, attribute: .trailing, multiplier: 1, constant: -16)
let matchThreeTopConstraint = NSLayoutConstraint(item: matchThreeDetailsView, attribute: .top, relatedBy: .equal, toItem: matchTwoDetailsView, attribute: .bottom, multiplier: 1, constant: 32)
let matchThreeBottomConstraint = NSLayoutConstraint(item: matchThreeDetailsView, attribute: .bottom, relatedBy: .equal, toItem: contentView, attribute: .bottom, multiplier: 1, constant: -32)
NSLayoutConstraint.activate([matchOneLeadingConstraint, matchOneTrailingConstraint, matchOneTopConstraint, matchTwoLeadingConstraint, matchTwoTrailingConstraint, matchTwoTopConstraint, matchThreeLeadingConstraint, matchThreeTrailingConstraint, matchThreeTopConstraint, matchThreeBottomConstraint])
updateViewConstraints()
matchViews.append(matchOneDetailsView)
matchViews.append(matchTwoDetailsView)
matchViews.append(matchThreeDetailsView)
styleMatchViews()
}
func styleMatchViews() {
for view in matchViews {
view.addShadow(opacity: 0.25, yOffset: 0, xOffset: 0, radius: 5.0)
view.roundCorners(withRadius: 5.0)
}
}
}
MatchDetailsView
class MatchDetailsView: UIView {
class func instanceFromNib(match: Match?, delegate: MatchDetailsViewDelegate, tag: Int) -> MatchDetailsView {
let view = UINib(nibName: "MatchDetailsView", bundle: nil).instantiate(withOwner: MatchDetailsView(), options: nil)[0] as! MatchDetailsView
view.translatesAutoresizingMaskIntoConstraints = false
view.updateMatchContent()
return view
}
}
UIViewExtension
extension UIView {
/**
Add Shadow
Puts a Drop Shadow in the UIView
*/
func addShadow(opacity: Float, yOffset: Int, xOffset: Int, radius: CGFloat) {
self.layer.masksToBounds = false
self.layer.shadowColor = UIColor.black.cgColor
self.layer.shadowOpacity = opacity
self.layer.shadowOffset = CGSize(width: xOffset, height: yOffset)
self.layer.shadowRadius = radius
self.layer.shadowPath = UIBezierPath(rect: self.bounds).cgPath
}
}
If MatchDetailsView's nib file has 'clipsToBounds' set to false, why isn't my shadow showing up when the page is loaded? Am I missing a layout function even though they seem to be laid out correctly?
You are setting the .shadowPath to the bounds of the view... but the bounds at that point may not be (probably will not be) the bounds after auto-layout lays out the views.
For a "shadowed view" you are much better off setting the shadow inlayoutSubviews() inside the view itself.
You should also add and setup your subviews in viewDidLoad() viewWillAppear() may be called more than once (depending on your navigation), and you'll end up adding multiple copies of your subviews.
So...
Change your MatchDetailsView class like this:
class MatchDetailsView: UIView {
var opacity: CGFloat = 0.0
var xOffset: CGFloat = 0.0
var yOffset: CGFloat = 0.0
// for the shadowRadius
var sRadius: CGFloat = 0.0
// for the cornerRadius
var cRadius: CGFloat = 0.0
override func layoutSubviews() {
self.layer.masksToBounds = false
self.layer.shadowColor = UIColor.black.cgColor
self.layer.shadowOpacity = Float(opacity)
self.layer.shadowOffset = CGSize(width: xOffset, height: yOffset)
self.layer.shadowRadius = sRadius
self.layer.shadowPath = UIBezierPath(rect: self.bounds).cgPath
self.layer.cornerRadius = cRadius
}
class func instanceFromNib(match: Match?, delegate: MatchDetailsViewDelegate, tag: Int) -> MatchDetailsView {
let view = UINib(nibName: "MatchDetailsView", bundle: nil).instantiate(withOwner: MatchDetailsView(), options: nil)[0] as! MatchDetailsView
view.translatesAutoresizingMaskIntoConstraints = false
view.updateMatchContent()
return view
}
}
Then, in your view controller:
override func viewDidLoad() {
super.viewDidLoad()
setMatches()
}
func setMatches() {
// Match One
let matchOneDetailsView = MatchDetailsView.instanceFromNib(match: matches.count > 0 ? matches[0] : nil, delegate: self, tag: 1)
contentView.addSubview(matchOneDetailsView)
matchOneDetailsView.translatesAutoresizingMaskIntoConstraints = false
let matchOneLeadingConstraint = NSLayoutConstraint(item: matchOneDetailsView, attribute: .leading, relatedBy: .equal, toItem: contentView, attribute: .leading, multiplier: 1, constant: 16)
let matchOneTrailingConstraint = NSLayoutConstraint(item: matchOneDetailsView, attribute: .trailing, relatedBy: .equal, toItem: contentView, attribute: .trailing, multiplier: 1, constant: -16)
let matchOneTopConstraint = NSLayoutConstraint(item: matchOneDetailsView, attribute: .top, relatedBy: .equal, toItem: contentView, attribute: .top, multiplier: 1, constant: 32)
// Match Two
let matchTwoDetailsView = MatchDetailsView.instanceFromNib(match: matches.count > 1 ? matches[1] : nil, delegate: self, tag: 2)
contentView.addSubview(matchTwoDetailsView)
matchTwoDetailsView.translatesAutoresizingMaskIntoConstraints = false
let matchTwoLeadingConstraint = NSLayoutConstraint(item: matchTwoDetailsView, attribute: .leading, relatedBy: .equal, toItem: contentView, attribute: .leading, multiplier: 1, constant: 16)
let matchTwoTrailingConstraint = NSLayoutConstraint(item: matchTwoDetailsView, attribute: .trailing, relatedBy: .equal, toItem: contentView, attribute: .trailing, multiplier: 1, constant: -16)
let matchTwoTopConstraint = NSLayoutConstraint(item: matchTwoDetailsView, attribute: .top, relatedBy: .equal, toItem: matchOneDetailsView, attribute: .bottom, multiplier: 1, constant: 32)
// Match Three
let matchThreeDetailsView = MatchDetailsView.instanceFromNib(match: matches.count > 2 ? matches[2] : nil, delegate: self, tag: 3)
contentView.addSubview(matchThreeDetailsView)
matchThreeDetailsView.translatesAutoresizingMaskIntoConstraints = false
let matchThreeLeadingConstraint = NSLayoutConstraint(item: matchThreeDetailsView, attribute: .leading, relatedBy: .equal, toItem: contentView, attribute: .leading, multiplier: 1, constant: 16)
let matchThreeTrailingConstraint = NSLayoutConstraint(item: matchThreeDetailsView, attribute: .trailing, relatedBy: .equal, toItem: contentView, attribute: .trailing, multiplier: 1, constant: -16)
let matchThreeTopConstraint = NSLayoutConstraint(item: matchThreeDetailsView, attribute: .top, relatedBy: .equal, toItem: matchTwoDetailsView, attribute: .bottom, multiplier: 1, constant: 32)
let matchThreeBottomConstraint = NSLayoutConstraint(item: matchThreeDetailsView, attribute: .bottom, relatedBy: .equal, toItem: contentView, attribute: .bottom, multiplier: 1, constant: -32)
NSLayoutConstraint.activate([matchOneLeadingConstraint, matchOneTrailingConstraint, matchOneTopConstraint, matchTwoLeadingConstraint, matchTwoTrailingConstraint, matchTwoTopConstraint, matchThreeLeadingConstraint, matchThreeTrailingConstraint, matchThreeTopConstraint, matchThreeBottomConstraint])
matchViews.append(matchOneDetailsView)
matchViews.append(matchTwoDetailsView)
matchViews.append(matchThreeDetailsView)
styleMatchViews()
}
func styleMatchViews() {
for view in matchViews {
view.opacity = 0.25
view.yOffset = 0.0
view.xOffset = 0.0
view.sRadius = 5.0
view.cRadius = 5.0
}
}

How to update constraints dynamically?

I have three UIButton, I have created programmatically constraints, at some condition i am removing one of UIButton as button.removeFromSuperview() & rest of two button will set constraints as per priorities.
Issue is when i am removing one UIButton(buttonWink) then as of viewLifeCycle viewWillLayoutSubviews will called & App crashes in below line
buttonWink.translatesAutoresizingMaskIntoConstraints = false
Ofcourse because buttonWink is removed from superview however we can check before setting constraints as
if buttonWink != nil {
buttonWink.translatesAutoresizingMaskIntoConstraints = false
}
But by checking nil for every button will make code lengthy, is there any way of doing the same? i will really appreciate friends.
Output -
Here i am attaching my tried full code.
import UIKit
class MasterViewController: UIViewController {
#IBOutlet weak var buttonMessage : UIButton!
#IBOutlet weak var buttonLike : UIButton!
#IBOutlet weak var buttonWink : UIButton!
#IBAction func tapsOnLike(_ sender: UIButton) {
sender.removeFromSuperview()
}
#IBAction func tapsOnWink(_ sender: UIButton) {
sender.removeFromSuperview()
}
#IBAction func tapsOnNextButton(){
let vc = DetailViewController(nibName: "DetailViewController", bundle: nil)
navigationController?.pushViewController(vc, animated: true)
}
override func viewDidLoad() {
super.viewDidLoad()
setConstraints()
navigationController?.isNavigationBarHidden = true
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(true)
// setConstraints()
}
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
//setConstraints()
}
func setConstraints() {
if buttonMessage != nil {
buttonMessage?.translatesAutoresizingMaskIntoConstraints = false
}
buttonLike?.translatesAutoresizingMaskIntoConstraints = false
buttonWink?.translatesAutoresizingMaskIntoConstraints = false
//Constraints for Message button
let leading = NSLayoutConstraint(item: buttonMessage, attribute: .leading, relatedBy: .equal, toItem: view, attribute: .leading, multiplier: 1.0, constant: 8)
leading.isActive = true
let trailingToSuperView = NSLayoutConstraint(item: buttonMessage, attribute: .trailing, relatedBy: .equal, toItem: view, attribute: .trailing, multiplier: 1, constant: -8)
trailingToSuperView.priority = 998
trailingToSuperView.isActive = true
let bottomToSuperView = NSLayoutConstraint(item: buttonMessage, attribute: .bottom, relatedBy: .equal, toItem: view, attribute: .bottom, multiplier: 1, constant: -8)
bottomToSuperView.isActive = true
let trailingToWink = NSLayoutConstraint(item: buttonMessage, attribute: .trailing, relatedBy: .equal, toItem: buttonWink, attribute: .leading, multiplier: 1, constant: -8)
trailingToWink.priority = 999
trailingToWink.isActive = true
let leadingToLike = NSLayoutConstraint(item: buttonMessage, attribute: .trailing, relatedBy: .equal, toItem: buttonLike, attribute: .leading, multiplier: 1.0, constant: -8)
leadingToLike.isActive = true
let alignBottomToWink = NSLayoutConstraint(item: buttonMessage, attribute: .bottom, relatedBy: .equal, toItem: buttonWink, attribute: .bottom, multiplier: 1, constant: 0)
alignBottomToWink.isActive = true
let alignBottomToLike = NSLayoutConstraint(item: buttonMessage, attribute: .bottom, relatedBy: .equal, toItem: buttonLike, attribute: .bottom, multiplier: 1, constant: 0)
alignBottomToLike.isActive = true
let equalWidthToWink = NSLayoutConstraint(item: buttonMessage, attribute: .width, relatedBy: .equal, toItem: buttonWink, attribute: .width, multiplier: 1, constant: 0)
equalWidthToWink.isActive = true
let equalWidthToLike = NSLayoutConstraint(item: buttonMessage, attribute: .width, relatedBy: .equal, toItem: buttonLike, attribute: .width, multiplier: 1, constant: 0)
equalWidthToLike.isActive = true
//Constraints for like button
let trailingLikeToSuperView = NSLayoutConstraint(item: buttonLike, attribute: .trailing, relatedBy: .equal, toItem: view, attribute: .trailing, multiplier: 1, constant: -8)
trailingLikeToSuperView.priority = 999
trailingLikeToSuperView.isActive = true
let leadingToWink = NSLayoutConstraint(item: buttonLike, attribute: .trailing, relatedBy: .equal, toItem: buttonWink, attribute: .leading, multiplier: 1.0, constant: -8)
leadingToWink.isActive = true
//Constraints for Wink button
let trailingWinkToSuperView = NSLayoutConstraint(item: buttonWink, attribute: .trailing, relatedBy: .equal, toItem: view, attribute: .trailing, multiplier: 1, constant: -8)
trailingWinkToSuperView.isActive = true
}
}
Just FYI -
simply use a stack view for this.
It is incredibly easy: it's why Apple finally added a stack concept a couple yrs ago.
I just added UIStackView & it's too easy,
Three buttons are added in horizontal stack view with 8 points spacing & added three constraints to stack view as leading, trailing & bottom to superview
#IBOutlet weak var stackView: UIStackView!
#IBAction func tapsOnLikeInStack(_ sender: UIButton) {
sender.isHidden = true
}
#IBAction func tapsOnWinkInStack(_ sender: UIButton) {
sender.isHidden = true
}
func constraintsForStackView(){
stackView?.translatesAutoresizingMaskIntoConstraints = false
stackView.spacing = 8
stackView.axis = .horizontal
stackView.distribution = .fillEqually
stackView.alignment = .fill
let leading = NSLayoutConstraint(item: stackView, attribute: .leading, relatedBy: .equal, toItem: view, attribute: .leading, multiplier: 1.0, constant: 8)
leading.isActive = true
let trailingToSuperView = NSLayoutConstraint(item: stackView, attribute: .trailing, relatedBy: .equal, toItem: view, attribute: .trailing, multiplier: 1, constant: -8)
trailingToSuperView.isActive = true
let bottomToSuperView = NSLayoutConstraint(item: stackView, attribute: .bottom, relatedBy: .equal, toItem: view, attribute: .bottom, multiplier: 1, constant: -120)
bottomToSuperView.isActive = true
}
as far as your viewWillLayoutSubviews code put your buttons into an array to use:
for btn in buttons {
if (btn.superview != nil) {
btn.translatesAutoresizingMaskIntoConstraints = false
}
}
and if you want to change the constraints for the buttons still in the view, give them identifiers when you create the constraints:
for btn in buttons {
if btn.superview != nil {
btn.translatesAutoresizingMaskIntoConstraints = false
for constraint in btn.constraints {
switch constraint.identifier {
case "winkBtnTrailing":
//do stuff like constraint = new layout constraint
//or constraint.firstItem = *new first Item to base trailing on*
break
default:
//don't do stuff?
break
}
}
}
}
Just as a FYI using button.removeFromSuperview() doesn't set that button to nil, it just removes it from the view you called .addSubview(button) on

Message bubble constraints with swift

I know this is a hard problem to debug. I will try to explain, you can ask question from comments section.
I want to use message bubbles in my app. I wrote some code for constraints, but it is not working well.
How can i put the left edge of blue bubble to red line?
Here is my code:
When tableview cell loads this method is running:
private func setup() {
bubbleImageView = UIImageView(image: bubbleImage.incoming, highlightedImage: bubbleImage.incomingHighlighed)
bubbleImageView.userInteractionEnabled = true
messageLabel = UILabel(frame: CGRectZero)
messageLabel.font = UIFont.systemFontOfSize(15)
messageLabel.numberOfLines = 0
messageLabel.userInteractionEnabled = true
selectionStyle = .None
contentView.addSubview(bubbleImageView)
bubbleImageView.addSubview(messageLabel)
messageLabel.translatesAutoresizingMaskIntoConstraints = false
bubbleImageView.translatesAutoresizingMaskIntoConstraints = false
contentView.addConstraint(NSLayoutConstraint(item: bubbleImageView, attribute: .Left, relatedBy: .Equal, toItem: contentView, attribute: .Left, multiplier: 1, constant: 10))
contentView.addConstraint(NSLayoutConstraint(item: bubbleImageView, attribute: .Top, relatedBy: .Equal, toItem: contentView, attribute: .Top, multiplier: 1, constant: 4.5))
bubbleImageView.addConstraint(NSLayoutConstraint(item: bubbleImageView, attribute: .Width, relatedBy: .Equal, toItem: messageLabel, attribute: .Width, multiplier: 1, constant: 30))
contentView.addConstraint(NSLayoutConstraint(item: bubbleImageView, attribute: .Bottom, relatedBy: .Equal, toItem: contentView, attribute: .Bottom, multiplier: 1, constant: -4.5))
bubbleImageView.addConstraint(NSLayoutConstraint(item: messageLabel, attribute: .CenterX, relatedBy: .Equal, toItem: bubbleImageView, attribute: .CenterX, multiplier: 1, constant: -2))
bubbleImageView.addConstraint(NSLayoutConstraint(item: messageLabel, attribute: .CenterY, relatedBy: .Equal, toItem: bubbleImageView, attribute: .CenterY, multiplier: 1, constant: -0.5))
messageLabel.preferredMaxLayoutWidth = 218
bubbleImageView.addConstraint(NSLayoutConstraint(item: messageLabel, attribute: .Height, relatedBy: .Equal, toItem: bubbleImageView, attribute: .Height, multiplier: 1, constant: -15))
}
As you can see i am creating bubble background and label for text.
And second method:
func setCell(message:Message) {
messageLabel.text="testtesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttest"
var layoutAttribute: NSLayoutAttribute
var layoutConstant: CGFloat
if message.incoming {
bubbleImageView.image=bgImage.incoming
messageLabel.textColor = UIColor.blackColor()
layoutAttribute = .Left
layoutConstant = 10
}else{
if message.isSent == 1 {
bubbleImageView.image = bgImage.outgoing
}
if message.isSent == 0 {
bubbleImageView.image = bgImage.notYetSent
}
messageLabel.textColor = UIColor.whiteColor()
layoutAttribute = .Right
layoutConstant = -10
}
contentView.addConstraint(NSLayoutConstraint(item: bubbleImageView, attribute: layoutAttribute, relatedBy: .Equal, toItem: contentView, attribute: layoutAttribute, multiplier: 1, constant: layoutConstant))
}
What is the problem with my code? It is working well for incoming messages (gray bubble) and not working well for outgoing messages (blue bubble)
I get it. You have to add a constraint that doesnt allows the bubble to exceed the max width. So you have to add a constraint like that:
let widthConstraint = NSLayoutConstraint(item: youritem, attribute: NSLayoutAttribute.Width, relatedBy: NSLayoutRelation.LessThanOrEqual,
toItem: self.youItem.superview, attribute: NSLayoutAttribute.Width,
multiplier: 1.0, constant: 100)

How to create Auto layout equal width constraint programmatically in ios swift?

How to give programmatically constraints equal width and equal height with multiple views.I check google but not perfect answer for programmatically equal width and height constraints through auto layout.
my code look like below:
var countNoOfViews:Int = 3
#IBOutlet var viewForRow1: UIView!
override func viewDidLoad() {
super.viewDidLoad()
self.specialButtonViewLeft()
}
func specialButtonViewLeft(){
for i in 0..<countNoOfViews{
var customView:UIView!
customView = UIView(frame: CGRect.zero)
customView.translatesAutoresizingMaskIntoConstraints = false
viewForRow1.addSubview(customView)
let widthConstraint = NSLayoutConstraint(item: customView, attribute: .width, relatedBy: .equal,toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: 20.0)
let topConstraint = NSLayoutConstraint(item: customView, attribute: .top, relatedBy: .equal,toItem: self.viewForRow1, attribute: .top, multiplier: 1.0, constant: 0)
let bottomConstraint = NSLayoutConstraint(item: customView, attribute: .bottom, relatedBy: .equal,toItem: self.viewForRow1, attribute: .bottom, multiplier: 1.0, constant: 0)
let leadingConstraint = NSLayoutConstraint(item: customView, attribute: .leading, relatedBy: .equal, toItem: self.viewForRow1, attribute: .leading, multiplier: 1, constant: customLeadingSpaceLeft)
customLeadingSpaceLeft = customLeadingSpaceLeft + customViewWidth
arrayLeftBtnConstraints.append(widthConstraint)
if i == 0{
customView.backgroundColor = UIColor.red
}else if i == 1{
customView.backgroundColor = UIColor.green
}else if i == 2{
leftViewVal = customLeadingSpaceLeft
customView.backgroundColor = UIColor.black
}
customView.alpha = 0.50
viewForRow1.addConstraints([widthConstraint, leadingConstraint,topConstraint,bottomConstraint])
}
}
I want to add equal width constraint programmatically.
You have three possible ways to do that:
1) By using anchors:
view.widthAnchor.constraint(equalTo: otherView.widthAnchor, multiplier: 1.0).isActive = true
2) Visual format:
simple example - "H:|[otherView]-[view(==otherView)]|"
3) "Old school" constraints:
NSLayoutConstraint(item: #view, attribute: .width, relatedBy: .equal, toItem: #otherView, attribute: .width, multiplier: 1.0, constant: 0.0).isActive = true
hope it will help somebody.
Try this:
let widthConstraint = NSLayoutConstraint(item: customView, attribute: .width, relatedBy: .equal, toItem: viewForRow1, attribute: .width, multiplier: 0.25, constant: 0.0)
multiplier: 0.25, denotes that customView's width will be 1/4th of the parent view, viewForRow1.

Dynamically positioning UILabel to bottom of content view with Swift?

I was trying to position my dynamically created UILabels to my contentView's bottom. I'm using HTML Parser called Fuzi to catch HTML tags and creating UILabels based on them;
func stringFromHTML( _ string: String?)
{
do{
let doc = try HTMLDocument(string: string!, encoding: String.Encoding.utf8)
if let root = doc.body {
for element in root.children {
if element.tag == "h2" {
// Create new label
let label = UILabel()
label.text = element.stringValue
label.numberOfLines = 0
label.font = UIFont(name: "Avenir Next", size: 17)
label.textColor = UIColor.black
self.contentView.addSubview(label)
label.translatesAutoresizingMaskIntoConstraints = false
// Label constraints
let labelLeading = NSLayoutConstraint(item: label, attribute: .leading, relatedBy: .equal, toItem: contentView, attribute: .leading, multiplier: 1, constant: 20)
let labelTrailing = NSLayoutConstraint(item: label, attribute: .trailing, relatedBy: .equal, toItem: contentView, attribute: .trailing, multiplier: 1, constant: -20)
let labelTop = NSLayoutConstraint(item: label, attribute: .top, relatedBy: .equal, toItem: contentView, attribute: .top, multiplier: 1, constant: self.contentView.frame.origin.y)
self.contentView.addConstraints([labelLeading, labelTrailing, labelTop])
}
updateContentViewHeight()
}
}
}
catch{
print("html error\n",error)
}
}
func updateContentViewHeight(){
var totalContentHeight:CGFloat = 0.0
for i in self.contentView.subviews {
totalContentHeight += i.frame.height
}
let contentViewHeight:NSLayoutConstraint = NSLayoutConstraint(item: self.contentView, attribute: NSLayoutAttribute.height, relatedBy: NSLayoutRelation.equal, toItem: nil, attribute: NSLayoutAttribute.notAnAttribute, multiplier: 1, constant: totalContentHeight-29)
self.contentView.addConstraint(contentViewHeight)
}
I tried calling my stringFromHTML function in viewDidAppear. But it positions my UILabels to far far away (they don't seem).
I want to position my labels to the bottom of the last label, see the image below;
Anyone can help? Thanks in advance!
Change
let labelTop = NSLayoutConstraint(item: label, attribute: .top, relatedBy: .equal, toItem: contentView, attribute: .top, multiplier: 1, constant: self.contentView.frame.origin.y)
To
if label != nil {
let labelTop = NSLayoutConstraint(item: label, attribute: .top, relatedBy: .equal, toItem: label[index], attribute: .top, multiplier: 1, constant: 2)
} else {
let labelTop = NSLayoutConstraint(item: label, attribute: .top, relatedBy: .equal, toItem: contentView, attribute: .top, multiplier: 1, constant: self.contentView.frame.origin.y)
}
Because you are basically forcing each label to be at the same y position.

Resources