25

TL;博士

我以编程方式创建的表格视图单元格没有根据其自定义视图的内在内容高度调整大小,即使我正在使用UITableViewAutomaticDimension和设置顶部和底部约束。

问题可能在于我UITableViewCell对子类的实现。请参阅下面的代码无法以编程方式工作 > 代码 > MyCustomCell.swift

目标

我正在尝试为自定义蒙古语键盘制作建议栏。蒙古文是竖写的。在 Android 中,它看起来像这样:

在此处输入图像描述

进步

我了解到我应该使用UITableView具有可变单元格高度的 a,它从 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!
}

不能以编程方式工作

由于我希望建议栏成为最终键盘的一部分,因此我需要能够以编程方式创建它。但是,当我尝试以编程方式重新创建上述示例项目时,它不起作用。我得到以下结果。

在此处输入图像描述

单元格高度未调整大小,并且自定义垂直标签彼此重叠。

我还收到以下错误:

仅警告一次:检测到约束模糊地建议 tableview 单元格的内容视图高度为零的情况。我们正在考虑意外折叠并使用标准高度。

在 Stack Overflow 上多次出现此错误:

然而,对于这些人中的大多数人来说,问题在于他们没有同时设置顶部和底部引脚约束。我是,或者至少我认为我是,如下面的代码所示。

代码

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

更新

项目的全部代码都在这里,所以如果有兴趣尝试一下,只需新建一个项目并将上面的代码剪切并粘贴到以下三个文件中:

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

5 回答 5

11

错误很简单:

代替

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在您的情况下,因为and的第一个共同祖先myCellLabel是 your UITableViewCell,所以它们被添加到 yourUITableViewCell中,因此它们实际上并没有约束contentView(约束在兄弟姐妹之间而不是在 superview-subview 之间)。

您的代码实际上触发了控制台警告:

仅警告一次:检测到约束模糊地建议 tableview 单元格的内容视图高度为零的情况。我们正在考虑意外折叠并使用标准高度。

这让我立即查看了为您的标签创建约束的方式。

于 2016-04-13T13:26:22.457 回答
6

我已经测试了您的代码,发现问题在于设置约束请使用下面的代码部分在“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 Dynamic Height Sample上上传了我的示例代码

输出现在看起来像这样

在此处输入图像描述

于 2016-04-11T11:03:32.903 回答
2

问题似乎来自单元格中的垂直约束通过将它们相对于 self 而不是 MyCustomCell 中的 self.contentView 您可以解决您的问题

    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
    }
}
于 2016-04-13T13:43:57.293 回答
1

你缺少的是这个功能:

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

我不太确定你应该做什么,但事实上你知道你的标签,你应该能够在这个方法中为每个单元格返回一个精确的高度值

于 2016-04-11T10:57:16.690 回答
0

我认为您缺少为带有超级视图的 tableView 设置约束。并尝试增加估计的行高。

于 2016-04-11T11:03:51.033 回答