簡體   English   中英

SwiftUI中LazyVStack如何設置自定義預取時間?

[英]How to set a custom prefetching time for LazyVStack in SwiftUI?

TL;DR:是否有一些參數或方法來設置 LazyVStack 初始化視圖的偏移量?

LazyVStack 懶惰地初始化視圖,所以當我滾動時,下一個(幾個?)視圖被初始化。 我在繪制視圖后加載圖像,使用 swift 中的 SDWebImage Package。這需要幾毫秒的時間,並且由於我使用的是 LazyVStack,如果快速滾動(即使在合理的限制內),占位符會在短時間內可見,因為剛剛(太)不久前創建了視圖。 如果我滾動得非常慢,圖像會在視圖出現之前加載,因此不會顯示任何占位符。

如果我能讓 LazyVStack 早幾毫秒初始化視圖,我的問題就會消失......

曾經認為這是一個非常普遍的問題,將此初始化時間恰到好處,以免加載太早或太晚..但文檔中根本沒有關於此的內容

這個過程稱為prefetching - 因為你正在預取它們所以它看起來很平滑 -

抱歉,現在無法在 SwiftUI 中訪問LazyVStack的預取。 另外,請記住,SwiftUI 的GridLazyH/VStack的性能都不如UIKitUICollectionView 所以你可以在這里做的是你可以在你的集合視圖數據中使用UICollectionViewUICollectionViewDataSourcePrefetching協議。

我使用SDWebImage庫從 inte.net 獲取圖像( UIKit最受歡迎的庫之一)

我試圖解釋一切,所以請注意它們,這就是它的樣子: 動圖

這是代碼:

import SwiftUI
import SDWebImage

struct CollectionView: UIViewRepresentable {
    let items: [String]
    
    func makeUIView(context: Context) -> UICollectionView {
        let collectionView = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout())
        collectionView.register(ImageCell.self, forCellWithReuseIdentifier: "ImageCell")
        collectionView.delegate = context.coordinator
        collectionView.dataSource = context.coordinator
        return collectionView
    }
    
    func updateUIView(_ uiView: UICollectionView, context: Context) {
        // Reload the collection view data if the items array changes
        uiView.reloadData()
    }
    
    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }
    
    class Coordinator: NSObject, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout, UICollectionViewDataSourcePrefetching {
        let parent: CollectionView
        
        init(_ collectionView: CollectionView) {
            self.parent = collectionView
        }
        
        func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
            return parent.items.count
        }
        
        func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
            let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ImageCell", for: indexPath) as! ImageCell
            let item = parent.items[indexPath.item]
            
            // Set the progress of the progress view as the image is being downloaded
            cell.progressView.progress = 0.0
            SDWebImageDownloader.shared.downloadImage(with: URL(string: item), options: .highPriority, progress: { (receivedSize, expectedSize, url) in
                DispatchQueue.main.async {
                    cell.progressView.progress = Float(receivedSize) / Float(expectedSize)
                }
            }) { (image, data, error, finished) in
                DispatchQueue.main.async {
                    cell.imageView.sd_setImage(with: URL(string: item))
                    cell.progressView.isHidden = true
                }
            }
            return cell
        }
        
        func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
            return CGSize(width: 100, height: 100)
        }
        
        // MARK: - UICollectionViewDataSourcePrefetching
        
        func collectionView(_ collectionView: UICollectionView, prefetchItemsAt indexPaths: [IndexPath]) {
            // Filter the index paths to only include the ones that are within the desired range, trick relies on here
            // In our example, i'm fetching 6 items beforehand which equals 2 rows, so i'm prefetching 2 rows beforehand. you can increase that amount if you w ant to
            let prefetchIndexPaths = indexPaths.filter { $0.item < collectionView.numberOfItems(inSection: $0.section) - 6 }
            let urls = prefetchIndexPaths.compactMap { URL(string: self.parent.items[$0.item])! }
            SDWebImagePrefetcher.shared.prefetchURLs(urls)
        }
        
        func collectionView(_ collectionView: UICollectionView, cancelPrefetchingForItemsAt indexPaths: [IndexPath]) {
            // Cancel the prefetching for the given index paths, this is not required but i wanted to add it
            let urls = indexPaths.map { URL(string: self.parent.items[$0.item]) }
        }
    }
}

class ImageCell: UICollectionViewCell {
    let imageView = UIImageView()
    let progressView = UIProgressView()
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        addSubview(imageView)
        addSubview(progressView)
        // if you're not familiar with uikit this is just a disgusting uikit code to make proper layouts :(
        progressView.translatesAutoresizingMaskIntoConstraints = false
        progressView.topAnchor.constraint(equalTo: topAnchor).isActive = true
        progressView.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
        progressView.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true
        
        imageView.translatesAutoresizingMaskIntoConstraints = false
        imageView.topAnchor.constraint(equalTo: progressView.bottomAnchor).isActive = true
        imageView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
        imageView.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
        imageView.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

以下是如何將它實現到 swiftui:


struct ContentView: View {
    var items : [String] {
        var i = 0
        var _items = [String]()
        while (i < 900) {
            _items.append("https://picsum.photos/\(Int.random(in: 300..<600))/\(Int.random(in: 300..<600))")
            i = i + 1
        }
        return _items
    }
    
    var body: some View {
        CollectionView(items: items)
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

我使用了 lorem picsum,這是一個用於生成隨機圖像的網站,這就是為什么你看到圖像在我的樣本(白色的)中隨機重新加載,在你的情況下,這應該不是問題

由於我之前使用SDWebImageSwiftUI ,因此在視圖開始初始化之前只需調用以下命令即可解決我的問題:

SDWebImagePrefetcher.shared.prefetchURLs(urls) { finishedCount, skippedCount in
     print("preloading complete")
}

然后在我的 LazyVStack 中使用:

LazyVStack {
     ForEach(items, id: \.self) { item in
             ItemView(item: item)
                   .onAppear {
                        // calling function to prefetch next x-items by their url
                  }
            }
     }
}

在 SwiftUI 中,我們可以使用“LazyVStack”視圖實現預取,這使我們能夠高效地渲染垂直堆棧中的大量項目。

LazyVStack {
    ForEach(data, id: \.self) { item in
        Text(item.attribute)
    }
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM