ios swift uitableview mongolian-vertical-script

ios - UITableView con altura de celda variable: trabajando en IB pero no mediante programación



swift mongolian-vertical-script (5)

Creo que falta para establecer restricciones para tableView con superview. Y tratar de aumentar la altura estimada de la fila también.

TL; DR

Las celdas de vista de tabla creadas de manera programática no cambian de tamaño según la altura del contenido intrínseco de sus vistas personalizadas, aunque estoy utilizando UITableViewAutomaticDimension y configurando las restricciones superior e inferior.

El problema probablemente radica en mi implementación de la subclase UITableViewCell . Vea el código a continuación en No funciona mediante programación> Código> MyCustomCell.swift .

Gol

Estoy tratando de hacer una barra de sugerencias para un teclado mongol personalizado. El mongol se escribe verticalmente. En Android se ve así:

Progreso

Aprendí que debería usar un UITableView con alturas de celda variables, que está disponible a partir de iOS 8. Esto requiere usar el diseño automático y decirle a la vista de tabla que use dimensiones automáticas para las alturas de celda.

Algunas de las cosas que he tenido que aprender en el camino están representadas en mis recientes preguntas y respuestas de SO:

Así que he llegado al punto en que tengo las etiquetas verticales que admiten el tamaño del contenido intrínseco. Estas etiquetas van en mis celdas de vista de tabla personalizadas. Y como se describe en la siguiente sección, funcionan cuando lo hago en el guión gráfico, pero no cuando creo todo programáticamente.

Trabaja en IB

Para aislar el problema, creé dos proyectos básicos: uno para el que utilizo el guión gráfico y otro donde hago todo programáticamente. El proyecto de guión gráfico funciona. Como se puede ver en la siguiente imagen, cada celda de vista de tabla cambia de tamaño para coincidir con la altura de la etiqueta vertical personalizada.

En IB

Establecí restricciones para fijar la parte superior e inferior, así como centrar la etiqueta.

Código

ViewController.swift

