0

i am trying to load set of images from the server and updating UI with returned images and displaying it by fade animation. it will be repeated forever.

Code snippet:

override func viewDidLoad(animated: Bool) {
        super.viewDidLoad(animated)
        self.loadImages()
}


func loadImages(){

        var urlImage:UIImage?

       //self.array_images contains preloaded images (not UIImage) objects which i get from     different api call.
        if imageCount >= self.array_images!.count{
            imageCount = 0
        }

            var img = self.array_images[imageCount]
            var url = NSURL(string: img.url!)
            var data = NSData(contentsOfURL: url!)
            if (data != nil){
                urlImage = UIImage(data: data!)

                UIView.transitionWithView(self.imageView_Promotion, duration: 1.2, options: UIViewAnimationOptions.TransitionCrossDissolve, animations: {
                    println("Begin Animation")
                    self.imageView_Promotion.image = realImage

                    }, completion:{ finished in
                        println("Completed")
                        self.imageCount++
                        self.loadImages() //another issue: it calls the function more than couple of times
                })

            }
        }else{
            var image:UIImage = UIImage(named: "test_Image.jpg")!
            imageView_Promotion.image = image
        }

The above one hangs the UI. i have tried to call loadImages in dispatch_async and animation in dispatch_main queue but the issue still persist.

let priority = DISPATCH_QUEUE_PRIORITY_BACKGROUND
dispatch_async(dispatch_get_global_queue(priority, 0)) {
 self.loadImages()
}

 dispatch_async(dispatch_get_main_queue(),{
//UIView animation
 })

What is the proper way to handle this.

Thanks

3 Answers 3

1

Thread-safe problem

Basically, UIKit APIs are not thread-safe. It means UIKit APIs should be called only from the main thread (the main queue). But, actually, there are some exceptions. For instance, UIImage +data: is thread-safe API.

Your code includes some unsafe calls.

UIView.transitionWithView(self.imageView_Promotion, duration: 1.2, options: UIViewAnimationOptions.TransitionCrossDissolve, animations: {

I don't think UIView.transitionWithView is thread-safe.

var image:UIImage = UIImage(named: "test_Image.jpg")!

UIImage +named: is not thread-safe, it uses a global cache or so on. According to UIImage init(named:) Discussion.

You can not assume that this method is thread safe.

Block the main thread

If the thread-safe problem was fixed, it would block the main thread.

Your code calls loadImages method, that downloads an image from the server or loads an image from the file system, from the completion block of UIView.transitionWithView. UIView.transitionWithView should be called from the main thread, so the completion block also will be called from the main thread. It means downloading or loading an image on the main thread. It blocks the main thread.

Example code

Thus, loadImages should be like the following.

Swift Playground code:

import UIKit
import XCPlayground

func loadImage(file:String) -> UIImage {
    let path = NSBundle.mainBundle().pathForResource(file, ofType: "")
    let data = NSData(contentsOfFile: path!)
    let image = UIImage(data: data!)
    return image!
}

var index = 0
let files = ["image0.png", "image1.png", "image2.png"]
let placementImage = loadImage(files[0])
let view = UIImageView(image:placementImage)

// loadImages function

func loadImages() {
    let priority = DISPATCH_QUEUE_PRIORITY_BACKGROUND
    dispatch_async(dispatch_get_global_queue(priority, 0)) {
        /*
         * This block will be invoked on the background thread
         * Load an image on the background thread
         * Don't use non-background-thread-safe APIs like UIImage +named:
         */
        if index >= files.count {
            index = 0
        }
        var file = files[index++]
        let image = loadImage(file)

        dispatch_async(dispatch_get_main_queue()) {
            /*
             * This block will be invoked on the main thread
             * It is safe to call any UIKit APIs
             */
            UIView.transitionWithView(view, duration: 1.2, options: UIViewAnimationOptions.TransitionCrossDissolve, animations: {
                /*
                 * This block will be invoked on the main thread
                 * It is safe to call any UIKit APIs
                 */
                view.image = image
            }, completion:{ finished in
                /*
                 * This block will be invoked on the main thread
                 * It is safe to call any UIKit APIs
                 */
                loadImages()
            })
        }
    }
}

// call loadImages() from the main thread

XCPShowView("image", view)
loadImages()
XCPSetExecutionShouldContinueIndefinitely()
Sign up to request clarification or add additional context in comments.

4 Comments

Thank you for your explanation. it works as expected. Thank you.
one more issue. I am calling loadImages initially in ViewWillAppear. some times loadImages calling repeatedly inside animation block which leads to rapid animations of images. i think loadImages request get added each time when i come to screen. How can i cancel the current request before initiating new one. I am using NSURLSession to initiate a call. If required, i can post it as new question. Thanks
Yeah, you should post the problem as a new question.
0

Your UI freezes as the comletion block for transitionWithView is called on the main thread (a.k.a. UI thread)- that's basically how you end up running "loadImages()" on the main thread. Then, when the load method is being called on the main thread, you create an instance of NSData and initialize it with contents of a URL - this is being done synchronously, therefore your UI is freezing. For what it's worth, just wrap the loadImages() call (the one which is inside the completion block) into a dispatch_async call to a background queue and the freeze should be gone. P.S. I would recommend queuing calls of loadImages() instead of relying on the completion blocks of UI animations.

1 Comment

Unfortunately it's not working. Animation blocks calls twice and stops.
0

You have to separate loadImages & update View into 2 GCD thread. Something like that:

override func viewDidLoad(animated: Bool) {
        super.viewDidLoad(animated)
        dispatch_async(dispatch_queue_create("com.company.queue.downloadImages", nil {
        () -> Void in
             self.loadImages()      
        }))
}


/**
Call this function in a GCD queue
*/
func loadImages() {
     ... first, download image

     ... you have to add stop condition cause I see that self.loadImages is called recursively in 'completion' clause below

     ... then, update UIView if data != nil
     if (data != nil) {
         dispatch_async(dispatch_get_main_queue(),nil, {
         () -> Void in
              UIView.transitionWithView.... {
              }, completion: { finished in 
                  dispatch_async(dispatch_queue_create("com.company.queue.downloadImages", nil, {
                  () -> Void in
                       self.imageCount++
                       self.loadImages()
                  }))
              })
         })
     }
}

4 Comments

i have to animate images repeatedly. That why i put loadImages in completion block. I have modified server call for getting images. i will now return the cache image if i call loadImages repeatedly. do i need to do anything?
I see that you update your images into the same UIImageView, is it what you need?
yes. but the above one is not working. is there any better way to animate loaded images
one more issue. I am calling loadImages initially in ViewWillAppear. some times loadImages calling repeatedly inside animation block which leads to rapid animations of images. i think loadImages request get added each time when i come to screen. How can i cancel the current request before initiating new one. I am using NSURLSession to initiate a call. If required, i can post it as new question. Thanks

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.