Chargement d'images une après l'autre

heliohelio Membre

Bonjour à tous,
Voici mon code pour afficher des images dans une ScrollView :

class ViewController: UIViewController {

var arrayOfImages : [String]? =
    ["https://static.fnac-static.com/multimedia/Images/FR/MDM/23/6d/38/3697955/1540-1/tsp20181109130417/Apple-iPhone-X-64-Go-5-8-Argent.jpg", "https://static.fnac-static.com/multimedia/Images/FR/MDM/17/ca/8d/9292311/1540-1/tsp20180920193530/Apple-iPhone-XS-Max-64-Go-6-5-Or.jpg", "https://static.fnac-static.com/multimedia/Images/FR/MDM/06/ca/8d/9292294/1540-1/tsp20181211170021/Apple-iPhone-XR-128-Go-6-1-Corail.jpg"]

@IBOutlet weak var scrollView: UIScrollView!

override func viewDidLoad() {
    super.viewDidLoad()

    // Do any additional setup after loading the view, typically from a nib.

    var newPageView = UIImageView(image: UIImage(named: " "))

    var xOffset : CGFloat = 0

    scrollView.backgroundColor = UIColor.black

    guard let cnt = arrayOfImages?.count else {
        return
    }

    for i in 0 ..< cnt {
        print(arrayOfImages?[i] as Any)

        guard let image = arrayOfImages?[i] else {
            return
        }

        guard let data1 = try? Data(contentsOf: URL(string:image)!) else {
            return
        }
        newPageView = UIImageView(image: UIImage(data: data1))

        newPageView.frame = CGRect(x: xOffset, y: 50, width: 200, height: 200)

        xOffset = xOffset + newPageView.frame.width
        scrollView.addSubview(newPageView)
    }

    self.scrollView.contentSize = CGSize(width: xOffset, height: 200)

}

}

le souci dans ce code est que les images se chargent en même temps, par conséquent si les images sont de tailles importantes l'utilisateur ne voit aucune image pendant un moment. Je souhaiterais que la première image se charge et que les suivantes se chargent uniquement si la précédente est affichée.
Comment modifier le code pour avoir ce que je souhaite ?
Merci.

Mots clés:

Réponses

  • LarmeLarme Membre

    Ceci: let data1 = try? Data(contentsOf: URL(string:image)!) bloque le thread courant. Comme c'est dans viewDidLoad(), ça sera sûrement le main thread.

    Tu peux faire de l'async qui load le suivant quand c'est terminé. Voire un call recursive qui load le premier et se rappelle sur le reste des images.

  • LarmeLarme Membre

    Vite fait, non-testé, à améliorer, mais tu auras la logique :

    class ViewController: UIViewController {
    
        var arrayOfImages : [String] =
            ["https://static.fnac-static.com/multimedia/Images/FR/MDM/23/6d/38/3697955/1540-1/tsp20181109130417/Apple-iPhone-X-64-Go-5-8-Argent.jpg", "https://static.fnac-static.com/multimedia/Images/FR/MDM/17/ca/8d/9292311/1540-1/tsp20180920193530/Apple-iPhone-XS-Max-64-Go-6-5-Or.jpg", "https://static.fnac-static.com/multimedia/Images/FR/MDM/06/ca/8d/9292294/1540-1/tsp20181211170021/Apple-iPhone-XR-128-Go-6-1-Corail.jpg"]
    
        @IBOutlet weak var scrollView: UIScrollView!
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            scrollView.backgroundColor = UIColor.black
            let imagesURLs = arrayOfImages.compactMap({ URL(string: $0) })
            loadImages(atURLs: imagesURLs) { () in
                print("Loading didEnd")
            }
        }
    
        func addImage(_ image: UIImage?) {
            let imageView = UIImageView(image: image)
            imageView.frame = CGRect(x: scrollView.contentSize.width, y: 50, width: 200, height: 200)
            scrollView.addSubview(imageView)
        }
    
        //Will load first Image of the array and add it into the scrollView.
        //Then it will call itself on the remaining urls
    
        func loadImages(atURLs urls: [URL], completion: @escaping (() -> Void)) {
            guard let firstURL = urls.first else { //There is no more URLs, we finished the loading
                completion()
                return
            }
    
            //Get the remaining URLs
            let restOfURLs = Array(urls.dropFirst())
    
            //Async loading: Not blocking the main thread
            URLSession.shared.dataTask(with: firstURL) { [weak self] (data, response, error) in
                guard let self = self else { return }
    
                guard let data = data, let image = UIImage(data: data) else {
                    print("Couldn't get data or transform it at \(firstURL.absoluteString) into image")
                    DispatchQueue.main.async {
                        self.addImage(nil) // If you still want to show a black image
                        self.loadImages(atURLs: restOfURLs, completion: completion) //If you still want to load the rest in case of one failed
                    }
                    return
                }
    
                DispatchQueue.main.async {
                    self.addImage(image)
                    self.loadImages(atURLs: restOfURLs, completion: completion)
                }
            }
        }
    
    }
    
  • heliohelio Membre

    Salut,
    merci beaucoup, exactement ce que je souhaitais, j'ai arrangé le code et ça fonctionne.
    J'ai simplement un petit souci, il arrive que dans arrayOfImages, au lieu d'avoir une URL j'ai la valeur "Null".
    Le problème est que j'aimerais que ça affiche par exemple un icone nopicture.png stocké localement mais à quel endroit faire le test ?
    Est-ce qu'il ne faudrait pas que je fasse plutot un Array d'images et non d'URL ?
    Merci.

  • LarmeLarme Membre

    Donc tu veux un [String?] plutôt, et donc un [URL?] pour arrayOfImages et imagesURLs.
    Fais-en sorte de modifier le code de création de imagesURLs pour qu'il puisse contenir des nil values, la déclaration de loadImages() pour qu'il puisse traiter une URL nil, et retourner ton image par défaut.

  • heliohelio Membre

    Salut,

    Je me suis remis à ce projet et concernant le dernier point, je n'arrive pas à afficher mon image par défaut quand j'ai la valeur nil dans mon tableau.

    j'arrive à avoir ceci dans imagesURLs:

    ["nil", "https://....jpg", "nil", "https://....jpg", "https://....jpg"]
    

    Ensuite pour charger les images, je fais ceci :

       loadImages(atURLs: imagesURLs) { () in
            print("Loading didEnd")
            self.activityIndicatorView.stopAnimating()
        }
    

    Et la fonction loadImages dans laquelle il faudrait que je puisse charger une image locale NoImage.png par exemple lorsque j'ai nil dans imagesURLs :

     func loadImages(atURLs urls: [String], completion: @escaping (() -> Void)) {
    
        guard let firstURL = URL(string: urls.first?) else { //There is no more URLs, we finished the loading
            completion()
            return
        }
    
        //Get the remaining URLs
        let restOfURLs = Array(urls.dropFirst())
    
        URLSession.shared.dataTask(with: firstURL, completionHandler: { (data, response, error) in
    
            guard let data = data, let image = UIImage(data: data) else {
                print("Couldn't get data or transform it at \(firstURL.absoluteString) into image")
    
                 DispatchQueue.main.async {
    
                 self.addImage(nil) // If you still want to show a black image
    
                 self.loadImages(atURLs: restOfURLs, completion: completion) //If you still want to load the rest in case of one failed
                 }
    
                return
    
            }
    
            DispatchQueue.main.async {
                self.addImage(image)
                self.loadImages(atURLs: restOfURLs, completion: completion)
            }
    
        }).resume()
    
    }
    

    A l'heure actuelle, cela génère une erreur (tout à fait logique) :

       Couldn't get data or transform it at nil into image
    

    Merci de votre aide.

  • Ben

    if url == nil --> setImage:[NSImage imageNamed:@NoImage]

    Je vois pas vraiment où est le problème ? O_ô

Connectez-vous ou Inscrivez-vous pour répondre.