Swift imageView animation doesn't work - ios

I have code to create animation:
var imageView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
imageView = UIImageView(frame:CGRect(x: 10, y: 10, width: 10, height: 10))
imageView.image = UIImage(named:"2.png")
UIView.animate(withDuration: 5) {
self.imageView = UIImageView(frame:CGRect(x: 10, y: 10, width: 500, height: 500))
self.view.addSubview(self.imageView)
self.view.layoutIfNeeded()
}
}
But animation doesn't work. And imageView not showing.

You should set the constraint's constant before the animation block and inside just call layoutIfNeeded
self.imageViewWidth.constant = 500
self.imageViewHeight.constant = 500
UIView.animate(withDuration: 10) {
self.layoutIfNeeded()
}

Try this..
UIView.animate(withDuration: 10) {
self.imageViewWidth.constant = 500
self.imageViewHeight.constant = 500
self.view.updateConstraintsIfNeeded()
self.view.layoutIfNeeded()
}
To decrease and increase size you have to use the on completion handler
UIView.animate(withDuration: 2, animations: {
self.imageViewWidth.constant = 10
self.imageViewHeight.constant = 10
self.view.layoutIfNeeded()
}) { (true) in
UIView.animate(withDuration: 10, animations: {
self.imageViewWidth.constant = 500
self.imageViewHeight.constant = 500
self.view.layoutIfNeeded()
})
}
if you want the decrease to start immediately change the first withDuration to 0

There are things that makes sense within an animation block (here: UIView.animate). Creating a newer image view is not one of them. Neither is addSubView.
The problem is that you are completely reassigning to your imageview variable - creating a new one within animation block.
You should create your image view and add it to your main view completely before you execute UIView.animate.
Animation is changing the visible properties of your objects, not recreating them. If you want frame change, simply do the following within animation block:
self.imageView.frame = CGRect(x: 10, y: 10, width: 500, height: 500)
You can also call layoutSubviews() or layoutIfNeeded() depending upon your constraint requirements, within UIView.animate.

Where is your problem
Problem 1
imageView = UIImageView(frame:CGRect(x: 10, y: 10, width: 10, height: 10))
imageView.image = UIImage(named:"2.png")
Here, you created new instance of an image view which never has been added into viewcontroller's view stack. You should add this view into stack. These line of code doesn't have any effect on animation performance.
Problem 2
UIView.animate(withDuration: 5) {
self.imageView = UIImageView(frame:CGRect(x: 10, y: 10, width: 500, height: 500))
self.view.addSubview(self.imageView)
self.view.layoutIfNeeded()
}
}
You initialized another imageview and added as a subview. This image view doesn't have any image or background color and as a result you do not see anything happending in the device screen. You should not do these stuff here. View.animate block does not need these stuff. Few things you should learn about animation. How animation works
Solution:
Try these lines of code instead.
// initial frame
var frame:CGRect = CGRect(x: 10, y: 10, width: 10, height: 10)
// create imageview and add this into view stack
self.imageView = UIImageView(frame:frame)
self.imageView.image = UIImage(named:"2.png")
self.view.addSubview(self.imageView)
// set new height & width
frame.size.width = 500;
frame.size.height = 500;
// animate view
UIView.animate(withDuration: 5) {
self.imageView.frame = frame;
self.view.layoutIfNeeded()
}
Hope, it will work!

if you want to animate constraints than you can use this
self.imageViewWidth.constant = 500
self.imageViewHeight.constant = 500;
UIView.animate(withDuration: 10) {
// if you are doing this in View controller then
self.view.layoutSubviews()
// if you are doing this in View then
self.layoutSubviews()
}

Related

How to properly animate SCNView height?

I'm trying to animate SCNView height, but it seems like there are some visual issue.
When content is growing it looks like everything is ok, but when I'm trying to decrease the height, view just jumps to final height instantly.
let newHeight = scene.frame.height + (open ? 100.0 : -100.0)
UIView.animate(withDuration: 1.0, animations: {
self.scene.frame = CGRect(x: 0, y: 0, width: self.view.frame.width, height: newHeight)
}) { finished in
self.open = !self.open
}
See
Code Running in Sumulator
With regular view everything works fine.
Set contentMode of SCNView to .scaleToFill where needed (e.g. in viewDidLoad). It works as expected.
let view: SCNView
...
view.contentMode = .scaleToFill
There is a relationship between this behavior and the content mode of scene. You may try:
scene.contentMode = .scaleToFill

Resizing UIView in runtime

