Tengo una aplicación con una vista basada NSTableView
en ella. Dentro de esta vista de tabla, tengo filas que tienen celdas que tienen contenido que consta de varias filas NSTextField
con ajuste de palabras habilitado. Dependiendo del contenido textual del NSTextField
, el tamaño de las filas necesarias para mostrar la celda variará.
Sé que puedo implementar el NSTableViewDelegate
método, tableView:heightOfRow:
para devolver la altura, pero la altura se determinará en función del ajuste de palabras utilizado en NSTextField
. La palabra envoltura de NSTextField
se basa de manera similar en qué tan ancho NSTextField
es ... que está determinado por el ancho de NSTableView
.
Así que… supongo que mi pregunta es… ¿cuál es un buen patrón de diseño para esto? Parece que todo lo que intento termina siendo un lío complicado. Dado que TableView requiere conocimiento de la altura de las celdas para distribuirlas ... y el NSTextField
conocimiento de las necesidades de su diseño para determinar el ajuste de palabras ... y la celda necesita conocimiento del ajuste de palabras para determinar su altura ... es un lío circular ... y me está volviendo loco .
Sugerencias
Si importa, el resultado final también tendrá elementos editables NSTextFields
que cambiarán de tamaño para ajustarse al texto dentro de ellos. Ya tengo esto funcionando en el nivel de vista, pero la vista de tabla aún no ajusta las alturas de las celdas. Me imagino que una vez que resuelva el problema noteHeightOfRowsWithIndexesChanged
de la altura, usaré el método - para informar a la vista de la mesa que la altura cambió ... pero aún así le pedirá al delegado la altura ... de ahí, mi problema.
¡Gracias por adelantado!
fuente
Respuestas:
Este es el problema del huevo y la gallina. La tabla necesita conocer la altura de la fila porque eso determina dónde se ubicará una vista determinada. Pero desea que ya exista una vista para poder usarla para calcular la altura de la fila. Entonces, ¿qué viene primero?
La respuesta es mantener una vista adicional
NSTableCellView
(o cualquier vista que esté usando como su "vista de celda") alrededor solo para medir la altura de la vista. En eltableView:heightOfRow:
método de delegado, acceder a su modelo de 'fila' y establecer elobjectValue
sobreNSTableCellView
. Luego, establezca el ancho de la vista para que sea el ancho de su tabla y (como quiera hacerlo) calcule la altura requerida para esa vista. Devuelve ese valor.¡No llame
noteHeightOfRowsWithIndexesChanged:
desde el método delegadotableView:heightOfRow:
oviewForTableColumn:row:
! Eso es malo y causará grandes problemas.Para actualizar dinámicamente la altura, lo que debe hacer es responder al cambio de texto (a través del objetivo / acción) y volver a calcular la altura calculada de esa vista. Ahora, no cambie dinámicamente la
NSTableCellView
altura de (o cualquier vista que esté usando como su "vista de celda"). La tabla debe controlar el marco de esa vista, y lucharás contra la vista de tabla si intentas configurarla. En cambio, en su objetivo / acción para el campo de texto donde calculó la altura, llamenoteHeightOfRowsWithIndexesChanged:
, lo que permitirá que la tabla cambie el tamaño de esa fila individual. Suponiendo que haya configurado la máscara de tamaño automático en las subvistas (es decir, subvistas deNSTableCellView
), ¡las cosas deberían cambiar de tamaño bien! Si no es así, primero trabaje en la máscara de cambio de tamaño de las subvistas para hacer las cosas bien con alturas de fila variables.No olvides que se
noteHeightOfRowsWithIndexesChanged:
anima de forma predeterminada. Para que no sea animado:[NSAnimationContext beginGrouping]; [[NSAnimationContext currentContext] setDuration:0]; [tableView noteHeightOfRowsWithIndexesChanged:indexSet]; [NSAnimationContext endGrouping];
PD: Respondo más a las preguntas publicadas en los foros de desarrollo de Apple que al desbordamiento de pila.
PSS: escribí el NSTableView basado en vistas
fuente
tableView:heightOfRow:
, configuro mi vista de celda de repuesto con los valores de la fila (solo tengo una columna) y regresocellView.fittingSize.height
.fittingSize
es un método NSView que calcula el tamaño mínimo de la vista que satisface sus restricciones.Esto se hizo mucho más fácil en macOS 10.13 con
.usesAutomaticRowHeights
. Los detalles están aquí: https://developer.apple.com/library/content/releasenotes/AppKit/RN-AppKit/#10_13 (En la sección titulada "NSTableView Automatic Row Heights").Básicamente, simplemente seleccione su
NSTableView
oNSOutlineView
en el editor de guión gráfico y seleccione esta opción en el Inspector de tamaño:Luego, configura las cosas en su
NSTableCellView
para tener restricciones superiores e inferiores en la celda y su celda cambiará de tamaño para ajustarse automáticamente. ¡No se requiere código!Su aplicación ignorará las alturas especificadas en
heightOfRow
(NSTableView
) yheightOfRowByItem
(NSOutlineView
). Puede ver qué alturas se calculan para sus filas de diseño automático con este método:func outlineView(_ outlineView: NSOutlineView, didAdd rowView: NSTableRowView, forRow row: Int) { print(rowView.fittingSize.height) }
fuente
TableCellView
NO es editable. No importa está marcado en Atributos [Comportamiento: Editable] o Inspector de enlaces [Establecer editable condicionalmente: Sí]Basado en la respuesta de Corbin (por cierto, gracias por arrojar algo de luz sobre esto):
Swift 3, NSTableView basado en vistas con diseño automático para macOS 10.11 (y superior)
Mi configuración: tengo una
NSTableCellView
que se presenta usando Auto-Layout. Contiene (además de otros elementos) una multilíneaNSTextField
que puede tener hasta 2 filas. Por lo tanto, la altura de la vista de celda completa depende de la altura de este campo de texto.Actualizo le digo a la vista de tabla que actualice la altura en dos ocasiones:
1) Cuando la vista de tabla cambia de tamaño:
func tableViewColumnDidResize(_ notification: Notification) { let allIndexes = IndexSet(integersIn: 0..<tableView.numberOfRows) tableView.noteHeightOfRows(withIndexesChanged: allIndexes) }
2) Cuando cambia el objeto del modelo de datos:
tableView.noteHeightOfRows(withIndexesChanged: changedIndexes)
Esto hará que la vista de la tabla solicite a su delegado la nueva altura de fila.
func tableView(_ tableView: NSTableView, heightOfRow row: Int) -> CGFloat { // Get data object for this row let entity = dataChangesController.entities[row] // Receive the appropriate cell identifier for your model object let cellViewIdentifier = tableCellViewIdentifier(for: entity) // We use an implicitly unwrapped optional to crash if we can't create a new cell view var cellView: NSTableCellView! // Check if we already have a cell view for this identifier if let savedView = savedTableCellViews[cellViewIdentifier] { cellView = savedView } // If not, create and cache one else if let view = tableView.make(withIdentifier: cellViewIdentifier, owner: nil) as? NSTableCellView { savedTableCellViews[cellViewIdentifier] = view cellView = view } // Set data object if let entityHandler = cellView as? DataEntityHandler { entityHandler.update(with: entity) } // Layout cellView.bounds.size.width = tableView.bounds.size.width cellView.needsLayout = true cellView.layoutSubtreeIfNeeded() let height = cellView.fittingSize.height // Make sure we return at least the table view height return height > tableView.rowHeight ? height : tableView.rowHeight }
Primero, necesitamos obtener nuestro objeto modelo para la fila (
entity
) y el identificador de vista de celda apropiado. Luego verificamos si ya hemos creado una vista para este identificador. Para hacer eso, tenemos que mantener una lista con vistas de celda para cada identificador:// We need to keep one cell view (per identifier) around fileprivate var savedTableCellViews = [String : NSTableCellView]()
Si no se guarda ninguno, necesitamos crear (y almacenar en caché) uno nuevo. Actualizamos la vista de celda con nuestro objeto modelo y le decimos que vuelva a distribuir todo en función del ancho actual de la vista de tabla. La
fittingSize
altura se puede utilizar como nueva altura.fuente
cellView.bounds.size.width = tableView.bounds.size.width
, es una buena idea restablecer la altura a un número bajo. Si planea reutilizar esta vista ficticia, establecerla en un número bajo "restablecerá" el tamaño de ajuste.Para cualquiera que desee más código, aquí está la solución completa que utilicé. Gracias Corbin Dunn por señalarme en la dirección correcta.
Necesitaba establecer la altura principalmente en relación con la altura
NSTextView
de miNSTableViewCell
.En mi subclase de,
NSViewController
creo temporalmente una nueva celda llamandooutlineView:viewForTableColumn:item:
- (CGFloat)outlineView:(NSOutlineView *)outlineView heightOfRowByItem:(id)item { NSTableColumn *tabCol = [[outlineView tableColumns] objectAtIndex:0]; IBAnnotationTableViewCell *tableViewCell = (IBAnnotationTableViewCell*)[self outlineView:outlineView viewForTableColumn:tabCol item:item]; float height = [tableViewCell getHeightOfCell]; return height; } - (NSView *)outlineView:(NSOutlineView *)outlineView viewForTableColumn:(NSTableColumn *)tableColumn item:(id)item { IBAnnotationTableViewCell *tableViewCell = [outlineView makeViewWithIdentifier:@"AnnotationTableViewCell" owner:self]; PDFAnnotation *annotation = (PDFAnnotation *)item; [tableViewCell setupWithPDFAnnotation:annotation]; return tableViewCell; }
En mi
IBAnnotationTableViewCell
cuál es el controlador de mi celda (subclase deNSTableCellView
) tengo un método de configuración-(void)setupWithPDFAnnotation:(PDFAnnotation*)annotation;
que configura todos los puntos de venta y establece el texto de mis PDFAnnotations. Ahora puedo calcular "fácilmente" la altura usando:
-(float)getHeightOfCell { return [self getHeightOfContentTextView] + 60; } -(float)getHeightOfContentTextView { NSDictionary *attributes = [NSDictionary dictionaryWithObjectsAndKeys:[self.contentTextView font],NSFontAttributeName,nil]; NSAttributedString *attributedString = [[NSAttributedString alloc] initWithString:[self.contentTextView string] attributes:attributes]; CGFloat height = [self heightForWidth: [self.contentTextView frame].size.width forString:attributedString]; return height; }
.
- (NSSize)sizeForWidth:(float)width height:(float)height forString:(NSAttributedString*)string { NSInteger gNSStringGeometricsTypesetterBehavior = NSTypesetterLatestBehavior ; NSSize answer = NSZeroSize ; if ([string length] > 0) { // Checking for empty string is necessary since Layout Manager will give the nominal // height of one line if length is 0. Our API specifies 0.0 for an empty string. NSSize size = NSMakeSize(width, height) ; NSTextContainer *textContainer = [[NSTextContainer alloc] initWithContainerSize:size] ; NSTextStorage *textStorage = [[NSTextStorage alloc] initWithAttributedString:string] ; NSLayoutManager *layoutManager = [[NSLayoutManager alloc] init] ; [layoutManager addTextContainer:textContainer] ; [textStorage addLayoutManager:layoutManager] ; [layoutManager setHyphenationFactor:0.0] ; if (gNSStringGeometricsTypesetterBehavior != NSTypesetterLatestBehavior) { [layoutManager setTypesetterBehavior:gNSStringGeometricsTypesetterBehavior] ; } // NSLayoutManager is lazy, so we need the following kludge to force layout: [layoutManager glyphRangeForTextContainer:textContainer] ; answer = [layoutManager usedRectForTextContainer:textContainer].size ; // Adjust if there is extra height for the cursor NSSize extraLineSize = [layoutManager extraLineFragmentRect].size ; if (extraLineSize.height > 0) { answer.height -= extraLineSize.height ; } // In case we changed it above, set typesetterBehavior back // to the default value. gNSStringGeometricsTypesetterBehavior = NSTypesetterLatestBehavior ; } return answer ; }
.
- (float)heightForWidth:(float)width forString:(NSAttributedString*)string { return [self sizeForWidth:width height:FLT_MAX forString:string].height ; }
fuente
Estuve buscando una solución durante bastante tiempo y se me ocurrió la siguiente, que funciona muy bien en mi caso:
- (double)tableView:(NSTableView *)tableView heightOfRow:(long)row { if (tableView == self.tableViewTodo) { CKRecord *record = [self.arrayTodoItemsFiltered objectAtIndex:row]; NSString *text = record[@"title"]; double someWidth = self.tableViewTodo.frame.size.width; NSFont *font = [NSFont fontWithName:@"Palatino-Roman" size:13.0]; NSDictionary *attrsDictionary = [NSDictionary dictionaryWithObject:font forKey:NSFontAttributeName]; NSAttributedString *attrString = [[NSAttributedString alloc] initWithString:text attributes:attrsDictionary]; NSRect frame = NSMakeRect(0, 0, someWidth, MAXFLOAT); NSTextView *tv = [[NSTextView alloc] initWithFrame:frame]; [[tv textStorage] setAttributedString:attrString]; [tv setHorizontallyResizable:NO]; [tv sizeToFit]; double height = tv.frame.size.height + 20; return height; } else { return 18; } }
fuente
Como uso personalizado
NSTableCellView
y tengo acceso al,NSTextField
mi solución fue agregar un método enNSTextField
.@implementation NSTextField (IDDAppKit) - (CGFloat)heightForWidth:(CGFloat)width { CGSize size = NSMakeSize(width, 0); NSFont* font = self.font; NSDictionary* attributesDictionary = [NSDictionary dictionaryWithObject:font forKey:NSFontAttributeName]; NSRect bounds = [self.stringValue boundingRectWithSize:size options:NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading attributes:attributesDictionary]; return bounds.size.height; } @end
fuente
¿Ha echado un vistazo a RowResizableViews ? Es bastante antiguo y no lo he probado, pero es posible que funcione.
fuente
Esto es lo que hice para solucionarlo:
Fuente: busque en la documentación de XCode, en "altura de fila nstableview". Encontrará un código fuente de muestra llamado "TableViewVariableRowHeights / TableViewVariableRowHeightsAppDelegate.m"
(Nota: estoy mirando la columna 1 en la vista de tabla, tendrá que ajustar para buscar en otra parte)
en Delegate.h
IBOutlet NSTableView *ideaTableView;
en Delegate.m
la vista de tabla delega el control de la altura de la fila
- (CGFloat)tableView:(NSTableView *)tableView heightOfRow:(NSInteger)row { // Grab the fully prepared cell with our content filled in. Note that in IB the cell's Layout is set to Wraps. NSCell *cell = [ideaTableView preparedCellAtColumn:1 row:row]; // See how tall it naturally would want to be if given a restricted with, but unbound height CGFloat theWidth = [[[ideaTableView tableColumns] objectAtIndex:1] width]; NSRect constrainedBounds = NSMakeRect(0, 0, theWidth, CGFLOAT_MAX); NSSize naturalSize = [cell cellSizeForBounds:constrainedBounds]; // compute and return row height CGFloat result; // Make sure we have a minimum height -- use the table's set height as the minimum. if (naturalSize.height > [ideaTableView rowHeight]) { result = naturalSize.height; } else { result = [ideaTableView rowHeight]; } return result; }
también necesita esto para afectar la nueva altura de fila (método delegado)
- (void)controlTextDidEndEditing:(NSNotification *)aNotification { [ideaTableView reloadData]; }
Espero que esto ayude.
Nota final: esto no admite el cambio de ancho de columna.
fuente
-[NSTableView preparedCellAtColumn:row]
, que los documentos dicen "solo están disponibles para las vistas de tabla basadas en NSCell". El uso de este método en una tabla basada en vistas produce esta salida de registro en tiempo de ejecución: "Error de vista NSTableView basada en vista: se llamó a la columna preparadaCellAt: fila:. Registre un error con el seguimiento de este registro o deje de usar el método".Aquí hay una solución basada en la respuesta de JanApotheker, modificada porque
cellView.fittingSize.height
no me devolvía la altura correcta. En mi caso, estoy usando el estándarNSTableCellView
, unNSAttributedString
para el texto del campo de texto de la celda y una tabla de una sola columna con restricciones para el campo de texto de la celda establecido en IB.En mi controlador de vista, declaro:
var tableViewCellForSizing: NSTableCellView?
En viewDidLoad ():
tableViewCellForSizing = tableView.make(withIdentifier: "My Identifier", owner: self) as? NSTableCellView
Finalmente, para el método delegado tableView:
func tableView(_ tableView: NSTableView, heightOfRow row: Int) -> CGFloat { guard let tableCellView = tableViewCellForSizing else { return minimumCellHeight } tableCellView.textField?.attributedStringValue = attributedString[row] if let height = tableCellView.textField?.fittingSize.height, height > 0 { return height } return minimumCellHeight }
mimimumCellHeight
es una constante establecida en 30, para respaldo, pero nunca se usa realmente.attributedStrings
es mi matriz modelo deNSAttributedString
.Esto funciona perfectamente para mis necesidades. Gracias por todas las respuestas anteriores, que me indicaron la dirección correcta para este molesto problema.
fuente
Esto suena mucho a algo que tenía que hacer anteriormente. Ojalá pudiera decirles que se me ocurrió una solución simple y elegante, pero, por desgracia, no lo hice. Sin embargo, no por falta de intentos. Como ya ha notado, la necesidad de UITableView de conocer la altura antes de que se construyan las celdas realmente hace que todo parezca bastante circular.
Mi mejor solución fue llevar la lógica a la celda, porque al menos podía aislar qué clase necesitaba para comprender cómo se distribuían las celdas. Un método como
+ (CGFloat) heightForStory:(Story*) story
podría determinar la altura que tenía que tener la celda. Por supuesto, eso implicaba medir texto, etc. En algunos casos, ideé formas de almacenar en caché la información obtenida durante este método que luego podría usarse cuando se creó la celda. Eso fue lo mejor que se me ocurrió. Es un problema exasperante, aunque parece que debería haber una mejor respuesta.
fuente