Изображения не попадают прямо в массив - PullRequest
1 голос
/ 09 июля 2020

У меня здесь небольшая проблема с кодом. Я пытаюсь заполнить представление коллекции пятью именами, описаниями и изображениями.

Я могу успешно загрузить все вышеперечисленное в их уважаемые массивы.

Проблема в том, что первый когда я выполняю переход, массив изображений имеет нулевые значения. Затем я go возвращаюсь на страницу и повторно захожу на страницу, чтобы обнаружить, что все массивы были успешно заполнены ....

Это действительно раздражает. Вот мой код:

//arrays of names, descriptions and images
    var names:[String] = []
    var descriptions: [String] = []
    var imagesArray: [UIImage] = []

Вот где я получаю изображения:

func downloadImages(){
        for x in 1...5{
            
            let url = URL(string: "https://www.imagesLocation.com/(x).png")
            
            let task = URLSession.shared.dataTask(with: url!){(data, response, error) in
                
                guard
                    let data = data,
                    let newImage = UIImage(data: data)
                    else{
                        print("Could not load image from URL: ",url!)
                        return
                }
                
                DispatchQueue.main.async {
                    self.imagesArray.append(newImage)
                }
            }
            
            task.resume()
        }
        
        loadDataFromFirebase()
    }
    

Вот откуда я загружаю имена и описания:

 func loadDataFromFirebase() {
        // Fetch and convert data
        let db = Firestore.firestore()
        db.collection(self.shopName).getDocuments { (snapshot, err) in
            if let err = err {
                print("Error getting documents: \(err)")
                return
            } else {
                for document in snapshot!.documents {
                    let name = document.get("Name") as! String
                    let description = document.get("Description") as! String
                    self.names.append(name)
                    self.descriptions.append(description)
                }
                self.setupImages() //safe to do this here as the firebase data is valid
            }
        }
    }

Вот где я настраиваю представление коллекции с содержимым массива Names, Description и Images:

func setupImages(){
    
    do {
        if imagesArray.count < 5 || names.count < 5  || descriptions.count < 5 {
            throw MyError.FoundNil("Something hasnt loaded")
        }
        
        self.pages = [
            Page(imageName: imagesArray[0], headerText: names[0], bodyText: descriptions[0]),
            
            Page(imageName: imagesArray[1], headerText: names[1], bodyText: descriptions[1]),
            
            Page(imageName: imagesArray[2], headerText: names[2], bodyText: descriptions[2]),
            
            Page(imageName: imagesArray[3], headerText: names[3], bodyText: descriptions[3]),
            
            Page(imageName: imagesArray[4], headerText: names[4], bodyText: descriptions[4]),
        ]
    }
    catch {
        print("Unexpected error: \(error).")
    }
}

Как видно из изображения ниже, каждый массив успешно заполняется, кроме массива изображений:

введите описание изображения здесь

Вот переход от кода предыдущей страницы:

DispatchQueue.main.async(){
            self.performSegue(withIdentifier: "goToNext", sender: self)   
        }

Любая помощь приветствуется :)

Ответы [ 3 ]

3 голосов
/ 11 июля 2020

Ваш вопрос - это просто вариант класса c: «Почему моя асинхронная функция возвращает пустые данные?» Я ответил на пару вопросов и приведу аналогию, объясняющую проблему. Возможно, вы уже понимаете суть проблемы, но я все равно включу ее для будущих читателей:

Твоя мама готовит ужин и просит go купить лимон.

Она начинает готовить, а у нее нет лимона!

Почему? Потому что вы еще не вернулись из супермаркета, и ваша мама не дождалась.

Источник

Основная проблема здесь в том, что вы звоните на loadDataFromFirebase слишком рано. Вы предполагаете, что он будет выполняться только после завершения ваших URL-запросов, но это не так. Зачем? Поскольку URL-запросы выполняются асинхронно . То есть они выполняются в другом потоке вместо того, чтобы блокировать поток, вызывающий dataTask.resume. Вот почему, как предлагает Шашанк Мишра, вы должны использовать DispatchGroup. Кроме того, нет гарантии, что ваши изображения будут загружаться в том порядке, в котором вы начинаете задачи с данными. Я включил исправление ниже.

