swift4 : how to animate UIView with NSLayoutConstraint - swift4

i want to change width of uiview1 (that created by NSLayoutConstraint visual format language) by animation
this is my codes:
var constraintArray : [NSLayoutConstraint] = []
constraintArray += NSLayoutConstraint.constraints(withVisualFormat: "H:|-10-[uiview1]-10-|", options: [], metrics: nil, views: viewsDict)
constraintArray += NSLayoutConstraint.constraints(withVisualFormat: "H:|-10-[uiview2]-10-|", options: [], metrics: nil, views: viewsDict)
constraintArray += NSLayoutConstraint.constraints(withVisualFormat: "H:|-10-[uiview3]-10-|", options: [], metrics: nil, views: viewsDict)
constraintArray += NSLayoutConstraint.constraints(withVisualFormat: "V:|-100-[uiview1]-20-[uiview2]-30-[uiview3]-30-|", options: [], metrics: nil, views: viewsDict)
NSLayoutConstraint.activate(constraintArray)
func changeUIViewWidth{
//i don't know how to change width of uiview1 by animation
UIView.animate(withDuration: 3){
self.view.layoutIfNeeded()
}
}
how i can do this?

In your case it's going to be challenging because you do not have any explicit width constraint for uiview1 that you can change in code. The closest thing you have would be by inspecting the constraints in
constraintArray += NSLayoutConstraint.constraints(withVisualFormat: "H:|-10-[uiview1]-10-|", options: [], metrics: nil, views: viewsDict)
and adjust each constraint (there should be two constraints) returned from that line and change their constant.
let uiview1Constraints = NSLayoutConstraint.constraints(withVisualFormat: "H:|-10-[uiview1]-10-|", options: [], metrics: nil, views: viewsDict)
...
// Make uiview1 thinner
uiview1Constraints[0].constant = 20
uiview1Constraints[1].constant = 20
UIView.animate(withDuration: 3) {
self.view.layoutIfNeeded()
}
I'd recommend you use NSLayoutAnchor to create constraints. It's simpler and easier to read and also less error prone. You can still manipulate constant directly after creating them.

Related

Constraint animation doesnt animate y position change

This code is to expand a label by disabling the constraint which is keeping it at a max height of 54 to a full size. It works as it should but it jumps to the new y position straight away (without animating) and changes frame height after which makes it look like it jumps before animating
#IBOutlet var descriptionHeightConstraint: NSLayoutConstraint!
#IBAction func toggleFullDescription(_ sender: Any) {
if descriptionHeightConstraint.isActive {
self.layoutIfNeeded()
descriptionHeightConstraint.isActive = false
UIView.animate(withDuration: 0.3, delay: 0, options: .curveEaseInOut, animations: {
self.layoutIfNeeded()
}, completion: nil)
//seeMoreButton.setTitle("See less", for: .normal)
} else {
self.layoutIfNeeded()
descriptionHeightConstraint.isActive = true
UIView.animate(withDuration: 0.3, delay: 0, options: .curveEaseInOut, animations: {
self.layoutIfNeeded()
}, completion: nil)
//seeMoreButton.setTitle("See more", for: .normal)
}
}
Video of the issue

Swift Visual Format Language four buttons in centre

I would like to arrange four buttons with Visual Format Language around the central X an Y of a view without hard coding any points, preferring to scale with constraints.
I can only achieve a cluster of buttons to align to the bottom margin, how do I centre them with the spacing you see (e.g. ~20 points) without resorting to NSLayoutConstraint?
I did not place them in a stack, they are all separate buttons.
I read that stacks were not a good idea, but it seems like the logical way, otherwise they stretch out vertically.
Ideally I would like to use VFL to make a calculator UI but am trying this first.
#IBDesignable class images_and_constraints: UIButton {
override init(frame: CGRect) {
super.init(frame: frame)
calcButtons()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
calcButtons()
}
private func calcButtons() {
let calcPlus = UIButton()
calcPlus.translatesAutoresizingMaskIntoConstraints = false
calcPlus.setTitle("+", for: .normal)
calcPlus.setTitleColor(UIColor.black, for: .normal)
calcPlus.setTitleColor(UIColor.white, for: .highlighted)
calcPlus.backgroundColor = UIColor.orange
addSubview(calcPlus)
let calcSubtract = UIButton()
calcSubtract.translatesAutoresizingMaskIntoConstraints = false
calcSubtract.setTitle("-", for: .normal)
calcSubtract.setTitleColor(UIColor.black, for: .normal)
calcSubtract.setTitleColor(UIColor.white, for: .highlighted)
calcSubtract.backgroundColor = UIColor.orange
addSubview(calcSubtract)
let calcMultiply = UIButton()
calcMultiply.translatesAutoresizingMaskIntoConstraints = false
calcMultiply.setTitle("x", for: .normal)
calcMultiply.setTitleColor(UIColor.black, for: .normal)
calcMultiply.setTitleColor(UIColor.white, for: .highlighted)
calcMultiply.backgroundColor = UIColor.orange
addSubview(calcMultiply)
let calcDivide = UIButton()
calcDivide.translatesAutoresizingMaskIntoConstraints = false
calcDivide.setTitle("/", for: .normal)
calcDivide.setTitleColor(UIColor.black, for: .normal)
calcDivide.setTitleColor(UIColor.white, for: .highlighted)
calcDivide.backgroundColor = UIColor.orange
addSubview(calcDivide)
let views = ["calcPlus": calcPlus,
"calcSubtract": calcSubtract,
"calcMultiply": calcMultiply,
"calcDivide": calcDivide]
NSLayoutConstraint.activate(NSLayoutConstraint.constraints(withVisualFormat: "H:|-[calcPlus]-[calcSubtract(==calcPlus)]-|",
options: .alignAllBottom,
metrics: nil,
views: views))
NSLayoutConstraint.activate(NSLayoutConstraint.constraints(withVisualFormat: "H:|-[calcMultiply]-[calcDivide(==calcMultiply)]-|",
options: .alignAllTop,
metrics: nil,
views: views))
NSLayoutConstraint.activate(NSLayoutConstraint.constraints(withVisualFormat: "V:[calcSubtract]-[calcDivide(==calcSubtract)]-|",
options: .alignAllCenterX,
metrics: nil,
views: views))
NSLayoutConstraint.activate(NSLayoutConstraint.constraints(withVisualFormat: "H:[calcSubtract]",
options: .alignAllCenterX,
metrics: nil,
views: views))
}
}
Using VFL to center views requires trickery.
Look at this question and particularly this answer for the trick.
For the kind of layout you want, VFL is just not a good fit.
Just one NSLayoutConstraint in addition to VFL would solve it but since you're only interested in VFL, I would suggest you use the trick to center a container view that holds your buttons.
Solution:
func calcButtons() {
//1. Create a container view that will contain your operator buttons
let buttonContainerView = UIView()
buttonContainerView.backgroundColor = UIColor.lightGray
buttonContainerView.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(buttonContainerView)
//Place it vertically in the center of the superview
NSLayoutConstraint.activate(NSLayoutConstraint.constraints(withVisualFormat: "V:[superview]-(<=1)-[childView]",
options: .alignAllCenterX,
metrics: nil,
views: ["superview" : self,
"childView" : buttonContainerView]))
//Place it horizontally in the center of the superview + equal widths to superview
NSLayoutConstraint.activate(NSLayoutConstraint.constraints(withVisualFormat: "H:[superview]-(<=1)-[childView(==superview)]",
options: .alignAllCenterY,
metrics: nil,
views: ["superview" : self,
"childView" : buttonContainerView]))
//2. Create your buttons as you were:
//DRY Fix: Helper function to create button and add it to `buttonContainerView`
func addButton(title: String, selector: Selector? = nil) -> UIButton {
let button = UIButton()
button.backgroundColor = UIColor.orange
button.setTitle(title, for: .normal)
button.setTitleColor(UIColor.black, for: .normal)
button.setTitleColor(UIColor.white, for: .highlighted)
//You might need this later cuz a button gotta do wat a button gotta do
if let selector = selector {
button.addTarget(self, action: selector, for: UIControlEvents.touchUpInside)
}
button.translatesAutoresizingMaskIntoConstraints = false
buttonContainerView.addSubview(button)
return button
}
let calcPlus = addButton(title: "+", selector: #selector(CalculatorView.add))
let calcSubtract = addButton(title: "-")
let calcMultiply = addButton(title: "x")
let calcDivide = addButton(title: "/")
let views = ["calcPlus": calcPlus,
"calcSubtract": calcSubtract,
"calcMultiply": calcMultiply,
"calcDivide": calcDivide]
//Same as before
NSLayoutConstraint.activate(NSLayoutConstraint.constraints(withVisualFormat: "H:|-[calcPlus]-[calcSubtract(==calcPlus)]-|",
options: .alignAllBottom,
metrics: nil,
views: views))
//Same as before
NSLayoutConstraint.activate(NSLayoutConstraint.constraints(withVisualFormat: "H:|-[calcMultiply]-[calcDivide(==calcMultiply)]-|",
options: .alignAllTop,
metrics: nil,
views: views))
/*
Same as before but this time we give a top constraint too
i.e.
"V:|-[calcSubtract]..."
instead of
"V:[calcSubtract]..."
*/
//
NSLayoutConstraint.activate(NSLayoutConstraint.constraints(withVisualFormat: "V:|-[calcSubtract]-[calcDivide(==calcSubtract)]-|",
options: .alignAllCenterX,
metrics: nil,
views: views))
}
In the end I decided on NSLayoutConstraint.activate of which each button would be reliant on the one before it (rows), with the leading (far left for left-to-right readers) button constrained to the one above it.
calculatriceButtons["7"]!.leadingAnchor.constraint(equalTo: guide.leadingAnchor, constant: 1.0),
calculatriceButtons["7"]!.topAnchor.constraint(equalTo: calculatriceButtons["C"]!.bottomAnchor, constant: 1.0),
This was the best way to assure the buttons scaled on all devices.
There is a new alternative to using VFL which is what I use in code now.
Layout Anchors
Each view has different anchors. leading, trailing, top, bottom, etc...
You can use these to create constraints for you...
NSLayoutConstraint.activate([
viewB.leadingAnchor.constraint(equalTo: viewA.leadingAnchor, constant: 20),
viewA.widthAnchor.constraint(equalTo: viewB.widthAnchor)
])
for example.
Stack View
In addition to that there is an even more modern approach which is to use UIStackView. This is a really useful view that takes away the need to add constraints and does it for you.
let stackView = UIStackView(arrangedSubViews: [viewA, viewB])
stackView.spacing = 20
stackView.axis = .horizontal
stackView.alignment = .center
stackView.distribution = .fillEqually
You can also nest stack views to create more complex layouts.
Definitely worth looking in to...
https://developer.apple.com/documentation/uikit/uistackview?changes=_6
Creating your layout
let upperStackView = UIStackView(arrangedSubviews: [topLeft, topRight])
upperStackView.axis = .horizontal
upperStackView.distribution = .fillEqually
upperStackView.spacing = 20
let lowerStackView = UIStackView(arrangedSubviews: [bottomLeft, bottomRight])
lowerStackView.axis = .horizontal
lowerStackView.distribution = .fillEqually
lowerStackView.spacing = 20
let mainStackView = UIStackView(arrangedSubviews: [upperStackView, lowerStackView])
mainStackView.axis = .vertical
mainStackView.distribution = .fillEqually
mainStackView.spacing = 20
view.addSubview(mainStackView)
NSLayoutConstraint.activate([
mainStackView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
mainStackView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
mainStackView.widthAnchor.constraint(equalToConstant: 200),
mainStackView.heightAnchor.constraint(equalToConstant: 200),
])
Why not VFL?
While VFL was a nice first attempt at AutoLayout, I feel that Apple has moved away from it now and are moving towards these more succinct methods of creating AutoLayout constraints.
It still allows you to think in constraints while writing code but provides a slightly more modern approach.
Of course... you can also create UIStackView in Interface Builder also :D

How to repeat flip animation?

I'm trying to create my own activity indicator view like twitch chat activity indicator view. But I've tried to use animate .repeat:
override func animate() {
UIView.animate(withDuration: 1, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: [.repeat, .transitionFlipFromLeft], animations: {
self.flip()
}, completion: nil)
}
and also timer
let timer = Timer.init(timeInterval: 2, target: self, selector: #selector(flip), userInfo: nil, repeats: true)
Here is my flip() function
func flip() {
self.isFlipped = !self.isFlipped
let fromView = self.isFlipped ? self.view1 : self.view2
let toView = self.isFlipped ? self.view2 : self.view1
UIView.transition(from: fromView, to: toView, duration: 1, options: [.transitionFlipFromTop, .showHideTransitionViews], completion: nil)
}
But it did not work. I don't know what I missed. Can you guys help me please?

How do I add a label on top of a gradient?

I have a very basic UICollectionView. Inside my CollectionViewCell, I have a "thumbnailImage" which is just an image. I would like to have a gradient layer that fades from black, to a clear color BUT I would also like to have a UILabel ON TOP of this CAGradient and not underneath. The label is the "MovieTitle". I am programmatically doing everything, including the constraints. How do I perform this? Here is my code
let myView: UIView = {
let view = UIView()
return view
}()
let gradientView: CAGradientLayer = {
let grad = CAGradientLayer()
grad.colors = [UIColor.white.cgColor, UIColor.red.cgColor]
grad.locations = [0.7, 1.2]
return grad
}()
func setupViews() {
thumbnailImageView.addSubview(movieTitle)
addSubview(thumbnailImageView)
thumbnailImageView.addSubview(dividerLine)
thumbnailImageView.addSubview(myView)
myView.layer.addSublayer(gradientView)
addConstrainsWithFormat(format: "H:|[v0]|", views: thumbnailImageView)
addConstrainsWithFormat(format: "V:|[v0]|", views: thumbnailImageView)
thumbnailImageView.addConstrainsWithFormat(format: "H:|[v0]|", views: myView)
thumbnailImageView.addConstrainsWithFormat(format: "V:|[v0]|", views: myView)
thumbnailImageView.addConstrainsWithFormat(format: "H:|[v0]|", views: dividerLine)
thumbnailImageView.addConstrainsWithFormat(format: "V:[v0(0.75)]|", views: dividerLine)
thumbnailImageView.addConstrainsWithFormat(format: "H:|-16-[v0]-16-|", views: movieTitle)
thumbnailImageView.addConstrainsWithFormat(format: "V:[v0(25)]-8-|", views: movieTitle)
}
Try to change the order of your views so that label appears above the view with gradient inside:
myView.layer.addSublayer(gradientView)
thumbnailImageView.addSubview(myView)
thumbnailImageView.addSubview(movieTitle)
thumbnailImageView.addSubview(dividerLine)
addSubview(thumbnailImageView)
You can also insert layers and views below or above already existing layers/views in your hierarchy:
view.insertSubview(subview, at: 0)
view.insertSubview(subview, belowSubview: existingView)
view.insertSubview(subview, aboveSubview: existingView)
layer.insertSublayer(gradientLayer, at: 0)
layer.insertSublayer(gradientLayer, below: anotherLayer)
layer.insertSublayer(gradientLayer, above: anotherLayer)

Programmatic layout : UIStackView alignment doesn't seem to work

I have created a UIViewController with a UIStackView (vertical axis) wrapped in a UIScrollView that's pinned to the edges of the root View with auto-layout constraints.
I have also generated a number of UIButtons and added to the arranged subviews of the UIStackView.
I have tried to no avail to centre align the UIButtons in the UIStackView.
I'm not certain what i'm doing wrong.
Here's the code:
import UIKit
class ViewController: UIViewController {
var scrollView: UIScrollView!
var stackView: UIStackView!
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor.whiteColor()
scrollView = UIScrollView()
scrollView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(scrollView)
view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|[scrollView]|", options: .AlignAllCenterX, metrics: nil, views: ["scrollView": scrollView]))
view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|[scrollView]|", options: .AlignAllCenterX, metrics: nil, views: ["scrollView": scrollView]))
stackView = UIStackView()
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.axis = .Vertical
stackView.alignment = .Center
scrollView.addSubview(stackView)
scrollView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|[stackView]|", options: NSLayoutFormatOptions.AlignAllCenterX, metrics: nil, views: ["stackView": stackView]))
scrollView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|[stackView]|", options: NSLayoutFormatOptions.AlignAllCenterX, metrics: nil, views: ["stackView": stackView]))
for _ in 1 ..< 100 {
let vw = UIButton(type: UIButtonType.System)
vw.setTitle("Button", forState: .Normal)
stackView.addArrangedSubview(vw)
}
}
}
An extra equal width constraint between the scrollView and the stackView was needed. Like this:
scrollView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:[stackView(==scrollView)]", options: .AlignAllCenterX, metrics: nil, views: ["stackView": stackView, "scrollView": scrollView]))
That did it for me.
How about:
stackView.Alignment = UIStackViewAlignment.Center
?