UILabel position not updating even after changing constraints on button click - ios

I am trying to change the constraints of a UILabel to 30 pixels down so I'm changing it like below:
The initial constraints are:
testLabel.topAnchor.constraint(equalTo: fastestInfoText.bottomAnchor,constant: 5).isActive = true
The button functionality:
#objc func infoIcon3Pressed(sender: UIButton){
fastestInfoText.isHidden = !fastestInfoText.isHidden
testLabel.topAnchor.constraint(equalTo: fastestInfoText.bottomAnchor,constant: 30).isActive = true
}
But still the UILabel is not changing its position to up or down.
I was trying to change constraints after button is clicked.

You're actually creating 2 constraints and activating them both.
Instead make constraint as global variable, and update the constant value when user taps on Button.
lazy var testLabelTopConstraint = testLabel.topAnchor.constraint(equalTo: fastestInfoText.bottomAnchor, constant: 5);
// Activate constraint in view didload.
testLabelTopConstraint.isActive = true
#objc func infoIcon3Pressed(sender: UIButton){
fastestInfoText.isHidden = !fastestInfoText.isHidden
testLabelTopConstraint.constant = 30
}

Related

Unable to activate constraint with anchors error with UIimageView

Posting a question for the first time here.
So I have been trying to make an animation of an UIimageView. I did that so far. So the image moves from the middle of the screen to the top. I want to be able to make that animation with constraints. But while trying to add some constraints, I receive this error "Unable to activate constraint with anchors error".
here is the code which I try to add some constraints to banditLogo imageview.
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(banditLogo)
view.translatesAutoresizingMaskIntoConstraints = false // autolayout activation
chooseLabel.alpha = 0
signInButtonOutlet.alpha = 0
self.banditLogo.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 304).isActive = true
self.banditLogo.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leftAnchor, constant: 94).isActive = true
self.banditLogo.widthAnchor.constraint(equalToConstant: 224).isActive = true
self.banditLogo.heightAnchor.constraint(equalToConstant: 289).isActive = true
}
and here is the func that makes the animation.
this func is being called in viewDidAppear and animatedImage variable of the function is referred to banditLogo UIimageView.
so when the view screen loads up, the image moves to top of the view.
func logoAnimate(animatedImage: UIImageView!, animatedLabel: UILabel!) {
UIView.animate(withDuration: 1.5, delay: 1, options: [.allowAnimatedContent]) {
animatedImage.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor, constant: 5).isActive = true
animatedImage.leftAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.leftAnchor, constant: 94).isActive = true
} completion: { (true) in
UIView.animate(withDuration: 0.25) {
animatedLabel.alpha = 1
}
}
}
You may find it easier to create a class-level property to hold the image view's top constraint, then change that constraint's .constant value when you want to move it.
Here's a quick example - tapping anywhere on the view will animate the image view up or down:
class AnimLogoViewController: UIViewController {
let banditLogo = UIImageView()
// we'll change this constraint's .constant to change the image view's position
var logoTopConstraint: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
if let img = UIImage(systemName: "person.fill") {
banditLogo.image = img
}
view.addSubview(banditLogo)
// I assume this was a typo... you want to set it on the image view, not the controller's view
//view.translatesAutoresizingMaskIntoConstraints = false // autolayout activation
banditLogo.translatesAutoresizingMaskIntoConstraints = false // autolayout activation
// create the image view's top constraint
logoTopConstraint = banditLogo.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 304)
// activate it
logoTopConstraint.isActive = true
// non-changing constraints
self.banditLogo.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leftAnchor, constant: 94).isActive = true
self.banditLogo.widthAnchor.constraint(equalToConstant: 224).isActive = true
self.banditLogo.heightAnchor.constraint(equalToConstant: 289).isActive = true
// animate the logo when you tap the view
let t = UITapGestureRecognizer(target: self, action: #selector(self.didTap(_:)))
view.addGestureRecognizer(t)
}
#objc func didTap(_ g: UITapGestureRecognizer) -> Void {
// if the logo image view is at the top, animate it down
// else, animate it up
if logoTopConstraint.constant == 5.0 {
logoTopConstraint.constant = 304.0
} else {
logoTopConstraint.constant = 5.0
}
UIView.animate(withDuration: 1.5, animations: {
self.view.layoutIfNeeded()
})
}
}
I animate views that have constraints by changing constraints, not setting them. Leave the constraints that are static "as is" - that is, use isActive = true. But those you wish to change? Put them in two arrays and activate/deactivte them. Complete the animation like you are by using UIView.animate.
For instance, let's say you wish to move banditLogo from top 304 to top 5, which appears to me to be what you trying to do. Leave all other constraints as is - left (which your code doesn't seem to change), height, and width. Now, create two arrays:
var start = [NSLayoutConstraint]()
var finish = [NSLayoutConstraint]()
Add in the constraints that change. Note that I'm not setting them as active:
start.append(banditLogo.topAnchor.constraint(equalTo: safeAreaView.topAnchor, constant: 305))
finish.append(banditLogo.topAnchor.constraint(equalTo: safeAreaView.topAnchor, constant: 5))
Initialize things in viewDidLoad or any other view controller method as needed:
NSLayoutConstraint.activate(start)
Finally, when you wish to do the animation, deactivate/activate and tell the view to show the animation:
NSLayoutConstraint.deactivate(start)
NSLayoutConstraint.activate(finish)
UIView.animate(withDuration: 0.3) { self.view.layoutIfNeeded() }
Last piece of critique, made with no intent of being offending.
Something in your code posted feels messy to me. Creating a function to move a single view should directly address the view IMHO, not pass the view into it. Maybe you are trying to move several views this way - in which case this is good code - but nothing in your question suggests it. It's okay to do the animation in a function - that way you can call it when needed. I do this all the time for something like this - sliding a tool overlay in and out. But if you are doing this to a single view, just address it directly. The code is more readable to other coders.
Also, my preference for the start is in viewDidLoad unless the VC is part of a navigation stack. But in that case, don't just use viewDidAppear, set things back to start in viewDidDisappear.
EDIT: looking at the comments, I assumed that yes you have already used translatesAutoresizingMaskIntoConstraints = false properly on every view needed.