Как правило, я бы рекомендовал определять переменные строго в тех областях, в которых они вам нужны. Удержание names, descriptions и images на таком большом объеме позволяет слишком легко совершать ошибки. Я предлагаю провести рефакторинг ваших функций и удалить эти три массива на уровне классов. Вместо этого:

func loadDataFromFirebase(images: [UIImage]) {
    // same function as you posted, except make names and descriptions local variables and
    // replace self.setupImages() with:
    DispatchQueue.main.async {
        self.setupImages(images: images, names: names, descriptions: descriptions)
    }
}

func setupImages(images: [UIImage], names: [String], descriptions: [String]) {
    guard images.count == 5, names.count == 5, descriptions.count == 5 else {
        print("Missing data.")
        return
    }

    self.pages = (0..<5).map({ Page(image: images[$0], header: names[$0], body: descriptions[$0]) })
    // super important!!!
    tableView.reloadData()
}

Наконец, вот мое предложение для поточно-безопасной downloadImages функции:

func downloadImages() {
    var images = [UIImage?](repeating: nil, count: 5)
    let dispatchGroup = DispatchGroup()
    
    for i in 1...5 {
        dispatchGroup.enter()
        
        let url = URL(string: "https://www.imagesLocation.com/\(i).png")!
        URLSession.shared.dataTask(with: url) { (data, response, error) in
            guard let data = data, let image = UIImage(data: data) else {
                print("Could not load image from", url)
                dispatchGroup.leave()
                return
            }
            
            images[i] = image
            dispatchGroup.leave()
        }.resume()
    }
    
    dispatchGroup.notify(queue: .main) {
        guard images.allSatisfy({$0 != nil}) else {
            print("Failed to fetch all images.")
            return
        }
        self.loadDataFromFirebase(images: images.compactMap({$0}))
    }
}

Как указал Фатт ie, вы должны использовать addSnapshotListener а не getDocuments. Кроме того, вы должны добавить прослушиватель / получить документы во время загрузки изображений, а не после, что будет быстрее. Однако я не добавляю ни того, ни другого к своему ответу, потому что он уже довольно длинный, и если у вас возникнут проблемы с ним, вы можете опубликовать другой вопрос.

0 голосов
/ 11 июля 2020

Вы неправильно понимаете, как работает Firebase.

По сути.

  1. Не используйте getDocuments. Используйте .addSnapshotListener

и

1012 1017 *

Типичный фрагмент ...

    let db = Firestore.firestore().db.collection("yourCollection")
        .whereField("user", isEqualTo: uid)
        .addSnapshotListener { [weak self] documentSnapshot, error in
            guard let self = self else { return }
            
            guard let ds = documentSnapshot else {
                return print("error: \(error!)")
            }
            
            self.displayItems =  .. that data
            self.tableView.reloadData()
    }

Обратите внимание на .reloadData()

Также ..

Это правда, что вы можете хранить изображение (двоичные данные) прямо в Firestore.

Но на самом деле никогда, никогда не делайте этого - это совершенно бесполезно.

Просто используйте невероятно простую систему Firebase / Storage, в которой вы может размещать изображения бесплатно. Тогда у них будут совершенно нормальные URL-адреса и т. Д.

Полное руководство: { ссылка }

0 голосов
/ 11 июля 2020

Вы можете использовать DispatchGroup для достижения асинхронных вызовов -

func downloadImages() {

   let dispatchGroup = DispatchGroup()

    for x in 1...5 {
         
         dispatchGroup.enter()
        
         let url = URL(string: "https://www.imagesLocation.com/(x).png")
         let task = URLSession.shared.dataTask(with: url!){(data, response, error) in
             guard
                 let data = data,
                 let newImage = UIImage(data: data)
                 else{
                     print("Could not load image from URL: ",url!)
                     dispatchGroup.leave()
                     return
             }
             self.imagesArray.append(newImage)
             dispatchGroup.leave()
         }
         task.resume()
     }
     dispatchGroup.notify(queue: DispatchQueue.main) {
        self.loadDataFromFirebase()
     }
 }

Вызовите метод loadDataFromFirebase () для получения всех 5 ответов, как указано выше. Перед загрузкой в ​​просмотр в нем всегда будут все изображения.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...