I have a UIView that's centered vertically and horizontally in my storyboard and has a fixed height (100) and width (300). Now I want that uiview to be resized (100, 100) during runtime.
I have tried this so far but nothing worked.
let cgRect = CGRect(x: 0, y: 0, width: 100, height: 100)
sampleView.draw(cgRect)
and
sampleView.frame = CGRect(x: 0, y: 0, width: 100, height: 100)
and
sampleView.frame.size.height = 100
sampleView.frame.size.width = 100
Hook the width and height constraints as IBOutlet and in
#IBAction func btnClicked(_ sender: Any) {
self.widthCon.constant = 100
self.heightCon.constant = 100
self.view.layoutIfNeeded()
}
To change the constraints during runtime via a button press you need to add it in an IBAction. In order to animate the change rather than it just jump, put the code in the UIView animation method:
#IBAction func buttonPressed() {
self.widthCon.constant = 100
self.heightCon.constant = 100
UIView.animate(withDuration: 0.3, animations: {
self.view.layoutIfNeeded()
})
}
You can do this:
override func layoutSubviews() {
super.layoutSubviews()
// set hight and width by constraints.
}
or:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
// set hight and width by constraints.
}

UIView.animate problems with view frame

I'm struggling trying to make an animation. This is what's happening: video
The back card was supposed to do the same animation as in the beginning of the video, but it's doing a completely different thing. I'm checking the UIView.frame at the beginning of the animation and it's the same as the first time the card enters, but obviously something is wrong... Here is the code:
func cardIn() {
let xPosition = (self.darkCardView?.frame.origin.x)! - 300
let yPosition = (self.darkCardView?.frame.origin.y)!
self.initialPos = self.darkCardView.frame
let height = self.darkCardView?.frame.size.height
let width = self.darkCardView?.frame.size.width
self.darkCardView?.transform = (self.darkCardImageView?.transform.rotated(by: CGFloat(Double.pi/4)))!
UIView.animate(withDuration: 1.1, animations: {
self.darkCardView?.frame = CGRect(x: xPosition, y: yPosition, width: width!, height: height!)
self.darkCardView?.transform = (self.darkCardImageView?.transform.rotated(by: CGFloat(0)))!
})
}
func cardOut() {
let xPosition = (self.darkCardView?.frame.origin.x)! - 600
let yPosition = (self.darkCardView?.frame.origin.y)!
let height = self.darkCardView?.frame.size.height
let width = self.darkCardView?.frame.size.width
self.darkCardView?.transform = (self.darkCardImageView?.transform.rotated(by: CGFloat(0)))!
UIView.animate(withDuration: 1.0, delay: 0, options: .allowAnimatedContent, animations: {
self.darkCardView?.frame = CGRect(x: xPosition, y: yPosition, width: width!, height: height!)
self.darkCardView?.transform = (self.darkCardImageView?.transform.rotated(by: CGFloat(-Double.pi/4)))!
}) { (true) in
self.darkCardView?.transform = (self.darkCardImageView?.transform.rotated(by: CGFloat(0)))!
self.darkCardView?.frame = self.initialPos
self.cardIn()
}
}
Does somebody know how can I repeat the same animation that's in the beginning of the video after cardOut function is called?
Given that we do not know where/when you are calling the above two functions I can only take a guess at what is happening.
So you have an animate in and an animate out, but what about a reset function?
Timeline:
Animate the card in.
...
Eventually animate the card out
...
Reset the cards location to off screen to the right (location before animating in for the first time)
...
animate the card in
...
Repeat, ect....
I managed to solve the animation problem by calling viewDidLoad() instead of calling self.cardIn() in the end o cardOut completion block. But I don't want to depend on calling viewDidLoad every time a new card has to enter the screen...
Also, I didn't set any properties of the back card in viewDidLoad. I only have it in the .xib file. Any ideas?
Note that if you set a view's transform to anything other than the identity transform then the view's frame rect becomes "undefined". You can neither set it to a new value nor read it's value reliably.
You should change your code to use the view's center property if you're changing the transform.
As Jerland points out in their post, you also probably need to reset your back card to it's starting position before beginning the next animation cycle. (Set it to hidden, put it's center back to the starting position and set it's transform back to the identity transform.
EDIT:
This code:
UIView.animate(withDuration: 1.0, delay: 0, options: .allowAnimatedContent, animations: {
self.darkCardView?.frame = CGRect(x: xPosition, y: yPosition, width: width!, height: height!)
self.darkCardView?.transform = (self.darkCardImageView?.transform.rotated(by: CGFloat(-Double.pi/4)))!
}) { (true) in
self.darkCardView?.transform = (self.darkCardImageView?.transform.rotated(by: CGFloat(0)))!
self.darkCardView?.frame = self.initialPos
self.cardIn()
}
Does not set the transform to identity. Try changing that line to
self.darkCardView?.transform = .identity

Create UIButtons dynamically with for loop in Swift