UIPickerView width constraint issue

I need to add UIPickerView to UIAlertController. I use code:
let alertView = UIAlertController()
let pickerView = UIPickerView()
pickerView.translatesAutoresizingMaskIntoConstraints = false
alertView.view.addSubview(pickerView)
pickerView.topAnchor.constraint(equalTo: alertView.view.topAnchor, constant: 50).isActive = true
pickerView.bottomAnchor.constraint(equalTo: alertView.view.bottomAnchor, constant: -50).isActive = true
pickerView.centerXAnchor.constraint(equalTo: alertView.view.centerXAnchor).isActive = true
pickerView.widthAnchor.constraint(equalTo: alertView.view.widthAnchor).isActive = true
present(alertView, animated: true)
Here it is a result:
Why width of the UIPickerView more than UIAlertController?
Also I tried:
pickerView.widthAnchor.constraint(equalTo: alertView.view.widthAnchor, multiplier: 0.5).isActive = true
But got the same result...
when you give a constraint of width which exactly doing right.Issue is from your side look below image when you give a width constant of alertview.view which one UIalertController's main view.For better understand look below image(last view behind blue colour view colour and PickerView color is red.).
Now,we show actual alerview which one subview of main view(in above image Blue colour with Ok button).
change width constraint with subview instead of main view like:-
For better under standing:-
for view in alertView.view.subviews {
print(view)
//just add color which is shown in bellow image.
view.backgroundColor = UIColor.blue
}
So, Solution is like:-
pickerView.widthAnchor.constraint(equalTo: alertView.view.subviews[0].widthAnchor).isActive = true
Result:-
I hope this help you,
Thanks

Programmatically setting constraints not working for my table view

I have these constraints in place for my UItableView (from storyboard):
musicHomeTableView.translatesAutoresizingMaskIntoConstraints = false
musicHomeTableView.topAnchor.constraint(equalTo: view.topAnchor ).isActive = true
musicHomeTableView.bottomAnchor.constraint(equalTo: mainTabBar.topAnchor).isActive = true
musicHomeTableView.widthAnchor.constraint(equalToConstant: view.frame.width).isActive = true
The above code is called from viewDidLoad and it works fine. Yet when I try to execute this code later:
musicHomeTableView.bottomAnchor.constraint(equalTo: playerView.topAnchor).isActive = true
Nothing happens. mainTabBar and playerView are both programmatically setUp views and subviews of view.
I've tried view.layoutSubviews() and view.layOutIfNeeded()
musicHomeTableView already has a bottomAnchor constraint. You need to remove this constraint before adding another one to the same anchor. Setting another constraint to the same anchor does not remove the old constraint.
class ViewController {
var musicHomeTableViewBottomAnchorConstraint: NSLayoutConstraint?
func viewDidLoad() {
musicHomeTableViewBottomAnchorConstraint = musicHomeTableView.bottomAnchor.constraint(equalTo: view.topAnchor)
musicHomeTableViewBottomAnchorConstraint!.isActive = true
}
func addNewConstraint() {
if let constraint = musicHomeTableViewBottomAnchorConstraint {
constraint.isActive = false
}
// Add new constraint here
}
}
There are other ways to do this, but this is one.