import UIKit class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource { let myStrings: [String] = ["a", "bbbbbbb", "cccc", "dddddddddd", "ee"] let cellReuseIdentifier = "cell" @IBOutlet var tableView: UITableView! override func viewDidLoad() { super.viewDidLoad() tableView.delegate = self tableView.dataSource = self tableView.estimatedRowHeight = 44.0 tableView.rowHeight = UITableViewAutomaticDimension } // number of rows in table view func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return self.myStrings.count } // create a cell for each table view row func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { let cell:MyCustomCell = self.tableView.dequeueReusableCellWithIdentifier(cellReuseIdentifier) as! MyCustomCell cell.myCellLabel.text = self.myStrings[indexPath.row] return cell } // method to run when table view cell is tapped func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) { print("You tapped cell number /(indexPath.row).") } }

MyCustomCell.swift

import UIKit class MyCustomCell: UITableViewCell { @IBOutlet weak var myCellLabel: UIMongolSingleLineLabel! }

No funciona programáticamente

Como quiero que la barra de sugerencias forme parte del teclado final, necesito poder crearla mediante programación. Sin embargo, cuando intento recrear el proyecto de ejemplo anterior mediante programación, no funciona. Me sale el siguiente resultado.

Las alturas de las celdas no cambian de tamaño y las etiquetas verticales personalizadas se solapan entre sí.

También me sale el siguiente error:

Solo advertencia una vez: detectó un caso en el que las restricciones sugieren de manera ambigua una altura de cero para la vista de contenido de una celda de vista de tabla. Estamos considerando el colapso involuntario y en su lugar estamos usando una altura estándar.

Este error se ha detectado varias veces en Desbordamiento de pila:

Sin embargo, el problema para la mayoría de esas personas es que no estaban configurando una restricción de pasador superior e inferior. Soy, o al menos creo que lo soy, como se muestra en mi código a continuación.

Código

ViewController.swift

import UIKit class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource { let myStrings: [String] = ["a", "bbbbbbb", "cccc", "dddddddddd", "ee"] let cellReuseIdentifier = "cell" var tableView = UITableView() override func viewDidLoad() { super.viewDidLoad() // Suggestion bar tableView.frame = CGRect(x: 0, y: 20, width: view.bounds.width, height: view.bounds.height) tableView.registerClass(MyCustomCell.self, forCellReuseIdentifier: cellReuseIdentifier) tableView.delegate = self tableView.dataSource = self tableView.estimatedRowHeight = 44.0 tableView.rowHeight = UITableViewAutomaticDimension view.addSubview(tableView) } // number of rows in table view func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return self.myStrings.count } // create a cell for each table view row func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { let cell:MyCustomCell = self.tableView.dequeueReusableCellWithIdentifier(cellReuseIdentifier) as! MyCustomCell cell.myCellLabel.text = self.myStrings[indexPath.row] return cell } // method to run when table view cell is tapped func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) { print("You tapped cell number /(indexPath.row).") } }

MyCustomCell.swift

Creo que el problema está probablemente aquí, ya que esta es la principal diferencia con el proyecto IB.

import UIKit class MyCustomCell: UITableViewCell { var myCellLabel = UIMongolSingleLineLabel() override init(style: UITableViewCellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) self.setup() } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } func setup() { self.myCellLabel.translatesAutoresizingMaskIntoConstraints = false self.myCellLabel.centerText = false self.myCellLabel.backgroundColor = UIColor.yellowColor() self.addSubview(myCellLabel) // Constraints // pin top NSLayoutConstraint(item: myCellLabel, attribute: NSLayoutAttribute.Top, relatedBy: NSLayoutRelation.Equal, toItem: self.contentView, attribute: NSLayoutAttribute.TopMargin, multiplier: 1.0, constant: 0).active = true // pin bottom NSLayoutConstraint(item: myCellLabel, attribute: NSLayoutAttribute.Bottom, relatedBy: NSLayoutRelation.Equal, toItem: self.contentView, attribute: NSLayoutAttribute.BottomMargin, multiplier: 1.0, constant: 0).active = true // center horizontal NSLayoutConstraint(item: myCellLabel, attribute: NSLayoutAttribute.CenterX, relatedBy: NSLayoutRelation.Equal, toItem: self.contentView, attribute: NSLayoutAttribute.CenterX, multiplier: 1, constant: 0).active = true } override internal class func requiresConstraintBasedLayout() -> Bool { return true } }

Código suplementario

También incluiré el código para la etiqueta vertical personalizada que utilicé en los dos proyectos anteriores, pero como el proyecto IB funciona, no creo que el problema principal esté aquí.

import UIKit @IBDesignable class UIMongolSingleLineLabel: UIView { private let textLayer = LabelTextLayer() var useMirroredFont = false // MARK: Primary input value @IBInspectable var text: String = "A" { didSet { textLayer.displayString = text updateTextLayerFrame() } } @IBInspectable var fontSize: CGFloat = 17 { didSet { updateTextLayerFrame() } } @IBInspectable var centerText: Bool = true { didSet { updateTextLayerFrame() } } // MARK: - Initialization override init(frame: CGRect) { super.init(frame: frame) setup() } required init?(coder: NSCoder) { super.init(coder: coder) setup() } func setup() { // Text layer textLayer.backgroundColor = UIColor.yellowColor().CGColor textLayer.useMirroredFont = useMirroredFont textLayer.contentsScale = UIScreen.mainScreen().scale layer.addSublayer(textLayer) } override func intrinsicContentSize() -> CGSize { return textLayer.frame.size } func updateTextLayerFrame() { let myAttribute = [ NSFontAttributeName: UIFont.systemFontOfSize(fontSize) ] let attrString = NSMutableAttributedString(string: textLayer.displayString, attributes: myAttribute ) let size = dimensionsForAttributedString(attrString) // This is the frame for the soon-to-be rotated layer var x: CGFloat = 0 var y: CGFloat = 0 if layer.bounds.width > size.height { x = (layer.bounds.width - size.height) / 2 } if centerText { y = (layer.bounds.height - size.width) / 2 } textLayer.frame = CGRect(x: x, y: y, width: size.height, height: size.width) textLayer.string = attrString invalidateIntrinsicContentSize() } func dimensionsForAttributedString(attrString: NSAttributedString) -> CGSize { var ascent: CGFloat = 0 var descent: CGFloat = 0 var width: CGFloat = 0 let line: CTLineRef = CTLineCreateWithAttributedString(attrString) width = CGFloat(CTLineGetTypographicBounds(line, &ascent, &descent, nil)) // make width an even integer for better graphics rendering width = ceil(width) if Int(width)%2 == 1 { width += 1.0 } return CGSize(width: width, height: ceil(ascent+descent)) } } // MARK: - Key Text Layer Class class LabelTextLayer: CATextLayer { // set this to false if not using a mirrored font var useMirroredFont = true var displayString = "" override func drawInContext(ctx: CGContext) { // A frame is passed in, in which the frame size is already rotated at the center but the content is not. CGContextSaveGState(ctx) if useMirroredFont { CGContextRotateCTM(ctx, CGFloat(M_PI_2)) CGContextScaleCTM(ctx, 1.0, -1.0) } else { CGContextRotateCTM(ctx, CGFloat(M_PI_2)) CGContextTranslateCTM(ctx, 0, -self.bounds.width) } super.drawInContext(ctx) CGContextRestoreGState(ctx) } }

Actualizar

El código completo para el proyecto está aquí, por lo que si alguien está lo suficientemente interesado como para probarlo, simplemente cree un nuevo proyecto y corte y pegue el código anterior en los siguientes tres archivos:

  • ViewController.swift
  • MyCustomCell.swift
  • UIMongolSingleLineLabel.swift

El error es bastante trivial:

En lugar de

self.addSubview(myCellLabel)

utilizar

self.contentView.addSubview(myCellLabel)

Además, me gustaría reemplazar

// pin top NSLayoutConstraint(...).active = true // pin bottom NSLayoutConstraint(...).active = true // center horizontal NSLayoutConstraint(...).active = true

con

let topConstraint = NSLayoutConstraint(...) let bottomConstraint = NSLayoutConstraint(...) let centerConstraint = NSLayoutConstraint(...) self.contentView.addConstraints([topConstraint, bottomConstraint, centerConstraint])

que es más explícito (tiene que especificar el propietario de la restricción) y, por lo tanto, más seguro.

El problema es que cuando se llama a active = true en una restricción, el sistema de diseño debe decidir a qué vista debe agregar las restricciones. En su caso, debido a que el primer antecesor común de contentView y myCellLabel es su UITableViewCell , se agregaron a su UITableViewCell , por lo que en realidad no restringieron el contentView (las restricciones fueron entre hermanos y no entre supervisión y subvista).

Su código realmente activó una advertencia de consola:

Solo advertencia una vez: detectó un caso en el que las restricciones sugieren de manera ambigua una altura de cero para la vista de contenido de una celda de vista de tabla. Estamos considerando el colapso involuntario y en su lugar estamos usando una altura estándar.

Lo que me hizo ver de inmediato la forma en que se crean las restricciones para su etiqueta.


El problema parece provenir de las restricciones verticales en la celda. Poniéndolos en relación con uno mismo en lugar de uno mismo. Contenido en MyCustomCell puede solucionar su problema

NSLayoutConstraint(item: myCellLabel, attribute: NSLayoutAttribute.Top, relatedBy: NSLayoutRelation.Equal, toItem: self, attribute: NSLayoutAttribute.TopMargin, multiplier: 1.0, constant: 0).active = true // pin bottom NSLayoutConstraint(item: myCellLabel, attribute: NSLayoutAttribute.Bottom, relatedBy: NSLayoutRelation.Equal, toItem: self, attribute: NSLayoutAttribute.BottomMargin, multiplier: 1.0, constant: 0).active = true // center horizontal NSLayoutConstraint(item: myCellLabel, attribute: NSLayoutAttribute.CenterX, relatedBy: NSLayoutRelation.Equal, toItem: self, attribute: NSLayoutAttribute.CenterX, multiplier: 1, constant: 0).active = true

La clase completa sería:

import UIKit class MyCustomCell: UITableViewCell { var myCellLabel = UIMongolSingleLineLabel() override init(style: UITableViewCellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) self.setup() } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } func setup() { self.myCellLabel.translatesAutoresizingMaskIntoConstraints = false self.myCellLabel.centerText = false self.myCellLabel.backgroundColor = UIColor.yellowColor() self.addSubview(myCellLabel) // Constraints // pin top NSLayoutConstraint(item: myCellLabel, attribute: NSLayoutAttribute.Top, relatedBy: NSLayoutRelation.Equal, toItem: self, attribute: NSLayoutAttribute.TopMargin, multiplier: 1.0, constant: 0).active = true // pin bottom NSLayoutConstraint(item: myCellLabel, attribute: NSLayoutAttribute.Bottom, relatedBy: NSLayoutRelation.Equal, toItem: self, attribute: NSLayoutAttribute.BottomMargin, multiplier: 1.0, constant: 0).active = true // center horizontal NSLayoutConstraint(item: myCellLabel, attribute: NSLayoutAttribute.CenterX, relatedBy: NSLayoutRelation.Equal, toItem: self, attribute: NSLayoutAttribute.CenterX, multiplier: 1, constant: 0).active = true } override internal class func requiresConstraintBasedLayout() -> Bool { return true } }


He probado su código y descubrí que el problema estaba en la configuración de restricciones. Por favor use la parte del código a continuación para configurar las constantes en su función de configuración de archivo "MyCustomCell.swift"

let topConstraint = NSLayoutConstraint(item: myCellLabel, attribute: .Top, relatedBy: .Equal, toItem: self, attribute: .Top, multiplier: 1, constant: 0) let bottomConstraint = NSLayoutConstraint(item: myCellLabel, attribute: .Bottom, relatedBy: .Equal, toItem: self, attribute: .Bottom, multiplier: 1, constant: 0) let centerConstraint = NSLayoutConstraint(item: myCellLabel, attribute: .CenterX, relatedBy: .Equal, toItem: self, attribute: .CenterX, multiplier: 1, constant: 0) self.addConstraints([centerConstraint, topConstraint, bottomConstraint])

También establezca clips a la propiedad vinculada a la etiqueta de su celda en "viewcontroller.swift"

// create a cell for each table view row func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { let cell:MyCustomCell = self.tableView.dequeueReusableCellWithIdentifier(cellReuseIdentifier) as! MyCustomCell cell.myCellLabel.text = self.myStrings[indexPath.row] cell.myCellLabel.clipsToBounds=true return cell }

Para su facilidad, he cargado mi código de muestra en GitHub github.com/tarunseera/DynamicHeightCell

La salida se ve así ahora


Lo que te falta es esta función:

override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat { return heightValue }

No estoy tan seguro de lo que debe hacer exactamente, pero por el hecho de que conoce sus etiquetas, debería poder devolver un valor de altura exacto para cada celda en este método