Подтвердить что ты не робот

UITableView с переменной высотой ячейки: работает в IB, но не программно

TL; DR

Мои программно созданные ячейки представления таблицы не изменяются в соответствии с высотой внутреннего содержимого своих пользовательских представлений, хотя я использую UITableViewAutomaticDimension и устанавливаю как верхние, так и нижние ограничения.

Проблема, вероятно, заключается в моей реализации подкласса UITableViewCell. См. Код ниже в разделе Не работает программно > Код > MyCustomCell.swift.

Цель

Я пытаюсь создать панель предложений для пользовательской монгольской клавиатуры. Монгольский написан вертикально. В Android это выглядит так:

введите описание изображения здесь

Прогресс

Я узнал, что должен использовать UITableView с высотой переменной ячейки, доступной начиная с iOS 8. Для этого требуется использование автоматического макета и указание в виде таблицы использовать автоматические измерения для высот ячеек.

Некоторые вещи, которые мне пришлось изучать на этом пути, представлены в моих последних SO-ответах и ​​ответах:

Итак, я пришел к тому, что у меня есть вертикальные метки, которые поддерживают внутренний размер содержимого. Эти ярлыки входят в мои собственные ячейки представления таблицы. И, как описано в следующем разделе, они работают, когда я делаю это в раскадровке, но не тогда, когда я создаю все программно.

Работает в IB

Чтобы изолировать проблему, я создал два базовых проекта: один для тех, где я использую раскадровку, и тот, где я делаю все программно. Проект раскадровки работает. Как видно на следующем изображении, каждая ячейка просмотра таблицы изменяется в соответствии с высотой пользовательской вертикальной метки.

введите описание изображения здесь

В IB

Я установил ограничения для привязки верхнего и нижнего уровня, а также центрирования метки.

введите описание изображения здесь

код

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!
}

Не работает программно

Так как я хочу, чтобы панель предложений была частью финальной клавиатуры, мне нужно ее программно создать. Однако, когда я пытаюсь воссоздать вышеприведенный проект программно, он не работает. Я получаю следующий результат.

введите описание изображения здесь

Высота ячейки не изменяется, а пользовательские вертикальные метки перекрываются друг с другом.

Я также получаю следующую ошибку:

Предупреждение только один раз: обнаружен случай, когда ограничения неоднозначно предложите высоту нуля для представления содержимого ячейки таблицы. Мы учитывая непреднамеренный крах и использование стандартной высоты вместо этого.

Эта ошибка была вызвана несколько раз при переполнении стека:

Однако проблема большинства этих людей заключается в том, что они не устанавливают ограничение на верхнее и нижнее ограничение. Я, или, по крайней мере, я думаю, что я, как показано в моем коде ниже.

код

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

Я думаю, что проблема, вероятно, здесь, поскольку это основное отличие от проекта 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
    }
}

Дополнительный код

Я также включу код для пользовательской вертикальной метки, который я использовал в обоих проектах выше, но поскольку проект IB работает, я не думаю, что главная проблема здесь.

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)
    }
}

Update

Весь код для проекта все здесь, поэтому, если кто-то заинтересован достаточно, чтобы попробовать его, просто создайте новый проект и вырежьте и вставьте указанный выше код в следующие три файла:

  • ViewController.swift
  • MyCustomCell.swift
  • UIMongolSingleLineLabel.swift
4b9b3361

Ответ 1

Ошибка довольно тривиальная:

Вместо

self.addSubview(myCellLabel)

использование

self.contentView.addSubview(myCellLabel)

Кроме того, я бы заменил

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

с

let topConstraint = NSLayoutConstraint(...)
let bottomConstraint = NSLayoutConstraint(...)
let centerConstraint = NSLayoutConstraint(...)

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

который является более явным (вы должны указать владельца ограничения) и, таким образом, безопаснее.

Проблема заключается в том, что при вызове active = true при ограничении система макета должна решить, к какому виду она должна добавить ограничения. В вашем случае, поскольку первым общим предком contentView и myCellLabel является ваш UITableViewCell, они были добавлены в ваш UITableViewCell, поэтому они фактически не ограничивали contentView (ограничения были между братьями и сестрами не между супервидом -subview).

Ваш код действительно вызвал предупреждение консоли:

Предупреждение только один раз: обнаружен случай, когда ограничения двусмысленно указывают на высоту нуля для представления содержимого ячейки таблицы. Мы рассматриваем развал непреднамеренно и вместо этого используем стандартную высоту.

Это заставило меня взглянуть на то, как создаются ограничения для вашей метки.

Ответ 2

Я проверил ваш код и обнаружил, что проблема связана с настройкой ограничений, пожалуйста, используйте приведенную ниже часть кода для установки констант в вашей функции "MyCustomCell.swift" setup() файла

 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])

Также установите клипы для привязки свойства к вашей ячейке в "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
}

Для вашей легкости я загрузил свой пример кода на GitHub Динамический пример высоты

Результат выглядит следующим образом

введите описание изображения здесь

Ответ 3

Проблема, похоже, связана с вертикальными ограничениями в ячейке Помещая их относительно себя, а не self.contentView в MyCustomCell, вы можете исправить свою проблему.

    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

полный класс:

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
    }
}

Ответ 4

То, что вам не хватает, это эта функция:

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

Я не уверен, что вы должны делать точно, но тем фактом, что вы знаете свои метки, вы должны иметь возможность вернуть точное значение высоты для каждой ячейки в этом методе

Ответ 5

Я думаю, что вам не хватает ограничений для tableView с супервидом. И попробуйте также увеличить оценочную высоту строки.