UICollectionView Cell加载SKProduct价格时跳变问题求助
解决UICollectionView Cell内购价格跳变问题
完全同意你的想法——必须把SKProduct的获取和价格格式化逻辑移到TopUpViewController中,而不是在Cell里处理。你遇到的价格跳变问题,本质是UICollectionView的Cell复用机制和异步价格请求的冲突,下面一步步给你讲清楚怎么改:
为什么之前会跳变?
当你在Cell里直接请求SKProduct价格时,Cell被复用(比如滑动列表)后,之前Cell发起的异步请求可能还没完成。等请求返回时,这个Cell已经被用来展示另一个产品了,旧的价格就会覆盖新Cell的内容,导致跳变。把逻辑移到VC里预加载所有数据,就能从根源解决这个问题。
具体实现步骤
1. 在TopUpViewController中预加载所有产品的本地化价格
首先,你需要提前获取所有4个内购产品,并把每个产品的本地化价格格式化好,存起来。建议封装一个简单的模型来统一管理产品和价格:
// 自定义模型,统一管理内购产品和格式化后的价格 struct TopUpProductModel { let product: SKProduct let localizedPrice: String } // 在TopUpViewController里定义存储数组 private var topUpProducts: [TopUpProductModel] = []
然后实现SKProductsRequest的请求逻辑,在VC的生命周期方法(比如viewDidLoad)里发起请求:
override func viewDidLoad() { super.viewDidLoad() fetchTopUpProducts() } private func fetchTopUpProducts() { // 替换成你的4个内购产品ID let productIDs = Set(["com.yourapp.product.1", "com.yourapp.product.2", "com.yourapp.product.3", "com.yourapp.product.4"]) let request = SKProductsRequest(productIdentifiers: productIDs) request.delegate = self request.start() } // 实现SKProductsRequestDelegate协议 extension TopUpViewController: SKProductsRequestDelegate { func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) { // 格式化价格的工具 let priceFormatter = NumberFormatter() priceFormatter.numberStyle = .currency // 遍历返回的产品,生成模型数组 topUpProducts = response.products.map { product in priceFormatter.locale = product.priceLocale let priceStr = priceFormatter.string(from: product.price) ?? "\(product.price)" return TopUpProductModel(product: product, localizedPrice: priceStr) } // 数据准备好后刷新CollectionView yourCollectionView.reloadData() } func request(_ request: SKRequest, didFailWithError error: Error) { // 处理请求失败的情况,比如提示用户 print("产品请求失败:\(error.localizedDescription)") } }
2. 在数据源方法中传递数据给TopUpMoneyCell
现在所有价格都预加载好了,只需要在CollectionView的数据源方法里,把对应位置的价格直接赋值给Cell即可:
extension TopUpViewController: UICollectionViewDataSource { func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return topUpProducts.count } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "TopUpMoneyCell", for: indexPath) as! TopUpMoneyCell let productModel = topUpProducts[indexPath.item] // 直接给Cell的Label赋值预格式化好的价格 cell.moneyLabel.text = productModel.localizedPrice // 如果后续购买需要用到SKProduct,也可以把产品对象传给Cell cell.associatedProduct = productModel.product return cell } }
3. 处理Cell复用的收尾工作
为了彻底避免旧数据残留,在TopUpMoneyCell里重写prepareForReuse方法,清空Label内容和关联的产品:
class TopUpMoneyCell: UICollectionViewCell { @IBOutlet weak var moneyLabel: UILabel! var associatedProduct: SKProduct? override func prepareForReuse() { super.prepareForReuse() // 清空旧数据,防止复用后显示错误内容 moneyLabel.text = nil associatedProduct = nil } }
为什么这样改更好?
- 彻底解决了异步请求和Cell复用的冲突,价格不会再跳变
- 数据逻辑集中在VC里,符合MVC架构,更容易维护和调试
- 预加载所有产品价格,页面加载更流畅,用户体验更好
内容的提问来源于stack exchange,提问作者JG_
相关产品推荐
相关产品推荐

