iphone - ejemplo - uitableview tutorial swift 4
UITableView flexible/dynamic heightForRowAtIndexPath (7)
Sí, acepto que ya asignará la celda, pero el método delegado heightForRowAtIndexPath solo se llama para las celdas que serán visibles, por lo que la celda se asignará de todos modos.
Esto es incorrecto. La vista de tabla necesita llamar a heightForRowAtIndexPath
(si está implementado) para todas las filas que están en la vista de tabla, no solo las que se muestran actualmente. La razón es que necesita calcular su altura total para mostrar los indicadores de desplazamiento correctos.
Caso
Normalmente, usaría el método de delegado cellForRowAtIndexPath
para configurar su celda. La información configurada para la celda es importante para saber cómo se dibuja la celda y cuál será el tamaño.
Desafortunadamente, se llama al método delegado cellForRowAtIndexPath
antes del método de delegado cellForRowAtIndexPath
, por lo que no podemos simplemente decirle al delegado que devuelva la altura de la celda, ya que será cero en ese momento.
Entonces, debemos calcular el tamaño antes de que la celda se dibuje en la tabla. Afortunadamente hay un método que hace precisamente eso, sizeWithFont
, que pertenece a la clase NSString. Sin embargo, hay un problema, para calcular el tamaño correcto de forma dinámica, necesita saber cómo se presentarán los elementos en la celda. Voy a dejar esto en claro en un ejemplo:
Imagine una UITableViewCell
, que contiene una etiqueta llamada textLabel
. Dentro del método de delegado cellForRowAtIndexPath
colocamos textLabel.numberOfLines = 0
, que básicamente le dice a la etiqueta que puede tener tantas líneas como sea necesario para presentar el texto para un ancho específico. El problema ocurre si le damos a textLabel un texto más grande que el ancho dado originalmente a textLabel. Aparecerá la segunda línea, pero la altura de la celda no se ajustará automáticamente y obtendremos una vista de tabla desordenada.
Como dije antes, podemos usar sizeWithFont
para calcular la altura, pero necesita saber qué fuente se usa, para qué ancho, etc. Si, por razones de simplicidad, nos importa el ancho, podríamos codificar que el ancho sería alrededor de 320.0 (sin tomar en consideración el relleno). Pero lo que sucedería si utilizáramos UITableViewStyleGrouped en vez de simplemente el ancho sería alrededor de 300.0 y la celda volvería a estar en mal estado. O lo que sucede si cambiamos de retrato a paisaje, tenemos mucho más espacio, pero no se usará ya que codificamos 300.0.
Este es el caso en el que en algún momento debe hacerse la pregunta de cuánto puede evitar la codificación rígida.
Mis Propios Pensamientos
Puede llamar al método cellForRowAtIndexPath
que pertenece a la clase UITableView para obtener la celda de una determinada sección y fila. Leí un par de publicaciones que decían que no querías hacer eso, pero realmente no entiendo eso. Sí, acepto que ya asignará la celda, pero el método delegado heightForRowAtIndexPath
solo se llama para las celdas que serán visibles, por lo que la celda se asignará de todos modos. Si usa correctamente el dequeueReusableCellWithIdentifier
la celda no se asignará nuevamente en el método cellForRowAtIndexPath
, sino que se utiliza un puntero y las propiedades se ajustan. ¿Entonces, cuál es el problema?
Tenga en cuenta que la celda NO se dibuja dentro del método de delegado cellForRowAtIndexPath
, cuando la celda de la vista de tabla se vuelve visible, la secuencia de comandos llamará al método setNeedDisplay
en la UITableVieCell que activa el método drawRect
para dibujar la celda. Por lo tanto, llamar al delegado cellForRowAtIndexPath
directamente no perderá rendimiento porque debe dibujarse dos veces.
De acuerdo, al llamar al método de delegado cellForRowAtIndexPath
dentro del método de delegado heightForRowAtIndexPath
, recibimos toda la información que necesitamos sobre la celda para determinar su tamaño.
Tal vez pueda crear su propio método sizeForCell
que se ejecute a través de todas las opciones, qué sucede si la celda está en el estilo Value1, o Value2, etc.
Conclusión / Pregunta
Es solo una teoría que describí en mis pensamientos. Me gustaría saber si lo que escribí es correcto. O que tal vez haya otra forma de lograr lo mismo. Tenga en cuenta que quiero ser lo más flexible posible.
Aquí está mi enfoque para resolver esto
- Supongo en esta solución que solo una etiqueta tiene una altura "dinámica"
- También asumo que si hacemos el tamaño automático de la etiqueta para estirar la altura a medida que la celda crece, solo se necesita la altura de la celda para cambiar
- Supongo que la punta tiene el espacio apropiado para donde estará la etiqueta y cuánto espacio hay arriba y debajo de ella
- No queremos cambiar el código cada vez que cambiamos la fuente o posición de la etiqueta en la punta
Cómo actualizar la altura:
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
// We want the UIFont to be the same as what is in the nib,
// but we dont want to call tableView dequeue a bunch because its slow.
// If we make the font static and only load it once we can reuse it every
// time we get into this method
static UIFont* dynamicTextFont;
static CGRect textFrame;
static CGFloat extraHeight;
if( !dynamicTextFont ) {
DetailCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"];
dynamicTextFont = cell.resizeLabel.font;
CGRect cellFrame = cell.frame;
textFrame = cell.resizeLabel.frame;
extraHeight = cellFrame.size.height-textFrame.size.height; // The space above and below the growing field
}
NSString* text = .... // Get this from the some object using indexPath
CGSize size = [text sizeWithFont:dynamicTextFont constrainedToSize:CGSizeMake(textFrame.size.width, 200000.f) lineBreakMode:UILineBreakModeWordWrap];
return size.height+extraHeight;
}
Cuestiones:
- Si no está utilizando una celda prototipo, necesitará verificar si la celda es nula e iniciarla
- Su plumilla / guión gráfico debe tener UILabel autosize y tener múltiples líneas configuradas en 0
Aquí está mi solución, que he utilizado para implementar algunas celdas bastante hábiles para una aplicación de chat.
Hasta este punto, siempre he estado realmente irritado con heightForCellAtIndexPath: porque lleva a violar el principio DRY. Con esta solución, my heightForRowAtIndexPath: cuesta 1,5 ms por célula, lo que podría reducir a ~ 1 ms.
Básicamente, desea que cada subvista dentro de su celda implemente sizeThatFits: cree una celda fuera de pantalla que configure y luego consulte la vista raíz con sizeThatFits: CGSizeMake (tableViewWidth, CGFLOAT_MAX).
Hay algunos problemas en el camino. Algunas vistas de UIKit tienen costosas operaciones de configuración. Por ejemplo, [UITextView setText] hace mucho trabajo. El truco aquí es crear una subclase, almacenar la variable en el búfer y luego anular setNeedsDisplay para llamar a [super setText:] cuando la vista esté a punto de ser renderizada. Por supuesto, tendrás que implementar tu propio tamañoThatFits: usando las extensiones de UIKit.
Debería echarle un vistazo a TTTableItemCell.m
en el marco Three20. Sigue un enfoque diferente, básicamente haciendo que cada clase de celda (con algunas configuraciones predefinidas como fuente, diseño, etc.) implemente un método compartido + tableView: sizeForItem:
(o algo así), donde se pasa el texto en el objeto de elemento . Cuando busca el texto de una celda específica, también puede buscar la fuente apropiada.
Con respecto a la altura de la celda: puede verificar el ancho de su tabla y, si es necesario, restar los márgenes por UITableViewStyleGrouped
y el ancho de una barra de índice eventual y elemento de divulgación (que busca en el almacenamiento de datos de sus celdas). Cuando el ancho de la tablaView cambia, por ejemplo, mediante la rotación de la interfaz, debe llamar a [tableView reloadData]
.
Para responder a la pregunta que hizo el cartel original que decía ''¿está bien llamar a cellForRowAtIndexPath?'', No es así. Eso le dará una celda, pero NO la asignará a ese indexPath internamente ni se volverá a poner en cola (no hay método para volver a colocarla), por lo que la perderá. Supongo que estará en un grupo de autorrelease y será desasignado eventualmente, pero aún crearás montones de células una y otra vez, y eso es realmente un desperdicio.
Puede hacer alturas de celda dinámicas, incluso puede hacer que se vean muy bien, pero es mucho trabajo realmente hacer que se vean sin problemas, incluso más si desea admitir orientaciones múltiples, etc.
Solía hacer esto por:
Creación de objetos de colección (matriz de información de tamaño (diccionario, NSNúmero de alturas de fila, etc.) en función de los objetos de colección que se utilizarán para la vista de tabla.
Esto se hace cuando procesamos los datos desde una fuente local o remota.
Preterminé el tipo y el tamaño de la fuente que se utilizará cuando creo estos objetos de colección. Incluso puede almacenar los objetos UIFont o cualquier objeto personalizado utilizado para representar el contenido.
Estos objetos de colección se usarán cada vez que implemente los protocolos UITableViewDataSource o UITableViewDelegate para determinar los tamaños de las instancias de UITableViewCell y sus subvistas, etc.
Al hacerlo de esta manera, puede evitar tener que subclasificar UITableViewCell solo para obtener las diversas propiedades de tamaño de su contenido.
No use un valor absoluto para inicializar los marcos. Use un valor relativo basado en la orientación y los límites actuales.
Si lo rotamos a cualquier orientación, simplemente haga un mecanismo de cambio de tamaño en el tiempo de ejecución. Asegúrese de que el autoresizingMask esté configurado correctamente.
Solo necesita las alturas, no necesita todas las cosas innecesarias dentro de una UITableViewCell para determinar la altura de la fila. Puede que ni siquiera necesite el ancho, porque como dije, el valor del ancho debe ser relativo a los límites de la vista.
Tengo una idea sobre la altura de la celda dinámica.
Simplemente cree una instancia de su celda personalizada como variable miembro de UITableViewController
. En tableView:heightForRowAtIndexPath:
method establece el contenido de la celda y devuelve la altura de la celda.
De esta forma, no creará / liberará automáticamente la celda varias veces como lo hará si llama a cellForRowAtIndexPath
dentro del método heightForRowAtIndexPath
.
UPD: para su comodidad, también puede crear un método estático en su clase de celda personalizada que creará una instancia de celda única para el cálculo de altura, establecerá el contenido de la celda y luego devolverá su altura.
tableView:heightForRowAtIndexPath:
cuerpo de la función se verá así:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return [MyCell cellHeightForContent:yourContent];
}