animation: stop reset position of an element (swift) - swift

I'm trying animations and I'm facing a big problem: when my animation finished and I do something ( touch the screen, etc), the elements reset their positions to their first position.
I found this: [blog]: Animation Blocks resets to original position after updating text
they say it's because elements have constraints or auto layout so turn it off to fix it but I don't want to turn it off.
Can we update constraints programmatically?
is there an other solution?
here's my animation:
#IBOutlet var tfUser: UITextfiled!
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
tfUser.center.x += view.bounds.width
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
UIView.animateWithDuration(2, delay: 1.8, options: [.CurveEaseInOut], animations: {
self.tfUser.center.x += self.view.bounds.width
//self.tfUser.frame = CGRectMake(0, 233, 375 , 89)
// i tried CGRectMake but it delete the animation
}, completion: nil)
}

In comment of my question , #Hardik Shekhat told to use :
self.tfUser.translatesAutoresizingMaskIntoConstraints = true
it works for me !

Related

How do I enable a .jpg image to fade in, in swift uiKit

Good evening.
I downloaded a .jpg image and attached it through storyboard, and linked it to Home.swift. At this point, when I run the simulator the image is there. It does nothing at this time, but it's there.
I am trying to modify the code so that the image fades in when the simulator first runs. However, swift isn't liking my approach.
Much appreciate any guidance.
import Foundation
import UIKit
class Home: UIViewController {
#IBOutlet var lonelinessLbl: UILabel!
#IBOutlet var Image: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
startAnimations()
// Do any additional setup after loading the view.
}
func startAnimations() {
Image.animationImages = [
UIImage(named: "birds.jpg")]
Image.animationDuration = 3
Image.startAnimations()
}
}
Inside startAnimations:
imageView.image = UIImage(named: "birds.jpg")
imageView.alpha = 0
UIViewPropertyAnimator(duration: 5.0, curve: .easeIn) {
self.imageView.alpha = 1.0
}.startAnimation()
(Note that I've renamed Image to imageView -- the Swift convention is to name variables in camel case, starting with a lowercase letter)
You can set the image to image view with animation.
Use the extension to apply animation anywhere.
extension UIImageView{
func setAnimatedImage(_ image: UIImage?) {
UIView.transition(with: self, duration: 0.5, options: .transitionCrossDissolve, animations: {
self.image = image
}, completion: nil)
}
}
Usage
yourImageView.setAnimatedImage(UIImage(named: "birds.jpg"))
Setting an image array to animationImages only helps you when you have a set of images to animate it like a gif.
If you want to present animation like increasing alpha you can do the following.
override func viewDidLoad() {
super.viewDidLoad()
Image.alpha = 0.0
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
UIView.animate(withDuration: 0.3,
animations: {
self.Image.alpha = 1.0
},
completion: nil)
}

Updating bottom constraint with SnapKit and Swift 4

Here is my code. I am using KeyboardHelper library for managing keyboard states and getting keyboard height, and also SnapKit. I have a text field that triggers keyboard. When a keyboard is visible I want to raise blue box above keyboard bounds, so it's visible. So far I failed to do so. Can you please help me with my code?
private var keyboardHelper : KeyboardHelper?
let box = UIView()
override func viewDidLoad() {
super.viewDidLoad()
keyboardHelper = KeyboardHelper(delegate: self)
guard let superview = self.view else {
return
}
box.backgroundColor = UIColor.blue
superview.addSubview(box)
box.snp.makeConstraints { make in
make.bottom.equalTo(superview.snp.bottom).offset(-16)
make.width.equalTo(200)
make.centerX.equalTo(superview)
make.height.equalTo(100)
}
}
func keyboardWillAppear(_ info: KeyboardAppearanceInfo) {
UIView.animate(withDuration: TimeInterval(info.animationDuration), delay: 0, options: info.animationOptions, animations: {
self.box.snp.updateConstraints({ make in
make.bottom.equalTo(info.endFrame.size.height).offset(10)
})
self.box.layoutIfNeeded()
}, completion: nil)
}
According to SnapKit docs:
if you are only updating the constant value of the constraint you can use the method snp.updateConstraints
You need to use snp.remakeConstraints in your case. Furthermore, you should probably call layoutIfNeeded on superview.
Optionally you could get rid of changing constraints and just animate proper transforms on show:
box.transform = CGAffineTransform(translationX: 0.0, y: neededTranslation)
on hide:
box.transform = .identity

Swift animation of constraint not working

The below function is just moving the view to a new place. It isn't showing the animation:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
UIView.animate(withDuration: 0.9, animations: {
//self.leadingConst.constant -= 200
self.view.setNeedsLayout()
})
}
self.leadingConst.constant -= 200 should be outside UIView.animate, like this:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.leadingConst.constant -= 200
UIView.animate(withDuration: 0.9, animations: {
self.view.setNeedsLayout()
})
}
reference: trying to animate a constraint in swift
A few weeks ago I ran into the same problem. The problems came down to a few things. I was mixing adding views programmatically and adding them to the storyboard, I wasn't calling layoutSubviews(), and I was adding my constraints as IBOutlets as weak vars. So I would suggest to only use storyboard (otherwise animations start to get complicated quickly) and make your leadingConst constraint a var. You should also move the object that you want to animate in the animation block and then use a completion block to reset the constraint.
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
view.layoutSubviews()
UIView.animate(withDuration: 0.9, animations: {
//something like this
self.animatableObject.origin.x -= 200
}, completion: {(finished: Bool) in
self.leadingConst.constant -= 200
})
}
I'm not sure what your trying to animate so the origin.x line is just an example.
2023 answer
The correct solution was given in a comment, you need to call layoutIfNeeded()
A typical example
UIView.animate(withDuration: t) { [weak self] in
guard let self = self else { return }
self.bottom.constant = -H
self.superview!.layoutIfNeeded()
}
IMPORTANT: "IN OR OUT" IS THE SAME
somewhat bizarrely, it makes NO difference if you do:
this
UIView.animate(withDuration: t) {
self.bottom.constant = -66
self.superview!.layoutIfNeeded()
}
or this
self.bottom.constant = -66
UIView.animate(withDuration: t) {
self.superview!.layoutIfNeeded()
}
There are 1000 totally incorrect comments on SO that you have to do it one way or the other to "mak eit work". It has nothing to do with the issue.
WHY THE SOLUTION IS layoutIfNeeded
It was perfectly explained in the #GetSwifty comment.
layoutIfNeeded is when the actual animation takes place. setNeedsLayout just tells the view it needs to layout, but doesn't actually do it.
WHY THE ISSUE IS CONFUSING: IT "SOMETIMES" WORKS ANYWAY
Often you actually do not need to explicitly have layoutIfNeeded. This causes lots of confusion. ie, this will work "sometimes" ...
UIView.animate(withDuration: t) {
self.bottom.constant = -66
}
There are particular things to consider:
If you are animating "the view itself" (ie likely in a custom UIView subclass), the cycle can be different from when you are animating some view below you "from above"
If you have other animations going on, that can affect the view cycle.
Note that if it "magically works" without layoutIfNeeded, it may NOT work other times in your app, depending on what's going on in the app.
In short you must always add layoutIfNeeded, that's all there is to it.
setNeedsLayout wont work, you need to layoutSubviews().
UIView.animate(withDuration: 0.2,
delay: 0.0,
animations: {
self.myConstraint.constant = value
self.view.layoutSubviews()
}

Invoke repeatable task when viewDidApper

I might be heading totally wrong direction, but what I'm trying to achieve is to load some view first and invoke some methods that will be running afterwards infinitely, while at the same time giving the User the possibility to interact with this view.
My first guess was to use viewDidApper with UIView.animate, as per below;
class MainView: UIView {
...
func animateTheme() {
//want this to print out infinitely
print("Test")
}
}
class ViewController: UIViewController {
#IBOutlet weak var mainView: MainView!
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
UIView.animate(withDuration: 1.0, delay: 0.0, options: .repeat, animations: {
self.mainView.animateTheme()
}, completion: nil)
}
}
The case is that I can only see a single "Test" printed out but this has to be invoked infinitely. Also, as I mentioned the User needs to have the possibility to interact with the view once everything is loaded.
Thanks a lot for any of your help.
ViewDidAppear will indeed be called latest when the view is loaded, but I don´t think the user can or will have the time to interact before it´s loaded.
If you want to load animations after everything is loaded you could create a timer and wait x seconds before you do that. Below example will be called 10 seconds after initiation.
var timer = Timer()
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
timer = Timer.scheduledTimer(timeInterval: 10.0, target: self, selector: #selector(animateSomething), userInfo: nil, repeats: false)
}
func animateSomething() {
// Animate here
}
Update
To make an infinite loop you can use the following snippet:
UIView.animate(withDuration: 5.0, delay: 0, options: [.repeat, .autoreverse], animations: {
self.view.frame = CGRect(x: 100, y: 200, width: 200, height: 200)
}, completion: nil)
I've done something very similar on one of my ViewControllers that loads a UIWebView. While the webpage is loading I show a UILabel with text "Loading" and every 1.5 seconds I add a period to the end of the text. I think the approach you describe above and mine shown below have the same effect and neither would be considered an infinite loop.
private func showLoadingMessage(){
let label = loadingLabel ?? UILabel(frame: CGRect.zero)
label.text = "Loading"
view.addSubview(label)
loadingLabel = label
addEllipsis()
}
private func addEllipsis() {
if webpageHasLoaded { return }
guard let labelText = loadingLabel?.text else {
return
}
DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) {
self.loadingLabel?.text = labelText + "."
self.addEllipsis()
}
}

Why is happening this weird behaviour between Animations and HorizontalMotionEffects?

I was testing out some code and ended up with this piece of code:
import UIKit
class ViewController: UIViewController {
#IBOutlet var backgroundImageView: UIImageView!
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(true)
hideAndShow()
}
func hideAndShow(){
self.backgroundImageView.alpha = 0
UIView.animateWithDuration(1.0, delay: 0, options: .Repeat | .AllowUserInteraction | .Autoreverse | UIViewAnimationOptions.CurveEaseOut, animations: { [weak self] in
self?.backgroundImageView.alpha = 1
}, completion: nil)
}
override func viewDidLoad() {
super.viewDidLoad()
self.backgroundImageView.contentMode = UIViewContentMode.Center;
self.backgroundImageView.backgroundColor = UIColor(patternImage: UIImage(named: "640x1136launch")!)
self.addParallaxEffectToView(self.backgroundImageView)
self.backgroundImageView.frame = CGRectInset(self.backgroundImageView.frame, -50, -50);
}
func addParallaxEffectToView(view: UIView ) {
// Set vertical effect
var verticalMotionEffect = UIInterpolatingMotionEffect(keyPath: "center.y", type: UIInterpolatingMotionEffectType.TiltAlongVerticalAxis)
verticalMotionEffect.minimumRelativeValue = -50
verticalMotionEffect.maximumRelativeValue = 50
// Set horizontal effect
var horizontalMotionEffect = UIInterpolatingMotionEffect(keyPath: "center.x", type: UIInterpolatingMotionEffectType.TiltAlongHorizontalAxis)
horizontalMotionEffect.minimumRelativeValue = -50
horizontalMotionEffect.maximumRelativeValue = -50
// Create group to combine both
var group = UIMotionEffectGroup.new()
group.motionEffects = [horizontalMotionEffect, verticalMotionEffect]
// Add both effects to your view
view.addMotionEffect(group);
}
}
What this code was supposed to do is to make a blinking effect over an UIImage that I have on my UIViewController.
This image also has an UIMotionEffect (tilt in this case) that works perfect to me when tilting the device vertically but for some reason does not work when my device is tilted horizontally.
Nevertheless, the point is that when I call to my hideandShow() function, my view starts to blink, but also, at the same pace,..starts to move horizontally!!!
Why is this happening? I'm out of ideas.