I am using this guy's Gif class to create a Gif UIImage. https://github.com/mayoff/uiimage-from-animated-gif
Load it with this :
let fileUrl = NSURL(string: "some url" as! String)
let Gif = UIImage.animatedImageWithAnimatedGIFURL(fileUrl!)
imageview.image=Gif
The thing is that it takes time, a lot of time, like 7 seconds till you see the image.
I would like to preload it in some way, or at least get a delegate when finish loading.
What are my options here ?
You can use Grand Central Dispatch (or GCD) to perform this task in the background.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
// This will happen in the background
let fileUrl = NSURL(string: "some url" as! String)
let gif = UIImage.animatedImageWithAnimatedGIFURL(fileUrl!)
dispatch_async(dispatch_get_main_queue()) {
// Always update UI on the main thread
imageview.image = gif
}
}
This will still take however long it takes to load the gif but it won't lock up the UI so the user can do things while the gif loads.
Related
Question
I am trying to download a large image from the web with the code below. I want to show how much download has been done using the progress bar. Currently, it is only possible to know whether the download was successful or not.
func downloadBigImg() -> UIImage? {
let imgURL = URL(string: "https://picsum.photos/1024")!
guard let data = try? Data(contentsOf: imgURL) else { return nil }
let image = UIImage(data: data)
return image
}
What I did
Using asynchronous operation, the progress bar was raised during the download of the image. But that Progress Bar is a fake. It's not really a progress. It just adds a very small number which isn't related to the actual progress. When the image download is completed, it will be changed to 100%.
I have an app that I'm adding sounds to. It has a keypad and when the user taps a button, the number animates to show the user that their press went through.
However, since both are happening on the main thread, adding the following code below, the play() function causes a slight delay in the animation. If the user waits ~2 seconds and taps a keypad number again, they see the delay again. So as long as they're hitting keypad numbers under 2s, they don't see another lag.
I tried wrapping the code below in a DispatchQueue.main.async {} block with no luck.
if let sound = CashSound(rawValue: "buttonPressMelody\(count)"),
let url = Bundle.main.url(forResource: sound.rawValue, withExtension: sound.fileType) {
self.audioPlayer = try? AVAudioPlayer(contentsOf: url)
self.audioPlayer?.prepareToPlay()
self.audioPlayer?.play()
}
How can I play this audio and have the animation run without them interfering and with the audio coinciding with the press?
Thanks
I experienced a rather similar problem in my SwiftUI app, and in my case the solution was in proper resource loading / AVAudioPlayer initialization and preparing.
I use the following function to load audio from disk (inspired by ImageStore class from Apple's Landmarks SwiftUI Tutorial)
final class AudioStore {
typealias Resources = [String:AVAudioPlayer]
fileprivate var audios: Resources = [:]
static var shared = AudioStore()
func audio(with name: String) -> AVAudioPlayer {
let index = guaranteeAudio(for: name)
return audios.values[index]
}
static func loadAudio(with name: String) -> AVAudioPlayer {
guard let url = Bundle.main.url(forResource: name, withExtension: "mp3") else { fatalError("Couldn't find audio \(name).mp3 in main bundle.") }
do { return try AVAudioPlayer(contentsOf: url) }
catch { fatalError("Couldn't load audio \(name).mp3 from main bundle.") }
}
fileprivate func guaranteeAudio(for name: String) -> Resources.Index {
if let index = audios.index(forKey: name) { return index }
audios[name] = AudioStore.loadAudio(with: name)
return audios.index(forKey: name)!
}
}
In the view's init I initialize the player's instance by calling audio(with:) with proper resource name.
In onAppear() I call prepareToPlay() on view's already initialized player with proper optional unwrapping, and finally
I play audio when the gesture fires.
Also, in my case I needed to delay the actual playback by some 0.3 seconds, and for that I despatched it to the global queue. I should stress that the animation with the audio playback was smooth even without dispatching it to the background queue, so I concluded the key was in proper initialization and preparation. To delay the playback, however, you can only utilize the background thread, otherwise you will get the lag.
DispatchQueue.global(qos: .userInitiated).asyncAfter(deadline: .now() + 0.3) {
///your audio.play() analog here
}
Hope that will help some soul out there.
My app gets stuck in the icon for about 4-5 seconds when entering foreground, if the networking is bad. In almost all the main loading pages I am fetching images from a server. I have nothing on applicationDidEnterBackground() nor applicationWillEnterForeground().
Here the code in the Main View controller where I have a collection view with images:
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(homeReuseIdentifier, forIndexPath: indexPath)as! HomeViewCell
// Reload the collection view after insert item at indexPath
cvHome.reloadItemsAtIndexPaths([indexPath])
let entry = dataCell.infoCell[indexPath.row]
cell.backImageView.image = nil
let stringURL = "https://.....\(entry.filename)"
let image = UIImage(named: entry.filename)
cell.backImageView.image = image
if cell.backImageView.image == nil {
if let image = dataCell.cachedImage(stringURL) {
cell.backImageView.image = image
} else {
request = dataCell.getNetworkImage(stringURL) { image in
cell.backImageView.image = image
}
}
}
cell.titleLabel.text = entry.title
cell.authorLabel.text = entry.author
// Fix the collection view cell in size
cell.contentView.frame = cell.bounds
cell.contentView.autoresizingMask = [UIViewAutoresizing.FlexibleWidth, UIViewAutoresizing.FlexibleHeight]
// To edit the cell for deleting
cell.editing = editing
return cell
}
And this is the getNetworkImage function with Alamofire:
func getNetworkImage(urlString: String, completion: (UIImage -> Void)) -> (ImageRequest) {
let queue = decoder.queue.underlyingQueue
let request = Alamofire.request(.GET, urlString)
let imageRequest = ImageRequest(request: request)
imageRequest.request.response(
queue: queue,
responseSerializer: Request.imageResponseSerializer(),
completionHandler: { response in
guard let image = response.result.value else {
return
}
let decodeOperation = self.decodeImage(image) { image in
completion(image)
self.cacheImage(image, urlString: urlString)
}
imageRequest.decodeOperation = decodeOperation
}
)
return imageRequest
}
I'm not sure if you're networking code is bad since you haven't provided any.
My guess is, fetching the data is taking way too long, and it's blocking the UI. So currently, your app is synchronous. So tasks are executed one at a time. the reason your UI is blocked is because it has to wait for the networking to finish.
What you need to do is perform the fetching in the background, and always keep your UI on the main queue.
If you are calling Synchronous you have to wait till the task is finishing.We can start next task once we finish the current task.When you are fetching image from server make sure that whether you are using Synchronous or not.When you don't wait for a second go with Asynchronous.You don't need to wait while fetch the images from server.You can do other tasks when fetching the images from server.
For this GCD is good one to use.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
// Do the back ground process here
......Fetch the image here
dispatch_async(dispatch_get_main_queue(), ^{
// Update the UI here
.....Do your UI design or updation design part here
});
});
Global Queue
Asynchronous task will be performed here.
It does not wait.
Asynchronous function does not block the current thread of execution from proceeding on to the next function.
Main Queue
We must always access UI Kit classes on the main thread.
If you don't want to wait a seconds,go with GCD
I am using Haneke to download and show images asynchronously in CollectionView.
I want to show a smaller thumbnail type of image while the larger image is being downloaded for each cell.
Here's the code:
let imageUrl = NSURL(string: "imgUrlString")
let miniImageUrl = NSURL(string: "miniImgUrlString")
func onSuccess(image: UIImage?) {
cell!.imageView.hnk_setImageFromURL(imageUrl!)
}
cell!.imageView.hnk_setImageFromURL(miniImageUrl!, placeholder: nil, format: nil, failure: onFailure, success: onSuccess)
Also there is preloader image set on ImageView of Cell just after dequeing it.
cell?.imageView.image = UIImage(named: "preloader")
The Problem
The onSuccess handler is called, but the mini image is never visible. The image changes from "preloader" to "image", while I want the mini image to load soon and then change to original image.
What am I missing?
This question already has answers here:
NSTimer change image iPhone Programming
(3 answers)
Closed 7 years ago.
I am trying to continuously update an image on one of my view controllers. The image is from this image URL that is continuously getting updated. If you keep refreshing that URL you can see the image changing. I'm effectively trying to get this to happen in my app.
I've tried loops but the loops freeze the view until it is completed. I'd like it to refresh the image every X seconds.
Right now I can refresh the image using a button:
#IBAction func refreshDogAction(sender:AnyObject) {
let url = NSURL(string: "http://fxp7.sensr.net/latest/e234d1fbd98a6505149d834ba6fd1810a82c0d9b")
let data = NSData(contentsOfURL: url!)
dogImage.image = UIImage(data: data!)
}
I'd like to have this happen automatically without having to click the button.
I suggest using NStimer say somthing like this for every 2 seconds.
var timer = NSTimer.scheduledTimerWithTimeInterval(2, target: self, selector: Selector("loadImage"), userInfo: nil, repeats: true)
func loadImage(){
let url = NSURL(string: "http://fxp7.sensr.net/latest/e234d1fbd98a6505149d834ba6fd1810a82c0d9b")
let data = NSData(contentsOfURL: url!)
dogImage.image = UIImage(data: data!)
}