[英]How to set a custom prefetching time for LazyVStack in SwiftUI?
TL;DR:是否有一些参数或方法来设置 LazyVStack 初始化视图的偏移量?
LazyVStack 懒惰地初始化视图,所以当我滚动时,下一个(几个?)视图被初始化。 我在绘制视图后加载图像,使用 swift 中的 SDWebImage Package。这需要几毫秒的时间,并且由于我使用的是 LazyVStack,如果快速滚动(即使在合理的限制内),占位符会在短时间内可见,因为刚刚(太)不久前创建了视图。 如果我滚动得非常慢,图像会在视图出现之前加载,因此不会显示任何占位符。
如果我能让 LazyVStack 早几毫秒初始化视图,我的问题就会消失......
曾经认为这是一个非常普遍的问题,将此初始化时间恰到好处,以免加载太早或太晚..但文档中根本没有关于此的内容
这个过程称为prefetching
- 因为你正在预取它们所以它看起来很平滑 -
抱歉,现在无法在 SwiftUI 中访问LazyVStack
的预取。 另外,请记住,SwiftUI 的Grid
和LazyH/VStack
的性能都不如UIKit
的UICollectionView
。 所以你可以在这里做的是你可以在你的集合视图数据中使用UICollectionView
的UICollectionViewDataSourcePrefetching
协议。
我使用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.