Change UIImageView image whilst animating constraints - swift

When attempting to change a UIImageView's image whilst animating its constraints, the image seems to momentarily glitch - the whole image rotates by 90 degrees. It then corrects itself once the animation finishes.
self.imageViewContainerTopConstraint.constant = 100
self.imageViewContainerLeadingConstraint.constant = 50
self.imageViewContainerTrailingConstraint.constant = 50
self.imageViewContainerBottomConstraint.constant = 100
self.view.setNeedsLayout()
UIView.animate(withDuration: 1.0, delay: 0, options: [.curveEaseOut], animations: {
self.view.layoutIfNeeded()
}, completion: nil)
UIView.transition(with: self.imageView, duration: 0.5, options: [.transitionCrossDissolve], animations: {
self.imageView.image = UIImage(named: "test")
}, completion: nil)
How can I achieve the animation of both the constraints and image without it glitching?
Before:
During animation:

Related

Fade in chain animations don't work: only view fades in

I'm trying to use a fade in animation chain to fade in my objects in order. First I make the view fade in, then the rest of the labels should follow suit. The labels are in a stack view, which has an alpha of 1.0. I made sure that all of the other alpha values started at zero. Here is my function:
func fadeIn() {
UIView.animate(withDuration: 0.5, delay: 0.0, options: UIView.AnimationOptions.curveEaseIn, animations: {
self.view.alpha = 1.0
}, completion: { finished in
if finished {
UIView.animate(withDuration: 0.5, delay: 0.0, options: UIView.AnimationOptions.curveEaseIn, animations: {
self.gameOverLabel.alpha = 1.0
}, completion: { finished in
if finished {
UIView.animate(withDuration: 0.5, delay: 0.0, options: UIView.AnimationOptions.curveEaseIn, animations: {
self.scoreLabel.alpha = 1.0
}, completion: { finished in
if finished {
UIView.animate(withDuration: 0.5, delay: 0.0, options: UIView.AnimationOptions.curveEaseIn, animations: {
self.highScoreLabel.alpha = 1.0
}, completion: { finished in
if finished {
UIView.animate(withDuration: 0.5, delay: 0.0, options: UIView.AnimationOptions.curveEaseIn, animations: {
self.restartButton.alpha = 1.0
}, completion: nil)
//breakpoint set here
}
})
}
})
}
})
}
})
}
I set a breakpoint after all of the animations were completed(as shown above) and all alpha values were 1.0. However, the device just gives me a blank, white screen. What is going on?
P.S. The function is called immediately after this view controller is presented:
view.present(vc, animated: false, completion: {
vc.fadeIn()
})
Try adding the .layoutSubviews option to your animations. Like this:
UIView.animate(withDuration: 1.0, delay: 0.0, options: UIView.AnimationOptions.layoutSubviews, animations: {
// animate here
})
From apple's docs this option does this:
Lay out subviews at commit time so that they are animated along with their parent.
Might be that the subviews aren't laid out yet at commit time due to being presented.

i am trying to animate my unbutton with a constraint and the animation is not working

Can some one please tell me why this is not animating? I feel that the code is all perfect but the animate function is not working. The button is jumping instantly from side to side but like i said it isn't animating...
func moveResetButton( constraint : NSLayoutConstraint, isOffScreen : Bool) {
if isOffScreen {
//animate the button onto the screen
UIView.animate(withDuration: 3.0, delay: 0.0, options: .curveLinear, animations: {
constraint.constant += 120
}, completion: nil)
} else {
//animate the button off of the screen
UIView.animate(withDuration: 1.0, delay: 0.0, options: .curveLinear, animations: {
constraint.constant -= 120
}, completion: nil)
}
}
i figured it out. just need to add self.view.layoutIfNeeded() to the code under the constraint change. worked smoothly.

Animate video UIView to go full screen like YouTube App

