Animated GIF as Background in Swift - ios

I am trying to make the background of my app an animated gif. This is my code by i am getting the error 'exc_bad_instruction' for this line: images.append(UIImage(named: imageNames[i])!)
I don't see what the problem is. Here is the code:
var imageNames = ["tmp-0.gif", "tmp-1.gif", "tmp-2.gif", "tmp-3.gif", "tmp-4.gif", "tmp-5.gif", "tmp-6.gif", "tmp-7.gif", "tmp-8.gif", "tmp-9.gif", "tmp-10.gif", "tmp11.gif", "tmp-12.gif", "tmp-13.gif", "tmp-14.gif", "tmp-15.gif", "tmp-16.gif", "tmp-17.gif", "tmp-18.gif", "tmp-19.gif"]
var images = [UIImage]()
for i in 0..<imageNames.count{
images.append(UIImage(named: imageNames[i])!)
}
theGif.animationImages = images
theGif.animationDuration = 1.0
theGif.startAnimating()

If you're considering using an animated GIF for the background, instead of trying to decouple all of the frames in the GIF and using the animation capabilities of UIImageView, I'd recommend using a proper animated GIF library.
Flipboard's FLAnimatedImage library is apparently one of the more popular ones (I haven't used it personally, so I can't say for sure). It's Objective-C, but it should interoperate with Swift with no issues.

Related

Can memory in my code be better managed when using UIImageView.animationImages and startAnimating()?

I'm declaring some UIImage arrays:
var animationImages1: [UIImage] = []
var animationImages2: [UIImage] = []
I'm using a background thread to load the images:
DispatchQueue.global(qos: .background).async { () -> Void in
self.animationImages1 = self.createImageArray(total: 57, imagePrefix: "animation1")
self.animationImages2 = self.createImageArray(total: 42, imagePrefix: "animation2")
}
The function called above:
var imageArray: [UIImage] = []
for imageCount in 1..<total {
var imageName = String(format: "\(imagePrefix)\(imageCount)")
let image = UIImage(contentsOfFile: Bundle.main.path(forResource: imageName, ofType: "png")!)!
//let image = UIImage(named: imageName)!
imageArray.append(image)
}
return imageArray
Then when I want to animate, I'm calling this function:
func animate(imageView: UIImageView, images: [UIImage], duration: TimeInterval) {
imageView.animationImages = images
imageView.animationDuration = duration
imageView.animationRepeatCount = 1
imageView.startAnimating()
}
I tried doing both theImageView.stopAnimating() and theImageView.animationImages = nil before calling the animation again but didn't notice any improvement to memory management using either.
With UIImage(named:) the images visible in the app start disappearing either partially or completely as memory runs low. With UIImage(contentsOfFile:) the app promptly crashes once memory runs low.
To note: I tried UIImage(named:) with the images in the Assets catalog, and then switched to UIImage(contentsOfFile:) with the images dragged in to the project outside of Assets.xcassets
Is it possible to use this function of UIImageView for longer animations (2-5 seconds: 40-150 pngs) with a file size of about 450k each, or is it too much a memory strain regardless of how you go about it?
It currently runs without an issue on a newer iPad Pro, and using Xcode's simulator it runs well (but eats a lot of memory) on all device sizes. On an iPhone X and an iPhone 8 Plus, it runs out of memory pretty early on - after playing through 5 to 10 animations or so.
Am I missing something, is it not possible, or do I need to do further research on ways to keep memory in check while running these large UIImage arrays through startAnimating()?
Memory usage is not going down. I must be caching this somewhere...
Thanks for any help!
I created a new Xcode project simplified to focus just on this issue, and was given a correct answer by #Stephan Schlecht here.
Although the memory hit doesn't occur when assigning the images to an array, and the memory hit only happens once the animation is played, still the only way to reclaim the memory seems to be removing all references to the image array by setting both the variable array and the animationImages property on the UIImageView to a blank array.
So in the particular example given in this question:
animationImages1 = []
imageView.animationImages = []

How do I get the current image of an animated GIF image?

