ios uikit autolayout size-classes

ios - ¿UITableViewCells fuera de pantalla(para cálculos de tamaño) no respeta la clase de tamaño?



uikit autolayout (2)

Estoy usando Auto Layout y clases de tamaño dentro de UITableView con celdas que se auto dimensionan en función de su contenido. Para esto, estoy usando el método donde para cada tipo de celda, mantienes una instancia fuera de la pantalla de esa celda y usas systemLayoutSizeFittingSize para determinar la altura correcta de la fila: este método se explica maravillosamente en esta publicación de StackOverflow y en elsewhere .

Esto funcionó muy bien hasta que comencé a usar clases de tamaño. Específicamente, he definido diferentes constantes en las restricciones de margen para el texto en diseños de Ancho regular, por lo que hay más espacios en blanco alrededor del texto en el iPad. Esto me da los siguientes resultados.

Parece que se está cumpliendo el nuevo conjunto de restricciones (hay más espacios en blanco), pero que el cálculo de la altura de la fila aún devuelve el mismo valor que para una celda que no aplicó las restricciones específicas del tamaño de la clase. Parte del proceso de diseño en la celda fuera de pantalla no tiene en cuenta la clase de tamaño de la ventana .

Ahora supuse que probablemente se deba a que la vista fuera de pantalla no tiene una supervista o ventana, y como tal no tiene rasgos de clase de tamaño a los que hacer referencia en el momento en que se produce la llamada systemLayoutSizeFittingSize (aunque parece utilizar las restricciones ajustadas para los márgenes) Ahora evito esto agregando la celda de tamaño fuera de pantalla como una subvista de la UIWindow después de que se crea, lo que da el resultado deseado:

Esto es lo que estoy haciendo en código:

func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat { let contentItem = content[indexPath.item] if let contentType = contentItem["type"] { // Get or create the cached layout cell for this cell type. if layoutCellCache.indexForKey(contentType) == nil { if let cellIdentifier = CellIdentifiers[contentType] { if var cachedLayoutCell = dequeueReusableCellWithIdentifier(cellIdentifier) as? UITableViewCell { UIApplication.sharedApplication().keyWindow?.addSubview(cachedLayoutCell) cachedLayoutCell.hidden = true layoutCellCache[contentType] = cachedLayoutCell } } } if let cachedLayoutCell = layoutCellCache[contentType] { // Configure the layout cell with the requested cell''s content. configureCell(cachedLayoutCell, withContentItem: contentItem) // Perform layout on the cached cell and determine best fitting content height. cachedLayoutCell.bounds = CGRectMake(0.0, 0.0, CGRectGetWidth(tableView.bounds), 0); cachedLayoutCell.setNeedsLayout() cachedLayoutCell.layoutIfNeeded() return cachedLayoutCell.contentView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize).height } } fatalError("not enough information to determine cell height for item /(indexPath.item).") return 0 }

Agregar vistas a la ventana que nunca se supone que deben dibujarse me parece un truco. ¿Hay alguna manera de que las UIViews adopten completamente la clase de tamaño de la ventana incluso cuando no están actualmente en la jerarquía de vistas? ¿O hay algo más que me estoy perdiendo? Gracias.


Actualización en diciembre de 2015:

Apple ahora desaconseja anular -traitCollection . Por favor considere usar otras soluciones. Del -traitCollection :

IMPORTANTE

Use la propiedad traitCollection directamente. No lo anule. No proporcione una implementación personalizada.

Respuesta original

La respuesta existente es genial. Explicó que el problema es que:

La solución propuesta es agregar temporalmente la celda a la vista de tabla. Sin embargo, esto NO funciona si estamos en, digamos, -viewDidLoad , en el cual la traitCollection de la vista de tabla, o la vista del controlador de vista, o incluso el controlador de vista en sí, aún no es válida.

Aquí, propongo otra solución, que es anular traitCollection de la celda. Para hacerlo:

  1. Cree una subclase personalizada de UITableViewCell para la celda (lo que probablemente ya haya hecho).

  2. En la subclase personalizada, agregue un método - (UITraitCollection *)traitCollection , que anula el captador de la propiedad traitCollection . Ahora, puede devolver cualquier UITraitCollection válida que UITraitCollection . Aquí hay una implementación de muestra:

    // Override getter of traitCollection property // https://.com/a/28514006/1402846 - (UITraitCollection *)traitCollection { // Return original value if valid. UITraitCollection* originalTraitCollection = [super traitCollection]; if(originalTraitCollection && originalTraitCollection.userInterfaceIdiom != UIUserInterfaceIdiomUnspecified) { return originalTraitCollection; } // Return trait collection from UIScreen. return [UIScreen mainScreen].traitCollection; }

    Alternativamente, puede devolver una UITraitCollection adecuada creada utilizando cualquiera de sus métodos de creación , por ejemplo:

    + (UITraitCollection *)traitCollectionWithDisplayScale:(CGFloat)scale + (UITraitCollection *)traitCollectionWithTraitsFromCollections:(NSArray *)traitCollections + (UITraitCollection *)traitCollectionWithUserInterfaceIdiom:(UIUserInterfaceIdiom)idiom + (UITraitCollection *)traitCollectionWithHorizontalSizeClass:(UIUserInterfaceSizeClass)horizontalSizeClass + (UITraitCollection *)traitCollectionWithVerticalSizeClass:(UIUserInterfaceSizeClass)verticalSizeClass

    O incluso puede hacerlo más flexible haciendo esto:

    // Override getter of traitCollection property // https://.com/a/28514006/1402846 - (UITraitCollection *)traitCollection { // Return overridingTraitCollection if not nil, // or [super traitCollection] otherwise. // overridingTraitCollection is a writable property return self.overridingTraitCollection ?: [super traitCollection]; }

Esta solución es compatible con iOS 7 porque la propiedad traitCollection se define en iOS 8+, por lo que, en iOS 7, nadie llamará a su getter, y por lo tanto a nuestro método de anulación.


Pasé días en esto después de pasar a usar clases de tamaño para facilitar el cambio de tamaño de fuente en iPad frente a iPhone, etc.

La raíz del problema parece ser que dequeueReusableCellWithIdentifier: devuelve una celda que no tiene una supervista de la que obtiene su UITraitCollection . dequeueReusableCellWithIdentifier:forIndexPath: por otro lado, devuelve una celda cuya supervista es un UITableViewWrapperView .

He planteado un informe de error con Apple, ya que no han ampliado este método para admitir clases de tamaño; Parece que no está documentado cómo lidiar con las clases de tamaño en iOS7. Cuando envía un mensaje a un UITableView solicitando una celda, debe devolver una que refleje la clase de tamaño de la tabla a la que envía el mensaje. Este es el caso de dequeueReusableCellWithIdentifier:forIndexPath:

También he notado que, al intentar usar el nuevo mecanismo de diseño automático, a menudo necesita volver a cargar la tabla en viewDidAppear: para que el nuevo mecanismo funcione correctamente. Sin esto, veo el mismo problema que tengo al usar el enfoque iOS7.

Por lo que puedo decir, no parece posible usar el diseño automático en iOS8 y el viejo mecanismo para iOS7 desde el mismo código.

Por ahora, he tenido que recurrir a solucionar el problema agregando la celda prototipo como una subvista de la tabla, haciendo el cálculo del tamaño y luego eliminándolo:

UITableViewCell *prototype=nil; CGFloat prototypeHeight=0.0; prototype=[self.tableView dequeueReusableCellWithIdentifier:@"SideMenuCellIdentifier"]; // Check for when the prototype cell has no parent view from // which to inherit size class related constraints. BOOL added=FALSE; if (prototype.superview == nil){ [self.tableView addSubview:prototype]; added=TRUE; } <snip ... Setup prototype cell> [prototype setNeedsLayout]; [prototype layoutIfNeeded]; CGSize size = [prototype.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize]; prototypeHeight=size.height+1; // Add one for separator // Remove the cell if added. Leaves it when in iOS7. if (added){ [prototype removeFromSuperview]; }

La configuración relacionada con la clase de tamaño parece controlarse a través de UITraitCollection que es una propiedad de solo lectura de un UIViewController . Para la compatibilidad con versiones anteriores de iOS7, esto parece ser manejado por el sistema de compilación como una solución con algunas restricciones. es decir, en iOS7 no puede acceder a la -traitCollection , pero puede hacerlo en iOS8.

Dado el estrecho acoplamiento con el controlador de vista del guión gráfico y cómo funciona la compatibilidad con versiones anteriores, parece que la celda prototipo tendrá que estar en la jerarquía del controlador de vista que definió en Xcode.

Hay una discusión sobre esto aquí:
¿Cómo pueden las interfaces de usuario adaptativas de Xcode 6 ser compatibles con iOS 7 y iOS 6?