I am trying to add buttons dynamically to a scroll view after pressing another button.
I created a custom UIView which I want to add to the scroll view.
Below you can find the code how I am trying to do it:
var buttonY: CGFloat = 0 // our Starting Offset, could be 0
for _ in audioCommentsArray {
UIView.animateWithDuration(0.15, animations: {
let commentView = AudioCommentView(frame: CGRectZero)
commentView.frame = CGRect(x: 0, y: buttonY, width: 75, height: 75)
buttonY = buttonY + 75 // we are going to space these UIButtons 75px apart
commentView.alpha = 1.0
audioCommentsListScrollView.addSubview(commentView)
})
}
I want to add these commentView's using a simple animation. However only the last commentView is added correctly to the scrollView whereas the above views are added like this:
Only the background of the commentView is shown whereas the other elements are not visible.
Does anyone have an idea what I might be missing? Adding views using a for loop shouldn't be complicated as I have done this many times before but this time I seem to miss something?
Thank you in advance!
The UIView animations are overlapping and interfering with each other as you process them through the loop. Instead, you should chain them so that the next animation does not interfere with the other. Delete the loop. Then call the animations one after the other in the completion handler. You can call it recursively to ensure each button is animated.
var count = 0
func startButtonsAnimation() {
UIView.animateWithDuration(0.15, animations: {
let commentView = AudioCommentView(frame: CGRectZero)
commentView.frame = CGRect(x: 0, y: buttonY, width: 75, height: 75)
buttonY = buttonY + 75 // we are going to space these UIButtons 75px apart
commentView.alpha = 1.0
audioCommentsListScrollView.addSubview(commentView)
}, completion: { (value: Bool) in
if self.count < self.audioCommentsArray.count {
self.count += 1
self.startButtonsAnimation()
}
})

iOS: UIView animation ignores delay

I have a problem with very simple UIView animation:
- (void)showView:(CGFloat)delay
{
[UIView animateWithDuration:1.0 delay:delay options:0 animations:^{
// self.alpha = 0.5; // Works OK with delay
self.frame = CGRectMake(100, 100, 100, 30); // delay is ignored
} completion:nil];
}
delay could be set to 1000 and still view is animated immediately. But somehow it works fine with alpha (without setting frame).
The frame is probably being set for you, perhaps by layout being performed by the superview, or this view itself. You haven't said how you're adding this view or what it does so its difficult to give specific advice, but in general terms:
Don't let a view be in control of its own frame - this is the superview's or view controllers responsibility. A view can have a preferred size, but the rest is outside.
it's usually better to animate bounds and / or centre instead of frame, particularly if you're ignoring the first point.
For presentation type animations, it may be better to animate the transform instead - either translation or scale, depending what your animation is.
[self performSelector:#selector(animateMe) withObject:nil afterDelay:1000];
If self is viewController then you should use self.view.frame instead of self.frame. If self is view then you should use self.frame.
- (void)animateMe {
[UIView animateWithDuration:1.0f animations:^{
// self.alpha = 0.5; // Works OK with delay
self.frame = CGRectMake(100, 100, 100, 30); // delay is ignored
} completion:^(BOOL finished) {
NSLog(#"Done");
}];
}
This answer uses Swift, specifically version 4.2, and is tested in Xcode 10 playgrounds. This is actually my first try animating, and I'm very green with iOS in general, but this is how I solved the problem of delay not being observed.
import UIKit
import PlaygroundSupport
// Set up the main view
let liveViewFrame = CGRect(x: 0, y: 0, width: 500, height: 500)
let liveView = UIView(frame: liveViewFrame)
liveView.backgroundColor = .white
// Show the live view using the main view
PlaygroundPage.current.liveView = liveView
// Set up a smaller view for animation
let smallFrame = CGRect(x: 0, y: 0, width: 100, height: 100)
let square = UIView(frame: smallFrame)
square.backgroundColor = .purple
// Add the square to the live view ...
liveView.addSubview(square)
// ... and create an alias for it to allow delays to work.
// Calling square directly won't allow delays,
// giving it or the liveView subview an alias directly fails,
// and unwrapping (? or !) appears to fail as well.
// So, just call like `alias?.backgroundColor` ...
let squareView = square.superview?.subviews[0]
UIView.animate(withDuration: 1.5, delay: 1.0, options: [.repeat], animations: {
//square.backgroundColor = .orange // this doesn't respect the delay arg
squareView?.backgroundColor = .orange
squareView?.frame = CGRect(x: 150, y: 150, width: 200, height: 200)
squareView?.transform = CGAffineTransform(rotationAngle: CGFloat(Double.pi))
}, completion: nil)
This may not answer the question directly, but I believe similar logic could be applied for a workaround in related cases.
Instead of
UIView.animate(
withDuration: 0.2,
delay: 1,
options: .curveLinear,
animations: {
self.someButton.isHidden = false
})
}
Do just:
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
UIView.animate(withDuration: 0.2,
delay: 0,
options: .curveLinear,
animations: {
self.someButton.isHidden = false
})
}

Resources