sequence of auto layout methods get called in rendering cycle

I was reading about auto layout rendering pipelines i mean how auto layout work under the hood. There are some methods which get called at different stages of autoLayout rendering like
layoutIfNeeded()
layoutSubviews()
updateConstraints()
updateConstraintsIfNeeded()
but i don't know which method is called when and what is the significance of that method and if i want to use auto layout then in which order i can use that methods and how can i control the autoLayout rendering pipeline
Usually you don't need to care about the autolayout method chain. You just need to create the constraints for the views to define their sizes and positions. You can add/remove, activate/deactivate constraints anytime in lifecycle of the view, but you want to always have a set of satisfiable (non-conflicting), yet complete set of constraints.
Take an example. You can tell the autolayout that button A should be 50 points wide, 20 points high, with its left top corner positioned at point (0,0) in the viewController's view. Now, this is non-conflicting, yet complete set of constraints for the button A. But lets say you want to expand that button, when the user taps it. So in the tap handler you will add one new constraint saying that the button should be 100 points wide - now you have unsatisfiable constraints - there is a constraint say it should be 50 points wide, and another one saying it shoul be 100 points wide. Therefore, to prevent conflict, before activating the new constraint, you have to deactivate the old one. Incomplete constraints is an opposite case, lets say you deactivate the old width constraint, but never activate the new one. Then autolayout can calculate position (because there are constraints defining it), and height, but not width, which usually ends in undefined behavior (now in case of a UIButton that's not true, because it has intrinsic size, which implicitly defines its width and height, but I hope you get the point).
So when you create those constraints is up to you (in my example you were manipulating them when the user tapped the button). Usually you start in initializer in case of a UIView subclass or in loadView in UIViewController subclass and there you can define and activate the default set of constraints. And then you can use handlers to react to user activity. My recommendation is prepare all the constraints in loadView, keep them in properties, and activate/deactivate them when necessary.
But there are of course some limitation as when and how not to create new constraints - for a more detailed discussion of those cases I really recommend looking at Advanced Autolayout Toolbox by objc.io.
EDIT
See following example of a simple custom SongView that uses autolayout for layout and supports also some dynamic changes in constraints by activating/deactivating them. You can just simply copy paste the whole code into a playground and test it out there, or include it in a project.
Notice there that I don't call any of the autolayout lifecycle methods, except of setNeedsLayout and layoutIfNeeded. setNeedsLayout sets a flag telling the autolayout that constraints have been changed, and layoutIfNeeded then tells it to recalculate frames. Normally, that would happen automatically, but to animate the constraints changes we need to tell it explicitly - see the setExpanded method in SongView. For more detailed explanation of using autolayout in animations, see my different answer.
import UIKit
import PlaygroundSupport
class ViewController: UIViewController {
let songView = SongView()
let button = UIButton()
override func loadView() {
super.loadView()
view.backgroundColor = .white
self.view.addSubview(button)
self.view.addSubview(songView)
button.setTitle("Expand/Collapse", for: .normal)
button.setTitleColor(.blue, for: .normal)
button.addTarget(self, action: #selector(expandCollapse), for: .touchUpInside)
button.translatesAutoresizingMaskIntoConstraints = false
songView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
// button has intrinsic size, no need to define constraints for size, position is enough
button.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: -50),
button.centerXAnchor.constraint(equalTo: self.view.centerXAnchor),
// songView has defined its height (see SongView class), but not width, therefore we need more constraints
songView.centerYAnchor.constraint(equalTo: self.view.centerYAnchor),
songView.leftAnchor.constraint(equalTo: self.view.leftAnchor),
songView.rightAnchor.constraint(equalTo: self.view.rightAnchor),
])
}
#objc func expandCollapse() {
if songView.isExpanded {
songView.setExpanded(to: false, animated: true)
} else {
songView.setExpanded(to: true, animated: true)
}
}
}
class SongView: UIView {
private let numberLabel: UILabel = UILabel()
private let nameLabel: UILabel = UILabel()
private var expandedConstraints: [NSLayoutConstraint] = []
private var collapsedConstraints: [NSLayoutConstraint] = []
// this can be triggered by some event
private(set) var isExpanded: Bool = false
func setExpanded(to expanded: Bool, animated: Bool) {
self.isExpanded = expanded
if animated {
if expanded {
// setup expanded state
NSLayoutConstraint.deactivate(collapsedConstraints)
NSLayoutConstraint.activate(expandedConstraints)
} else {
// setup collapsed
NSLayoutConstraint.deactivate(expandedConstraints)
NSLayoutConstraint.activate(collapsedConstraints)
}
self.setNeedsLayout()
UIView.animate(withDuration: 0.2, animations: {
self.layoutIfNeeded()
})
} else {
// non animated version (no need to explicitly call setNeedsLayout nor layoutIfNeeded)
if expanded {
// setup expanded state
NSLayoutConstraint.deactivate(collapsedConstraints)
NSLayoutConstraint.activate(expandedConstraints)
} else {
// setup collapsed
NSLayoutConstraint.deactivate(expandedConstraints)
NSLayoutConstraint.activate(collapsedConstraints)
}
}
}
var data: (String, String)? {
didSet {
numberLabel.text = data?.0
nameLabel.text = data?.1
}
}
init() {
super.init(frame: CGRect.zero)
setupInitialHierarchy()
setupInitialAttributes()
setupInitialLayout()
}
fileprivate func setupInitialHierarchy() {
self.addSubview(numberLabel)
self.addSubview(nameLabel)
}
fileprivate func setupInitialAttributes() {
numberLabel.font = UIFont.boldSystemFont(ofSize: UIFont.preferredFont(forTextStyle: UIFontTextStyle.body).pointSize)
numberLabel.textColor = UIColor.darkGray
numberLabel.text = "0"
numberLabel.textAlignment = .right
nameLabel.font = UIFont.preferredFont(forTextStyle: UIFontTextStyle.body)
nameLabel.text = "NONE"
nameLabel.textAlignment = .left
self.backgroundColor = UIColor.lightGray
}
fileprivate func setupInitialLayout() {
self.translatesAutoresizingMaskIntoConstraints = false
numberLabel.translatesAutoresizingMaskIntoConstraints = false
nameLabel.translatesAutoresizingMaskIntoConstraints = false
// just randomly selected different layouts for collapsed and expanded states
expandedConstraints = [
numberLabel.widthAnchor.constraint(equalToConstant: 35),
self.heightAnchor.constraint(equalToConstant: 80),
]
collapsedConstraints = [
numberLabel.widthAnchor.constraint(equalToConstant: 50),
self.heightAnchor.constraint(equalToConstant: 40),
]
// activating collapsed as default layout
NSLayoutConstraint.activate(collapsedConstraints)
NSLayoutConstraint.activate([
numberLabel.topAnchor.constraint(equalTo: self.topAnchor, constant: 4),
numberLabel.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: -4),
numberLabel.leftAnchor.constraint(equalTo: self.leftAnchor, constant: 4),
nameLabel.centerYAnchor.constraint(equalTo: numberLabel.centerYAnchor),
nameLabel.leftAnchor.constraint(equalTo: numberLabel.rightAnchor, constant: 8),
nameLabel.rightAnchor.constraint(equalTo: self.rightAnchor, constant: -4)
])
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
PlaygroundPage.current.liveView = ViewController()

Dynamic corner radius for a UITextView

I am trying to make my uitextview's corner radius dynamic depending on the number of lines displayed. The text is retrieved from my backend, so the number of lines will vary...
I've tried setting the corner radius in viewDidLoad after the text view is created by using its frame but for some reason that doesn't work. I'm assuming its returning 0 for some reason.
Any help is greatly appreciated.
(I'm cutting out a lot of code just to keep it simple here. Everything is added to the subview properly. Everything is added programmatically - not using the storyboard at all here. The text views display as expected besides the corner radius)
inside viewDidLoad:
questionOneTextBox.layer.cornerRadius = self.questionOneTextBox.frame.height * 0.5
questionTwoTextBox.layer.cornerRadius = self.questionTwoTextBox.frame.height * 0.5
If you are creating the text view programmatically with constraints, then you need to call self.questionOneTextBox.layoutIfNeeded() and self.questionTwoTextBox.layoutIfNeeded(). This will initialise the bounds.
class ViewController: UIViewController {
var textView: UITextView!
override func viewDidLoad() {
super.viewDidLoad()
// Create the Text View
self.textView = UITextView()
self.textView.text = "This text will appear in the text view."
self.textView.backgroundColor = .lightGray
self.view.addSubview(self.textView)
// Set the constraints
self.textView.translatesAutoresizingMaskIntoConstraints = false
self.textView.widthAnchor.constraint(equalToConstant: 280).isActive = true
self.textView.heightAnchor.constraint(equalToConstant: 60).isActive = true
self.textView.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 60).isActive = true
self.textView.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true
// This will initialise the bounds
self.textView.layoutIfNeeded()
// Now the this should work
self.textView.layer.cornerRadius = self.textView.frame.height * 0.5
}
}
This what this code looks like.
Views are not laid out (sized) when viewDidLoad is called. Move your corner radius sizing to viewDidLayoutSubviews or viewWillAppear, at which time the sizing has occurred.
Did you set clipsToBounds to true? This might be causing your problem. I hope this helped

Resources