Swift - How to Animate Images

Swift - How to animate Images?

[UIImage imageNamed (strImageName)]

This not swift code. In swift it would be

UIImage(named:strImageName)  

Modified code:

var imgListArray :NSMutableArray = []
for countValue in 1...11
{

var strImageName : String = "c\(countValue).png"
var image = UIImage(named:strImageName)
imgListArray .addObject(image)
}

self.imageView.animationImages = imgListArray;
self.imageView.animationDuration = 1.0
self.imageView.startAnimating()

How to animate a sequence of images using SwiftUI?

The accepted answer works very well, with the unfortunate issues mentioned by bhagyash ingale which makes it very hard to use. It would be useful if the specific methods of Image could be reused via protocols or something. I have a very poor and maybe huge cannon for a fly solution for this, maybe it'll be easier in time but for now...

class LoadingTimer {

let publisher = Timer.publish(every: 0.1, on: .main, in: .default)
private var timerCancellable: Cancellable?

func start() {
self.timerCancellable = publisher.connect()
}

func cancel() {
self.timerCancellable?.cancel()
}
}
struct LoadingView: View {

@State private var index = 0

private let images = (0...7).map { UIImage(named: "Image-\($0).jpg")! }
private var timer = LoadingTimer()

var body: some View {

return Image(uiImage: images[index])
.resizable()
.frame(width: 100, height: 100, alignment: .center)
.onReceive(
timer.publisher,
perform: { _ in
self.index = self.index + 1
if self.index >= 7 { self.index = 0 }
}
)
.onAppear { self.timer.start() }
.onDisappear { self.timer.cancel() }
}
}

I don't like this but it gets the job done and relies on a Image.

How to animate images in SwiftUI, to play a frame animation

I solved this using UIViewRepresentable protocol. Here I returned a UIView with the ImageView as it's subview. This gave me more control over the child's size, etc.

import SwiftUI

var images : [UIImage]! = [UIImage(named: "pushup001")!, UIImage(named: "pushup002")!]

let animatedImage = UIImage.animatedImage(with: images, duration: 0.5)

struct workoutAnimation: UIViewRepresentable {

func makeUIView(context: Self.Context) -> UIView {
let someView = UIView(frame: CGRect(x: 0, y: 0, width: 400, height: 400))
let someImage = UIImageView(frame: CGRect(x: 20, y: 100, width: 360, height: 180))
someImage.clipsToBounds = true
someImage.layer.cornerRadius = 20
someImage.autoresizesSubviews = true
someImage.contentMode = UIView.ContentMode.scaleAspectFill
someImage.image = animatedImage
someView.addSubview(someImage)
return someView
}

func updateUIView(_ uiView: UIView, context: UIViewRepresentableContext<workoutAnimation>) {

}
}

struct WorkoutView: View {
var body: some View {
VStack (alignment: HorizontalAlignment.center, spacing: 10) {
workoutAnimation()
Text("zzzz")
}
}
}

How to animate the change of image in an UIImageView?

I am not sure if you can animate UIViews with fade effect as it seems all supported view transitions are defined in UIViewAnimationTransition enumeration. Fading effect can be achieved using CoreAnimation. Sample example for this approach:

#import <QuartzCore/QuartzCore.h>
...
imageView.image = [UIImage imageNamed:(i % 2) ? @"3.jpg" : @"4.jpg"];

CATransition *transition = [CATransition animation];
transition.duration = 1.0f;
transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
transition.type = kCATransitionFade;

[imageView.layer addAnimation:transition forKey:nil];

Swift image appear animation from bottom of its frame to top (wipe animation)

There are various ways to do a "soft wipe" animation... here is one.

We can add a gradient layer mask and then animate the gradient.

When using a layer mask, clear (alpha 0) areas hide what's under the mask, and opaque (alpha 1) areas show what's under the mask. This includes parts of the mask that are translucent - alpha 0.5 for example - so the content becomes "translucent".

