SwiftUI List - Удаленная загрузка изображений = мерцание изображений (перезагрузка) - PullRequest
0 голосов
/ 21 февраля 2020

Я новичок в SwiftUI, а также Combine. У меня есть простой список, и я загружаю данные iTunes и строю список. Загрузка изображений работает - но они мерцают, потому что, кажется, моя отправка в главном потоке продолжает работать. Я не уверен почему. Ниже приведен код для загрузки изображения, а также место его реализации.

struct ImageView: View {
    @ObservedObject var imageLoader: ImageLoaderNew
    @State var image: UIImage = UIImage()

    init(withURL url: String) {
        imageLoader = ImageLoaderNew(urlString: url)
    }

    func imageFromData(_ data: Data) -> UIImage {
        UIImage(data: data) ?? UIImage()
    }

    var body: some View {
        VStack {
            Image(uiImage: imageLoader.dataIsValid ?
                imageFromData(imageLoader.data!) : UIImage())
                .resizable()
                .aspectRatio(contentMode: .fit)
                .frame(width:60, height:60)
                .background(Color.gray)
        }
    }
}

class ImageLoaderNew: ObservableObject
{    
    @Published var dataIsValid = false
    var data: Data?

    // The dispatch fires over and over again. No idea why yet
    // this causes flickering of the images in the List. 
    // I am only loading a total of 3 items. 

    init(urlString: String) {
        guard let url = URL(string: urlString) else { return }
        let task = URLSession.shared.dataTask(with: url) { data, response, error in
            guard let data = data else { return }
            DispatchQueue.main.async {
                self.dataIsValid = true
                self.data = data
                print(response?.url as Any) // prints over and over again.
            }
        }
        task.resume()
    }
}

И здесь это реализовано после загрузки JSON, et c.

List(results, id: \.trackId) { item in
    HStack {

        // All of these image end up flickering
        // every few seconds or so.
        ImageView(withURL: item.artworkUrl60)
            .padding(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 5))

        VStack(alignment: .leading) {
            Text(item.trackName)
                .foregroundColor(.gray)
                .font(.headline)
                .truncationMode(.middle)
                .lineLimit(2)

            Text(item.collectionName)
                .foregroundColor(.gray)
                .font(.caption)
                .truncationMode(.middle).lineLimit(1)
            }
        }
    }
    .frame(height: 200.0)
    .onAppear(perform: loadData) // Only happens once

Я не уверен, почему изображения продолжают загружаться снова и снова (пока). Должно быть, я упускаю что-то простое, но я определенно еще не мудр к путям объединения. Любое понимание или решение будет высоко ценится.

Ответы [ 3 ]

1 голос
/ 22 февраля 2020

Я бы изменил порядок назначения, как показано ниже (потому что в противном случае dataIsValid force refre sh, но data еще не установлен)

