Solución:
Solución de trabajo 100% con código de muestra
Me las arreglé para lograr el mismo diseño con contenido de etiqueta dinámica y dimensiones de imagen dinámicas. Lo hice a través de limitaciones y Diseño automático. Eche un vistazo al proyecto de demostración en este Repositorio de GitHub
Como señaló Matt, tenemos que calcular la altura de cada celda después de descargar la imagen (cuando conocemos su ancho y alto). Tenga en cuenta que la altura de cada celda se calcula mediante el método delegado de tableView heightForRowAt IndexPath
Entonces, después de descargar cada imagen, guarde la imagen en una matriz en este indexPath y vuelva a cargar ese indexPath para que la altura se calcule nuevamente, según las dimensiones de la imagen.
Algunos puntos clave a tener en cuenta son los siguientes
- Utilice 3 tipos de células. Uno para etiqueta, uno para subtítulo y uno para imagen. Dentro
cellForRowAt
inicializar y devolver la celda apropiada. Cada celda tiene uncellIdentifier
pero la clase es la misma- número de secciones en tableView == recuento de la fuente de datos
- número de filas en la sección == 3
- La primera fila corresponde al título, la segunda fila corresponde al subtítulo y la tercera corresponde a la imagen.
- el número de líneas para las etiquetas debe ser 0 para que la altura se calcule en función del contenido
- Dentro
cellForRowAt
descargue la imagen de forma asincrónica, almacénela en una matriz y vuelva a cargar esa fila.- Al recargar la fila,
heightForRowAt
se llama, calcula la altura de celda requerida en función de las dimensiones de la imagen y devuelve la altura.- Entonces, la altura de cada celda se calcula dinámicamente en función de las dimensiones de la imagen
Eche un vistazo a algunos códigos
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 }
}
Relacionado.
Otro enfoque que podemos seguir es devolver las dimensiones de la imagen de la solicitud de API. Si eso se puede hacer, simplificará mucho las cosas. Eche un vistazo a esta pregunta similar (para collectionView).
Celdas de vista de colección de tamaño propio con descarga de imágenes asíncronas.
Placholder.com Se utiliza para obtener imágenes de forma asincrónica
Células de tamaño propio: (una buena lectura)
Muestra
Es relativamente fácil hacer lo que está describiendo: la vista de su imagen necesita una restricción de ancho que sea igual al ancho de la “pantalla” (como usted lo expresa) y una restricción de altura que sea proporcional a la restricción de ancho (multiplier
) según las proporciones de la imagen descargada (también conocida como “relación de aspecto”). Este valor no se puede establecer de antemano; necesitas configurarlo una vez tengas la imagen descargada, ya que no conoces sus proporciones hasta entonces. Por lo tanto, necesita una salida a la restricción de altura para poder quitarla y reemplazarla con una que tenga la multiplier
cuando lo sepas. Si sus otras restricciones son correctas en relación con la parte superior e inferior de la vista de imagen, todo lo demás seguirá como se desee.
Estas capturas de pantalla muestran que este enfoque funciona:
(Desplazándose hacia abajo en la vista de tabla 🙂
No es 100% idéntica a la interfaz deseada, pero la idea es la misma. En cada celda tenemos dos etiquetas y una imagen, y las imágenes pueden tener diferentes relaciones de aspecto, pero esas relaciones de aspecto se muestran correctamente, y las propias celdas tienen diferentes alturas dependiendo de eso.
Este es el código clave que utilicé:
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! Cell
// in real life you’d set the labels here too
// in real life you’d be fetching the image from the network...
// ...and probably supplying it asynchronously later
let im = UIImage(named:self.pix[indexPath.row])!
cell.iv.image = im
let con = cell.heightConstraint!
con.isActive = false
let ratio = im.size.width/im.size.height
let newcon = NSLayoutConstraint(item: con.firstItem, attribute: con.firstAttribute, relatedBy: con.relation, toItem: con.secondItem, attribute: con.secondAttribute, multiplier: ratio, constant: 0)
newcon.isActive = true
cell.heightConstraint = newcon
return cell
Existe una solución sencilla para su problema si no desea cambiar su diseño.
1- define tu celda
2- poner el UIImageView
y otros elementos de la interfaz de usuario que le gusten dentro de su celda y agregue estas restricciones para la vista de la imagen: -top, inicial, final, inferior a supervista -restricciones de altura y agregue salida a su código (por ejemplo: heightConstraint)
3-Cambie el relleno de contenido a: ajuste de aspecto
4- Carga tus imágenes a través de Kingfisher o de cualquier otra forma que desees, una vez que pases tu imagen, verifica el tamaño y calcula la relación: imageAspectRatio = height / width
5-Establezca heightConstraint.constant = screenWidth * imageAspectRatio
¡LayoutIfNeeded () de 6 llamadas para la celda y debería estar bien!
* Esta solución funciona con cualquier composición de diseño de interfaz de usuario, incluidas las vistas de pila, el punto es tener una restricción en las imágenes y permitir que la vista de tabla descubra cómo calcular y dibujar restricciones.
class CustomTableViewCell: UITableViewCell {
@IBOutlet weak var heightConstraint: NSLayoutConstraint!
@IBOutlet weak var sampleImageView: UIImageView!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
func configure(image:UIImage) {
let hRatio = image.size.height / image.size.width
let newImageHeight = hRatio * UIScreen.main.bounds.width
heightConstraint.constant = newImageHeight
sampleImageView.image = image
sampleImageView.layoutIfNeeded()
}
}
Resultado: