Reputation: 207
I have this strange issue where I want to update the UIView width but it only updates when the new width value if smaller, let's say UIView.width = 100. If new width = 80 the view updates, if it's 120 the view doesn't update.
The new value is fetched from core data.
No storyboard involved
P.s. I noticed that Xcode creates two constraints with the previous value and the new value at the same time and it breaks the one with the new value. But I can't understand why it creates two constraints.
<NSLayoutConstraint:0x280ef28a0 usedBar.right == unusedBar.right - 147.5 (active, names: usedBar:0x1029110c0, unusedBar:0x102911220 )>
<NSLayoutConstraint:0x280ef7570 usedBar.right == unusedBar.right - 73.75 (active, names: usedBar:0x1029110c0, unusedBar:0x102911220 )>
Will attempt to recover by breaking constraint
<NSLayoutConstraint:0x280ef7570 usedBar.right == unusedBar.right - 73.75 (active, names: usedBar:0x1029110c0, unusedBar:0x102911220 )>
class ViewController: UIViewController {
var width: CGFloat?
let usedBar = UIView()
var captionCount: CGFloat = 0
var captionUsed: CGFloat = 0
var unused: CGFloat = 0
var captionsUsedNumber: [Bool] = [false]
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
fetchCaptions()
width = ((scrollView.frame.width - 80) / captionCount) * unused
configureNumbers()
}
func configureNumbers() {
captionsView.addSubview(usedBar)
usedBar.backgroundColor = .bluePurple()
usedBar.anchors(top: captionTitleLabel.bottomAnchor, left: captionsView.leftAnchor, paddingTop: 12, paddingLeft: 20, width: width, height: 20)
}
func fetchCaptions() {
let request = NSFetchRequest<NSFetchRequestResult>(entityName: "Caption")
request.returnsObjectsAsFaults = false
do {
let result = try context.fetch(request)
if result.count > 0 {
captionCount = CGFloat(result.count)
captionUsed = CGFloat(captionsUsedNumber.count)
}
}
}
}
.anchors()
func anchors(top: NSLayoutYAxisAnchor? = nil, left: NSLayoutXAxisAnchor? = nil, bottom: NSLayoutYAxisAnchor? = nil,
right: NSLayoutXAxisAnchor? = nil, paddingTop: CGFloat? = 0, paddingLeft: CGFloat? = 0, paddingBottom: CGFloat? = 0,
paddingRight: CGFloat? = 0, width: CGFloat? = nil, height: CGFloat? = nil) {
translatesAutoresizingMaskIntoConstraints = false
if let top = top {
topAnchor.constraint(equalTo: top, constant: paddingTop!).isActive = true
}
if let left = left {
leftAnchor.constraint(equalTo: left, constant: paddingLeft!).isActive = true
}
if let bottom = bottom {
bottomAnchor.constraint(equalTo: bottom, constant: -paddingBottom!).isActive = true
}
if let right = right {
rightAnchor.constraint(equalTo: right, constant: -paddingRight!).isActive = true
}
if let width = width {
widthAnchor.constraint(equalToConstant: width).isActive = true
}
if let height = height {
heightAnchor.constraint(equalToConstant: height).isActive = true
}
}
[1]: https://i.sstatic.net/kSV8j.jpg
[2]: https://i.sstatic.net/Nqzp6.jpg
Upvotes: 2
Views: 1315
Reputation: 77477
You have a slight misunderstanding of how constraints work.
When you do something like this (in your func):
widthAnchor.constraint(equalToConstant: width).isActive = true
You are not setting THE width constraint. Instead, you are adding a width constraint. If you call that func again, you are adding another width constraint. You're also adding additional top, left and height constraints, but since you are not changing those values, you don't get any conflicts... but you still get extras.
While your "helper" func can save some repetitive code, right now it's causing issues.
You could edit the func to first remove any existing constraints, then add new ones... but that can lead to other issues (such as constraints which are relied on by other views).
In your specific case, since the only constraint you want to change is the width constraint, a better approach would be to create the constraint as a class property (var), and update its .constant
property when you want it to change.
Upvotes: 1
Reputation:
There may be other ways to do this, but for me the easiest is to create two arrays and toggle between them. And yes, I'm saying do not use setActive
when setting this up.
Let's say you want a view's width to change, and that normally you'd have this:
myView.centerXAnchor.constraint(equalTo: parentView.centerXAnchor).isActive = true
myView.centerYAnchor.constraint(equalTo: parentView.centerYAnchor).isActive = true
myView.widthAnchor.constraint(equalToConstant: 150).isActive = true
myView.heightAnchor.constraint(equalToConstant: 40).isActive = true
But now you wish to have the width go from 150 to 300. Create two arrays, move the width constraint into it, and activate
and/or deactivate
accordingly:
// declare the arrays
var growTheWidth = [NSLayoutConstraint]()
var shrinkTheWidth = [NSLayoutConstraint]()
// this can be done in viewDidLoad, just make sure the view exists in the hierarchy
growTheWidth.append(myView.widthAnchor.constraint(equalToConstant: 300))
shrinkTheWidth.append(myView.widthAnchor.constraint(equalToConstant: 150))
NSLayoutConstraint.activate(shrinkTheWidth)
// these two functions will change the width and animate
func growWidth() {
NSLayoutConstraint.deactivate(shrinkTheWidth)
NSLayoutConstraint.activate(growTheWidth)
UIView.animate(withDuration: 0.3) { self.view.layoutIfNeeded() }
}
func shrinkWidth() {
NSLayoutConstraint.deactivate(growTheWidth)
NSLayoutConstraint.activate(shrinkTheWidth)
UIView.animate(withDuration: 0.3) { self.view.layoutIfNeeded() }
}
Notes:
isActive
constraint I have in the original set of constraints.Upvotes: 1
Reputation: 156
If you are using XIB or StoryBoard with Autolayout you will update the constraint not the frame.
You can bind the autolayout to your code like this:
Remember delete the weak prefix or the value of your constraint will be always nil.
@IBOutlet var viewWidth: NSLayoutConstraint
Now you can edit your view with constraint like this:
self.viewWidth.constant = 120
I hope I've helped!
Upvotes: 0