I have a view controller that has a UIView that I call playerView. In playerView, I use an AVLayer and AVPlayerLayer to show the user a video. Image the Youtube app when you first click on any video.
How do I manipulate the frame of this playerView so that it can take up the entire screen. (Go full screen)
This is the current frame:
//16 x 9 is the aspect ratio of all HD videos
let height = view.frame.width * 9 / 16
let playerFrame = CGRect(x: 0, y: 0, width: view.frame.width, height: height)
playerView.frame = videoPlayerFrame
I was testing around in the YouTube app and their app only supports Portrait orientation (just like mine) due to how the animation of a video going full screen looks. How can I do this at the the tap of a button and even further when the user holds the device horizontally?
UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseOut, animations: {
self.playerView.frame = ?
}, completion: nil)
Edit: Tried this but it doesn't really work how I want it... it doesn't take up the whole screen
UIView.animate(withDuration: 0.8, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseOut, animations: {
self.playerView.frame = CGRect(x: 0, y: 0, width: self.view.frame.height, height: self.view.frame.width)
self.playerView.transform = CGAffineTransform(rotationAngle: CGFloat(Double.pi / 2))
}, completion: nil)
I figured it out.
I create a global variable called isExpanded that will be set to true when we're in full screen mode. Also, I create a CGPoint variable called videoPlayerViewCenter so I can save the center before going full screen so I can set it again when going back to small screen.
This is the code that gets called when the user wants to go full screen
func fullScreenTapped(sender: vVideoPlayer) {
if !isExpanded {
//Expand the video
UIView.animate(withDuration: 0.8, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseOut, animations: {
self.videoPlayerViewCenter = self.videoPlayerView.center
self.videoPlayerView.frame = CGRect(x: 0, y: 0, width: self.view.frame.height, height: self.view.frame.width)
self.videoPlayerView.center = self.view.center
self.videoPlayerView.transform = CGAffineTransform(rotationAngle: CGFloat(Double.pi / 2))
self.videoPlayerView.layoutSubviews()
}, completion: nil)
} else {
//Shrink the video again
UIView.animate(withDuration: 0.8, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseOut, animations: {
//16 x 9 is the aspect ratio of all HD videos
self.videoPlayerView.transform = CGAffineTransform.identity
self.videoPlayerView.center = self.videoPlayerViewCenter
let height = self.view.frame.width * 9 / 16
let videoPlayerFrame = CGRect(x: 0, y: 0, width: self.view.frame.width, height: height)
self.videoPlayerView.frame = videoPlayerFrame
self.videoPlayerView.layoutSubviews()
}, completion: nil)
}
isExpanded = !isExpanded
}
To expand the video, I save the center in my global CGPoint variable and then set the frame. Then, I set the center and do a 90 degree turn using CGAffineTransform.
To shrink the video again, I basically set the settings to how they were before. (The order in which things are done is very important)
Another thing that's also very important is to override your layoutSubviews method in your videoPlayerView to be like this:
override func layoutSubviews() {
super.layoutSubviews()
self.playerLayer?.frame = self.bounds
}
This will make it so your playerLayer adjusts its frame as the view is getting bigger.
I know is too late but maybe someone else can find this code helpful.
I use to use Auto layout constraints in my views so animating must have a different approach.
func setMapFullScreen(_ fullscreen: Bool) {
if fullscreen {
// Set the full screen constraints
self.setFullScreenLayoutConstraints()
// Hide the navigation bar
self.navigationController?.setNavigationBarHidden(true, animated: true)
} else {
// Set small screen constraints
self.setSmallScreenLayoutConstraints()
// Show the navigation bar
self.navigationController?.setNavigationBarHidden(false, animated: true)
}
// Animate to full screen
UIView.animate(withDuration: 0.8, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseOut, animations: {
self.view.layoutIfNeeded()
}) { (finished) in
// ...
}
In my case a have I MKMapView and I want to tap on it in order to see the map fullscreen (and back).
This is the function that toogle the state.
As you can see, in the UIView.animate call, I animate only the self.view.layoutIfNeeded()
I also hide the navigation bar cause I have one ☺
Here my setSmallScreenLayoutConstraints and setFullScreenLayoutConstraints functions using SnapKit framework
func setSmallScreenLayoutConstraints() {
self.mapView.snp.remakeConstraints { (make) -> Void in
make.top.equalTo(self.mapLabel.snp.bottom).offset(8)
make.left.equalTo(self.view).offset(0)
make.right.equalTo(self.view).offset(0)
make.height.equalTo(150)
}
}
func setFullScreenLayoutConstraints() {
self.mapView.snp.remakeConstraints { (make) -> Void in
make.top.equalTo(self.view).offset(0)
make.left.equalTo(self.view).offset(0)
make.right.equalTo(self.view).offset(0)
make.bottom.equalTo(self.view).offset(0)
}
}
This is the result
Enjoy!!
As I said it is not an answer for your question. It is an idea and hope it helps you:
var rectangleView:UIView!
override func viewDidLoad() {
super.viewDidLoad()
// Tap Gesture Recognizer for growing the rectangleView
let tapForward = UITapGestureRecognizer.init(target: self, action: #selector(self.tapForward(tap:)))
tapForward.numberOfTapsRequired = 1
// Configure the rectangleView
self.rectangleView = UIView.init(frame: CGRect.init(x: 0, y: 0, width: self.view.bounds.width, height: self.view.bounds.height))
self.rectangleView.transform = CGAffineTransform.init(scaleX: 0.65, y: 0.25)
self.rectangleView.backgroundColor = UIColor.red
self.rectangleView.addGestureRecognizer(tapForward)
self.view.addSubview(rectangleView)
}
And this going to be our tapForward(tap:) function:
func tapForward(tap:UITapGestureRecognizer) {
UIView.animate(withDuration: 0.5, delay: 0.0, usingSpringWithDamping: 0.7, initialSpringVelocity: 0.8, options: .curveEaseInOut, animations: {
self.rectangleView.transform = CGAffineTransform.identity
}) { (completed:Bool) in
// Completion block
}
}

UIViewAnimationOptions.CurveEaseIn not working inside coordinated animation

I am in the middle of developing a tvOS application.
In my application I have a UICollectionView. On focus I am using a coordinated animation. Inside that animation I have this code:
UIView.animateWithDuration(1.0, delay: 0.0, options: UIViewAnimationOptions.CurveEaseIn, animations: {
self.bg.image = finalImage
self.bg.alpha = 1.0
}, completion: nil)
But it seems like its not using the curveEaseIn animation option.
Its changing the alpha bluntly, which is odd. Where I am going wrong?
Sorry. Forgot to post the answer.
I did animating the imageview in the completion of the coordinated animation like this
self.bg.alpha = 0.0
coordinator.addCoordinatedAnimations({ //code for coordinated animation}, completion: { _ in
UIView.animateWithDuration(1.0, delay: 0.0, options: UIViewAnimationOptions.CurveEaseIn, animations: {
self.bg.image = self.finalImage
self.bg.alpha = 1.0
}, completion: nil)})
What is the desired effect? Are you trying to cross fade bg.image with a new image? Have you tried changing the duration to something higher, for example 10.0, to see if the animation is perhaps happening too fast?
Here's an example of what you may be looking for:
let myImageView = UIImageView()
let myFinalImage = UIImage(named:"FinalImage.png")
// Animate alpha
UIView.animateWithDuration(1.0, delay: 0.0, options: .CurveEaseIn, animations: {
print("alpha animation started")
myImageView.alpha = 0.0
}) { (finished) in
print("alpha animation finished")
}
// Animate change of image
UIView.transitionWithView(myImageView, duration: 1.0, options: .TransitionCrossDissolve, animations: {
print("cross dissolve animation started")
myImageView.image = myFinalImage
}) { (finished) in
print("cross dissolve animation finished")
}
In Swift 4 or Swift 5 use: .curveEaseIn
UIView.animate(withDuration: 1.0, delay: 0.0, options: .curveEaseIn, animations: {
...
}) { (finished) in
...
}

Smooth move of the contentoffset UIScrollView Swift

I would like to set the contentOffset of my scrollview programmatically when the contentOffset is between two points (please see the picture below) with Swift.
The problem is, I would like to add a smooth transition for the move, but I didn't find documentation for this. I tried to do a loop in order to decrease gradually the content offset but the result is not so good.
With this example, if the content offset is less than 150 px at the end of the scroll, it goes smoothly (duration of the animation would be 1 sec) to the point with the offset equal to 100. Up to 150 px, it goes to 200px.
If you can provide me directions (documentation or quick example) of what to do it would be great :) Thank you !
You can use UIView.animations
func goToPoint() {
dispatch_async(dispatch_get_main_queue()) {
UIView.animateWithDuration(2, delay: 0, options: UIViewAnimationOptions.CurveLinear, animations: {
self.scrollView.contentOffset.x = 200
}, completion: nil)
}
}
Swift version
DispatchQueue.main.async {
UIView.animate(withDuration: 0.2, delay: 0, options: UIViewAnimationOptions.curveEaseOut, animations: {
self.myScrollView.contentOffset.x = CGFloat(startingPointForView)
}, completion: nil)
}
Swift 4
DispatchQueue.main.async {
UIView.animate(withDuration: 1, delay: 0, options: UIView.AnimationOptions.curveLinear, animations: {
self.scrollView.contentOffset.x = 200
}, completion: nil)
}
Here is the Swift 3 version of fatihyildizhan's code.
DispatchQueue.main.async {
UIView.animate(withDuration: 0.2, delay: 0, options: UIViewAnimationOptions.curveEaseOut, animations: {
self.myScrollView.contentOffset.x = CGFloat(startingPointForView)
}, completion: nil)
}
Now you can simply call the method setContentOffset(_ contentOffset: CGPoint, animated: Bool) instead of the previous workarounds calling a messy animation block. See:
x = CGFloat(startingPointForView)
myScrollView.setContentOffset(CGPoint(x: x, y: 0), animated: true)
Hope it helps.
Swift 4:
DispatchQueue.main.async {
UIView.animate(withDuration: 1, delay: 0, options: UIView.AnimationOptions.curveLinear, animations: {
self.scrollView.contentOffset.x = 200
}, completion: nil)
}