DispatchQueue.main.async {
    self.data = data             // 1) set data
    self.dataIsValid = true.     // 2) notify that data is ready

все остальное не кажется важным. Однако рассмотрим возможность оптимизации ниже, потому что UIImage построение из данных также может быть достаточно длинным (для потока пользовательского интерфейса)

func imageFromData(_ data: Data) -> UIImage {
    UIImage(data: data) ?? UIImage()
}

, поэтому я бы рекомендовал перемещать не только данные, но и всю конструкцию изображения в фоновый поток и refre sh только когда готовое изображение готово.

Обновление: Я сделал ваш снимок кода работающим локально, с некоторыми заменами и упрощениями, конечно, из-за того, что изначально не все части доступно, и предложенное выше исправление. Вот некоторый экспериментальный результат:

enter image description here

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

Вот мой код (полностью протестирован с Xcode 11.2 / 3 & iOS 13.2 / 3):

struct ImageView: View {
    @ObservedObject var imageLoader: ImageLoaderNew
    @State var image: UIImage = UIImage()

    init(withURL url: String) {
        imageLoader = ImageLoaderNew(urlString: url)
    }

    func imageFromData(_ data: Data) -> UIImage {
        UIImage(data: data) ?? UIImage()
    }

    var body: some View {
        VStack {
            Image(uiImage: imageLoader.dataIsValid ?
                imageFromData(imageLoader.data!) : UIImage())
                .resizable()
                .aspectRatio(contentMode: .fit)
                .frame(width:60, height:60)
                .background(Color.gray)
        }
    }
}

class ImageLoaderNew: ObservableObject
{
    @Published var dataIsValid = false
    var data: Data?

    // The dispatch fires over and over again. No idea why yet
    // this causes flickering of the images in the List.
    // I am only loading a total of 3 items.

    init(urlString: String) {
        guard let url = URL(string: urlString) else { return }
        let task = URLSession.shared.dataTask(with: url) { data, response, error in
            guard let data = data else { return }
            DispatchQueue.main.async {
                self.data = data
                self.dataIsValid = true
                print(response?.url as Any) // prints over and over again.
            }
        }
        task.resume()
    }
}

struct TestImageFlickering: View {
    @State private var results: [String] = []
    var body: some View {
        NavigationView {
            List(results, id: \.self) { item in
                HStack {

                    // All of these image end up flickering
                    // every few seconds or so.
                    ImageView(withURL: item)
                        .padding(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 5))

                    VStack(alignment: .leading) {
                        Text("trackName")
                            .foregroundColor(.gray)
                            .font(.headline)
                            .truncationMode(.middle)
                            .lineLimit(2)

                        Text("collectionName")
                            .foregroundColor(.gray)
                            .font(.caption)
                            .truncationMode(.middle).lineLimit(1)
                    }
                }
            }
            .frame(height: 200.0)
            .onAppear(perform: loadData)
        } // Only happens once
    }

    func loadData() {
        var urls = [String]()
        for i in 1...10 {
            urls.append("https://placehold.it/120.png&text=image\(i)")
        }
        self.results = urls
    }
}

struct TestImageFlickering_Previews: PreviewProvider {
    static var previews: some View {
        TestImageFlickering()
    }
}
0 голосов
/ 05 мая 2020

Я столкнулся с аналогичной проблемой в удаленном загрузчике изображений SwiftUI, над которым я работал. Попробуйте сделать ImageView равным, чтобы избежать перерисовки после того, как для uiImage было установлено что-то отличное от инициализированного значения nil или если оно возвращается к nil.

struct ImageView: View, Equatable {

    static func == (lhs: ImageView, rhs: ImageView) -> Bool {
        let lhsImage = lhs.image
        let rhsImage = rhs.image
        if (lhsImage == nil && rhsImage != nil) || (lhsImage != nil && rhsImage == nil) {
            return false
        } else {
            return true
        }
    }

    @ObservedObject var imageLoader: ImageLoaderNew
    @State var image: UIImage?

// ... rest the same
0 голосов
/ 22 февраля 2020
// The dispatch fires over and over again. No idea why yet
// this causes flickering of the images in the List. 
// I am only loading a total of 3 items. 

    init(urlString: String) { ....

означает, что init вызывает снова и снова

он может быть вызван только из этой части

// All of these image end up flickering
// every few seconds or so.
    ImageView(withURL: item.artworkUrl60)
        .padding(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 5))

В каком случае SwiftUI будет делать это? Я не вижу ничего, кроме изменения макета. Кажется, что изменение размера ImageView - это то, где можно найти это мерцание.

Я попробую сыграть с этой частью

.resizable()
.aspectRatio(contentMode: .fit)

или применить

ImageView(withURL: item.artworkUrl60)
    .padding(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 5))
    .fixedSize()

Что такое по предложению Asperi ( создайте изображение фиксированного размера из загруженных данных в фоновом режиме ) и удалите

.resizable()
.aspectRatio(contentMode: .fit)

из макета, вероятно, это лучший подход

или просто переместите .frame(width:60, height:60) из ImageView и примените его в своем Списке

ImageView(withURL: item.artworkUrl60)
    .frame(width:60, height:60)
    .padding(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 5))

Я также постараюсь исправить размеры всех компонентов Списка ..., пока вы не найдете, какой из них вызывает мерцание.

...