To get a "soft wipe" we want the gradient to use only a portion of the frame, so if we set the starting Y point to 0.5 (halfway down), for example, we can set the ending Y point to 0.6 ... this would give us a horizontal "gradient band".

So, a yellow-to-red gradient at 0.5 to 0.6 would look like this:

Sample Image

If we set the starting Y to 1.0 and the ending Y to 1.1, the gradient band will start "off the bottom of the view" ... and we can animate it up until it's "off the top of the view" (note that converted to gif loses some of the smooth gradient property):

Sample Image

Now, if we instead use clear-to-red and set it as a layer mask, using the same animation it will "soft wipe" the view, it will look something like this:

Sample Image

Sample Image

Can't get the animated gif small enough to post here, so here's a link to the animation: https://i.imgur.com/jo2DH3Z.mp4

Here's some sample code for you to play with. Lots of comments in there:

class AnimatedGradientImageView: UIImageView {

let maskLayer = CAGradientLayer()

override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() -> Void {

// vertical gradient
// start at bottom
maskLayer.startPoint = CGPoint(x: 0.5, y: 1.0)

// to bottom + 1/10th
maskLayer.endPoint = CGPoint(x: 0.5, y: 1.1)

// use "if true" to see the layer itself
// use "if false" to see the image reveal
if false {
// yellow to red
maskLayer.colors = [UIColor.yellow.cgColor, UIColor.red.cgColor]
// add it as a sublayer
layer.addSublayer(maskLayer)
} else {
// clear to red
maskLayer.colors = [UIColor.clear.cgColor, UIColor.red.cgColor]
// set the mask
layer.mask = maskLayer
}

}

override func layoutSubviews() {
super.layoutSubviews()
// set mask layer frame
maskLayer.frame = bounds
}

func reveal() -> Void {

let anim1: CABasicAnimation = CABasicAnimation(keyPath: "startPoint.y")

// anim1 animates the gradient start point Y
// to -0.1 (1/10th above the top of the view)
anim1.toValue = -0.1
anim1.duration = 1.0
anim1.isRemovedOnCompletion = false
anim1.fillMode = .forwards

let anim2: CABasicAnimation = CABasicAnimation(keyPath: "endPoint.y")

// anim2 animates the gradient end point Y
// to 0.0 (the top of the view)
anim2.toValue = 0.0
anim2.duration = 1.0
anim2.isRemovedOnCompletion = false
anim2.fillMode = .forwards

maskLayer.add(anim1, forKey: nil)
maskLayer.add(anim2, forKey: nil)

}

}

class AnimatedGradientViewController: UIViewController {

let testImageView = AnimatedGradientImageView(frame: .zero)

override func viewDidLoad() {
super.viewDidLoad()

// replace with your image name
guard let img = UIImage(named: "sampleImage") else {
fatalError("Could not load image!!!")
}
// set the image
testImageView.image = img

testImageView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(testImageView)

// respect safe area
let g = view.safeAreaLayoutGuide

NSLayoutConstraint.activate([

// size: 240 x 240
testImageView.widthAnchor.constraint(equalToConstant: 240.0),
testImageView.heightAnchor.constraint(equalToConstant: 240.0),
// centered
testImageView.centerXAnchor.constraint(equalTo: g.centerXAnchor),
testImageView.centerYAnchor.constraint(equalTo: g.centerYAnchor),

])

// tap anywhere in the view
let t = UITapGestureRecognizer(target: self, action: #selector(gotTap(_:)))
view.addGestureRecognizer(t)
}

@objc func gotTap(_ g: UITapGestureRecognizer) -> Void {
testImageView.reveal()
}

}

How to show image with animation in UIImageView in swift

It almost seems like there is an internal issue with the animation loop being access from within this delegate method. I looked into a few options and the solution below dispatch_after() is the only option that worked for me for all my test cases.

Note: I'm not saying this is pretty or the final answer. Just reporting my findings and what works for me. The UIView.animateWithDuration(1, delay:0, options:[.ShowHideTransitionViews]... solution only worked for me on iOS8 not iOS9. YMMV

func imagePickerController(_picker: UIImagePickerController,
didFinishPickingMediaWithInfo info: [String : AnyObject]){
_picker.dismissViewControllerAnimated(true, completion: nil)
self.imageView.image = info[UIImagePickerControllerOriginalImage] as? UIImage
self.imageView.alpha = 0

let shortDelay = dispatch_time(DISPATCH_TIME_NOW, Int64(1_000))
dispatch_after(shortDelay, dispatch_get_main_queue()) {
UIView.animateWithDuration(1.0) {
self.imageView.alpha = 1.0
}
}

// // Does not work. No animation of alpha.
// UIView.animateWithDuration(1.0) {
// self.imageView.alpha = 1.0
// }

// // Does not work. No animation of alpha.
// UIView.animateWithDuration(1.0) {
// self.view.layoutIfNeeded()
// self.imageView.alpha = 1.0
// }
// // Does not work. No animation of alpha.
// UIView.animateWithDuration(1, delay:1, options:[], animations: {
// self.imageView.alpha = 1
// }, completion: nil)

// // Works iOS8 Simulator. Does not work iOS9 Simulator (9.1) or device (9.2.1).
// UIView.animateWithDuration(1, delay:0, options:[.ShowHideTransitionViews], animations: {
// self.imageView.alpha = 1
// }, completion: nil)

// // Old school, does not work
// UIView.commitAnimations()
// UIView.beginAnimations("alphaAni", context: nil)
// self.imageView.alpha = 1
// UIView.commitAnimations()

}

SwiftUI: Unable to animate images

Here is possible approach. I simplified it for a demo purpose and less posting code, but idea should be clear and easy transferrable to your real code

Result demo (really it is much fluent than on gif):

demo

Modified model

enum SpeakerSymbol: Int, CaseIterable { // Inherited from Int for convenient below

case speakerEmpty, speaker1, speaker2, speaker3

var image: some View {
var name: String
switch self {
case .speakerEmpty: name = "speaker.slash.fill"
case .speaker1: name = "speaker.1.fill"
case .speaker2: name = "speaker.2.fill"
case .speaker3: name = "speaker.3.fill"
}
return Image(systemName: name).font(.largeTitle)
}
}

Animatable modifier for SpeakerSymbol, required to let SwiftUI animation know that changed SpeakerSymbol value is able to animate

struct SpeakerModifier: AnimatableModifier {
var symbol: SpeakerSymbol

init(symbol: SpeakerSymbol) {
self.symbol = symbol
self.animating = Double(symbol.rawValue) // enum to Double
}

private var animating: Double // Double supports Animatable
var animatableData: Double { // required part of Animatable protocol
get { animating }
set { animating = newValue }
}

func body(content: Content) -> some View {
return SpeakerSymbol(rawValue: Int(animating))!.image // Double -> enum
}
}

Demo of usage

struct TestSpeakerModifier: View {
@State private var speaker: SpeakerSymbol = .speakerEmpty

var body: some View {
VStack {
Color.clear // << just holder area
.modifier(SpeakerModifier(symbol: speaker))
.frame(width: 60, height: 60, alignment: .leading)
Divider()
Button("Toggle") {
withAnimation { // animates between enum states
self.speaker =
(self.speaker == .speakerEmpty ? .speaker3 : .speakerEmpty)
}
}
}
}
}

Swift - animating UIImageView image

You don't need to use CAffineTranform inside animateWithDuration, you can simply define the new center like:

let oldCenter = image.center
let newCenter = CGPoint(x: oldCenter.x - 100, y: oldCenter.y)

UIView.animate(withDuration: 1, delay: 0, options: .curveLinear, animations: {
image.center = newCenter
}) { (success: Bool) in
print("Done moving image")
}

Hope it helps.



Related Topics



Leave a reply



Submit