ios - Swift - TableViewCell 中不同大小的图像的困难
问题描述
我正在使用 Kingfisher 加载许多远程图像,并且很难将它们正确加载到具有动态高度单元格的 Tableview 中。我的目标是让图像始终是屏幕的全宽和动态高度,如何实现?
我之前问过一个相关问题,这导致使用堆栈视图了解基本布局:SnapKit:如何以编程方式为 TableViewCell 中的项目设置布局约束
所以我构建了如下内容:
使用以下代码(为简洁起见,删除了某些部分):
// CREATE VIEWS
let containerStack = UIStackView()
let header = UIView()
let headerStack = UIStackView()
let title = UILabel()
let author = UILabel()
var previewImage = UIImageView()
...
// KINGFISHER
let url = URL(string: article.imageUrl)
previewImage.kf.indicatorType = .activity
previewImage.kf.setImage(
with: url,
options: [
.transition(.fade(0.2)),
.scaleFactor(UIScreen.main.scale),
.cacheOriginalImage
]) { result in
switch result {
case .success(_):
self.setNeedsLayout()
UIView.performWithoutAnimation {
self.tableView()?.beginUpdates()
self.tableView()?.endUpdates()
}
case .failure(let error):
print(error)
}
}
...
// LAYOUT
containerStack.axis = .vertical
headerStack.axis = .vertical
headerStack.spacing = 6
headerStack.addArrangedSubview(title)
headerStack.addArrangedSubview(author)
header.addSubview(headerStack)
containerStack.addArrangedSubview(header)
containerStack.addSubview(previewImage)
addSubview(containerStack)
headerStack.snp.makeConstraints { make in
make.edges.equalToSuperview().inset(20)
}
containerStack.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
如果没有 的约束imageView
,图像不会出现。
使用以下约束,图像也不会出现:
previewImage.snp.makeConstraints { make in
make.leading.trailing.bottom.equalToSuperview()
make.top.equalTo(headerView.snp.bottom).offset(20)
}
通过其他尝试,图像完全倾斜或与标签/其他单元格和图像重叠。
最后,遵循此评论:使用自动布局,如何根据图像使 UIImageView 的大小动态变化?这个要点:https ://gist.github.com/marcc-orange/e309d86275e301466d1eecc8e400ad00并且有了这些限制make.edges.equalToSuperview()
,我能够让图像以正确的比例显示,但它们完全覆盖了标签。
理想情况下,它看起来像这样:
解决方案
带有示例代码的 100 % 工作解决方案
我只是设法通过动态标签内容和动态图像尺寸实现了相同的布局。我通过约束和Autolayout 做到了。看看这个GitHub 存储库中的演示项目
正如马特指出的那样,我们必须在图像下载后计算每个单元格的高度(当我们知道它的宽度和高度时)。注意每个单元格的高度是通过tableView的delegate方法计算出来的heightForRowAt IndexPath
因此,在下载每个图像后,将图像保存在此 indexPath 的数组中并重新加载该 indexPath,以便根据图像尺寸再次计算高度。
需要注意的一些关键点如下
- 使用 3 种类型的细胞。一个用于标签,一个用于字幕,一个用于图像。在里面
cellForRowAt
初始化并返回适当的单元格。每个单元格都有一个唯一的cellIdentifier
但类是相同的- tableView 中的部分数 == 数据源计数
- 部分中的行数 == 3
- 第一行对应标题,第二行对应副标题,第三行对应图片。
- 标签的行数应为 0,以便应根据内容计算高度
- 在内部
cellForRowAt
异步下载图像,将其存储在数组中并重新加载该行。- 通过重新加载行,
heightForRowAt
被调用,根据图像尺寸计算所需的单元格高度并返回高度。- 所以每个单元格的高度是根据图像尺寸动态计算的
看看一些代码
override func numberOfSections(in tableView: UITableView) -> Int {
return arrayListItems.count
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
//Title, SubTitle, and Image
return 3
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
switch indexPath.row {
case 0:
//configure and return Title Cell. See code in Github Repo
case 1:
//configure and return SubTitle Cell. See code in Github Repo
case 2:
let cellImage = tableView.dequeueReusableCell(withIdentifier: cellIdentifierImage) as! TableViewCell
let item = arrayListItems[indexPath.section]
//if we already have the image, just show
if let image = arrayListItems[indexPath.section].image {
cellImage.imageViewPicture.image = image
}else {
if let url = URL.init(string: item.imageUrlStr) {
cellImage.imageViewPicture.kf.setImage(with: url) { [weak self] result in
guard let strongSelf = self else { return } //arc
switch result {
case .success(let value):
print("=====Image Size \(value.image.size)" )
//store image in array so that `heightForRowAt` can use image width and height to calculate cell height
strongSelf.arrayListItems[indexPath.section].image = value.image
DispatchQueue.main.async {
//reload this row so that `heightForRowAt` runs again and calculates height of cell based on image height
self?.tableView.reloadRows(at: [indexPath], with: .automatic)
}
case .failure(let error):
print(error) // The error happens
}
}
}
}
return cellImage
default:
print("this should not be called")
}
//this should not be executed
return .init()
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
//calculate the height of label cells automatically in each section
if indexPath.row == 0 || indexPath.row == 1 { return UITableView.automaticDimension }
// calculating the height of image for indexPath
else if indexPath.row == 2, let image = arrayListItems[indexPath.section].image {
print("heightForRowAt indexPath : \(indexPath)")
//image
let imageWidth = image.size.width
let imageHeight = image.size.height
guard imageWidth > 0 && imageHeight > 0 else { return UITableView.automaticDimension }
//images always be the full width of the screen
let requiredWidth = tableView.frame.width
let widthRatio = requiredWidth / imageWidth
let requiredHeight = imageHeight * widthRatio
print("returned height \(requiredHeight) at indexPath: \(indexPath)")
return requiredHeight
}
else { return UITableView.automaticDimension }
}
有关的。
我们可以遵循的另一种方法是从 API 请求中返回图像尺寸。如果可以做到这一点,它将简化很多事情。看看这个类似的问题(对于collectionView)。
Placholder.com用于异步获取图像
样本
推荐阅读
- typescript - 未找到 mapbox-sdk-js 的子模块的 Typescript 导入错误
- github - 在 Github Pages 中删除自定义页面后,原始页面无法正常工作
- c# - .NET Core 在错误的位置发布文件夹
- javascript - 如何在地图中使用等待?
- javascript - 我正在研究暗模式偏好。Windows 应用程序
- android - 将 Android 的 Connection(来自 ConnectionService)与 OpenTok 音频流集成
- microsoft-graph-api - Microsoft Graph 返回未找到资源
- typescript - 类型具有私有属性的单独声明 - urql 与 @urql
- node.js - Chrome v88+ 和 Nestjs 服务器上的 socket-io 连接出现 CORS 错误
- java - Bouncy Castle RSAKeyGenerationPerameters publicExponent 必须是正数吗?