I'm using SwiftGif framework to animate a GIF I made, problem is I need to know which image is currently displaying.
The extension is using UIImage.animatedImage(with images: [UIImage], duration: TimeInterval) -> UIImage? to display the animated image.
Here is how I load my gif :
spikes.loadGif(name: "Trap")
and here is how I get the image I'd like to compare it to :
spikes.image.images[2]
I know those are weak explanations but I can't find any more relevant informations, however I can give you more informations if it can help you.
If you know any other solution that could involve me using another framework I would take it too.
If someone ever needs an answer as I did, I found a way to do it.
Load GIF with the extension's loadGif() method
Since the images that are composing the GIF are now stored in spikes.image.images, I store those images in another var
Set spikes.image = nil to cancel animations generated from loadGif() method
Make a timer that keeps changing the actual image of the view
Here's how it looks like :
spikes.loadGif(name: "Trap", completion: { _ in
self.spikesImages = self.spikes.image?.images
self.spikes.image = nil
self.spikes.image = self.spikesImages.first?.img
self.animateSpikes()
})
func animateSpikes() {
Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true, block: { _ in
if let currentImage = self.spikesImages.first {
self.spikes.image = currentImage
self.spikesImages.append(currentImage)
self.spikesImages.remove(at: 0)
}
})
}
I'm pretty sure this is not the best way to do it but it works

iOS: making video file from many images generated by code

I need to make a video file from thousands of images generated by code.
I cannot put those images into an array because they are too many--a 30second-long video would consist of 1800 imgae frames. And I don't get all images at once.
To get each image, it first triggers Javascript fucntion in WebView asking if a video frame,which is UIImage, should be generated. if Yes, my code makes UIImage for a video frame one at a time. and the app does other things and at some point it asks webview again for a permission to generate another image. it does this for a thousand times and more.
If Delegate gets a message that says No, the last image was a final video frame so a complete video file should be made at this point and saved to Document directory.
How should I do this? Objective C solutions would be acceptable too. thank you in advance
//ask webview if another video frame should be made
func askWebView() {
webView?.evaluateJavaScript("Ask JS function",completionHandler:nil)
}
Delegate
//Delegate method
func userContentController(userContentController:WKUserContentController,didReceiveScriptMessage message:WKScriptMessage){
let body:String = message.body as! String
if body == "makeAFrame" {
let videoFrame = self.makeImage()
//should asseble video frames
} else {
//No,nomore video frame. write a complete video file to Document directory
}
Generating an UIImage for a video frame
func makeImage() -> UIImage {
//make an image and return it
}

Creating a smooth Gif Segue between view controllers like Vine Sign Up

I have a sign up and sign in view controller and a moving gif background along with other storyboarded UI elements.
Currently I have the gif coding on each view controller obviously making the gif restart as I segue between each view controller and also more concerning having a quick pop of white before it loads whenever moving between the pages.
What can I add to my code to:
1. Prevent the white pop when it segues
2. Swapping between view controllers smoothly with the gif just continuing as if nothing has happened, so it looks like you move the UI not the page.
Look at Vine Sign In / Sign Up for reference as it basically functions in a similar way and want the background gif to function as it does on Vine.
override func viewDidLoad() {
super.viewDidLoad()
let filePath = NSBundle.mainBundle().pathForResource("beachwater", ofType: "gif")
let gif = NSData(contentsOfFile: filePath!)
let webViewBG = UIWebView(frame: self.view.frame)
webViewBG.loadData(gif!, MIMEType: "image/gif", textEncodingName: "UTF-8", baseURL: NSURL(string: "")!)
webViewBG.userInteractionEnabled = false;
webViewBG.layer.zPosition = -3.0;
self.view.addSubview(webViewBG)
As a starting point don't use a UIWebView for this. It is the reason you are seeing a white background just before the gif, the web view has been rendered on screen then shortly after the gif is rendered.
I'd recommend separating out the frames of the gif and loading them into a UIImage (and finally into a UIImageView) using one of the following techniques:
animatedImageNamed:duration:
animatedImageWithImages:duration:
If you don't want to separate the frames, use a gif rendering library such as FLAnimatedImage

UIscrollView image from web in swift

I want to make a gallery with UIScrollview like this.
In case, I want to get the images from web, but with this code my VC is very slow to load
var pageImage: [UIImage] = []
for d in image {
var imgUrl: NSURL = NSURL(string: "https://images.com/\(d.thumbnail)")!
var imgData = NSData(contentsOfURL: imgUrl)
pageImage.append(UIImage(data: imgData!)!)
}
How to make my VC load faster?
Only load the images when you actually need them. Use the scroll view delegate to find out when an image is about to appear on screen.
You should also not fetch the images on the UIThread which will make the app feel unresponsive. I would recommend using a 3rd party lib for loading and displaying the images such as SDWebImage or AFNetworking+UIImageView
as eric said you are loading imae on main thread you can set image in swift with url like imageView.setImageWithURL
or you can load image with background thread

Resources