简体   繁体   English

对自定义文本视图的自动布局支持

[英]Autolayout support for custom text view

This question is about supporting a variable-height, custom text view using constraints and the view's intrinsicContentSize for autolayout.这个问题是关于使用约束和视图的用于自动布局的内在内容大小来支持可变高度的自定义文本视图。 Before you click that 'duplicate' button, hear me out.在您单击“复制”按钮之前,请听我说完。

I have a custom text view (from scratch, inherits from NSView).我有一个自定义文本视图(从头开始,从 NSView 继承)。 It supports many of the usual NSTextView features, the most relevant here being multiple lines and laying out its content based on width available.它支持许多常见的 NSTextView 功能,这里最相关的是多行并根据可用宽度布置其内容。 The app it's built for loads a couple of these text views into each row of a table view.它构建的应用程序将几个这样的文本视图加载到表视图的每一行中。 The issue is that the height doesn't get set properly according to its intrinsicContentSize.问题是高度没有根据其内在内容大小正确设置。

I created a sample project that simplifies the problem.我创建了一个简化问题的示例项目 It uses a "pseudo" text view with a fixed number and size of characters used to determine width/height required.它使用具有固定数量和字符大小的“伪”文本视图,用于确定所需的宽度/高度。 In the sample, there is a table view of one column whose cell view has only one subview, a PseudoTextView.在示例中,有一列的表视图,其单元格视图只有一个子视图,即 PseudoTextView。 The text view is pinned to the edges of its cell view with a little padding.文本视图固定在其单元格视图的边缘,并带有一点填充。 How can I get the system to recognize that the text view should abide by the constraints that define the width while allowing the text view to grow in height, wrapped tightly by the cell view?我怎样才能让系统认识到文本视图应该遵守定义宽度的约束,同时允许文本视图在高度上增长,被单元格视图紧紧包裹? Here's the text view code:这是文本视图代码:

class PseudoTextView: NSView {
@IBInspectable var characterCount: Int = 0
@IBInspectable var characterWidth: CGFloat = 5
@IBInspectable var characterHeight: CGFloat = 8
@IBInspectable var background: NSColor = .blue {
    didSet {
        layer?.backgroundColor = background.cgColor
    }
}

required init?(coder decoder: NSCoder) {
    super.init(coder: decoder)
    wantsLayer = true
    layer?.backgroundColor = background.cgColor
}

override var intrinsicContentSize: NSSize {
    let requiredWidth = characterWidth * CGFloat(characterCount)
    let lineCount = (requiredWidth / frame.width).rounded(.up)
    let usedHeight = lineCount * characterHeight
    let charactersPerLine = (frame.width / characterWidth).rounded(.down)
    let usedWidth = charactersPerLine * characterWidth
    return NSSize(width: usedWidth, height: usedHeight)
}

This version returns the appropriate size based on the frame of the view.此版本根据视图的框架返回适当的大小。 This obviously doesn't work because it's accessed during the updateConstraints phase of layout when the frame hasn't been set.这显然不起作用,因为它是在尚未设置框架的布局的 updateConstraints 阶段访问的。 I've also tried using NSView.noIntrinsicMetric for the width, but this will drive the text view to zero width and the height never recovers.我也尝试使用 NSView.noIntrinsicMetric 作为宽度,但这会将文本视图驱动为零宽度并且高度永远不会恢复。 There are an enormous number of other attempts I've made, but I won't bore you with them all.我已经进行了大量其他尝试,但我不会让您厌烦所有这些。

NSTextField does something different (assuming 'Editable' is off, 'Wrap' is on). NSTextField 做了一些不同的事情(假设 'Editable' 关闭,'Wrap' 开启)。 It's intrinsicContentSize reports the full width of the text on a single line (even if it's much longer than the width available), but it is somehow resized to the correct width.它的内在内容大小在单行上报告文本的全宽(即使它比可用宽度长得多),但它以某种方式调整为正确的宽度。 Once resized, the intrinsicContentWidth then still reports the full single-line width, but the height is adjusted to account for multiple lines.调整大小后,内在内容宽度仍会报告完整的单行宽度,但会调整高度以考虑多行。 There is some magic somewhere I haven't been able to divine.某处有一些我无法预知的魔法。

I've read every line of related documentation.我已经阅读了相关文档的每一行。 If there's a blog post on the topic, I've probably read it.如果有关于该主题的博客文章,我可能已经阅读过。 If there's a question on SO on the topic, I've probably read it.如果有关于该主题的 SO 问题,我可能已经阅读过。 If you wrote a book on the topic, I've probably bought it.如果你写了一本关于这个主题的书,我可能已经买了它。 All of these sources tease at the problem I'm having, but none of them answer the question of how to handle this particular situation.所有这些来源都在嘲笑我遇到的问题,但没有一个回答如何处理这种特殊情况的问题。 Desperate.绝望的。

Update:更新:

After reading an old blog post by Jonathon Mah ( http://devetc.org/code/2014/07/07/auto-layout-and-views-that-wrap.html ) I created an example that uses his approach.在阅读了 Jonathon Mah 的一篇旧博文( http://devetc.org/code/2014/07/07/auto-layout-and-views-that-wrap.html )后,我创建了一个使用他的方法的示例。 Here's another project that mimics his technique and works correctly.这是另一个模仿他的技术并正常工作的项目 This is the top portion of the app.这是应用程序的顶部。 It's a fixed container view that's adjusted with a slider.这是一个用滑块调整的固定容器视图。 The patchwork are the pseudo characters of the custom view whose background is the pink color.拼凑是自定义视图的伪字符,其背景为粉红色。

在此处输入图片说明

However, when inserted into a self-sizing table view, the custom view correctly matches the width of its cell, but the cell is not adjusted to respect the intrinsic height.但是,当插入到自调整表格视图中时,自定义视图正确匹配其单元格的宽度,但未调整单元格以尊重固有高度。 If you change the custom view's bottom constraint to be optional (say, with a >= relation) the custom view does shrink to the correct height, but the cell view remains fixed.如果您将自定义视图的底部约束更改为可选(例如,使用 >= 关系),自定义视图确实会缩小到正确的高度,但单元格视图保持固定。 How do I convince the cell view to shrink its height to respect the intrinsicContentSize.height of its subview?如何说服单元格视图缩小其高度以尊重其子视图的内在内容尺寸.高度?

在此处输入图片说明

I have a solution for your problem, although it may not be optimal, since I do not have too much experience with macos specifics.我对您的问题有一个解决方案,尽管它可能不是最佳解决方案,因为我对 macos 的细节没有太多经验。 So, first of all, let's define that the table view should use automatic row heights:所以,首先,让我们定义 table view 应该使用自动行高:

override func awakeFromNib() {
    super.awakeFromNib()
    tableView.usesAutomaticRowHeights = true
}

In your second sample the tableView outlet was not not connected to TableViewController , but it probably should be, so do not forget to connect it.在您的第二个示例中, tableView 插座没有连接到TableViewController ,但它可能应该连接,所以不要忘记连接它。

Now, in your WrappingCellView , you override layout() , but the value that you set for preferredMaxLayoutWidth is incorrect.现在,在您的WrappingCellView ,您覆盖了layout() ,但您为preferredMaxLayoutWidth设置的值不正确。 I think it should be the width of the superview:我认为应该是superview的宽度:

override func layout() {
    // 16 is used for the sake of example
    wrappingView.preferredMaxLayoutWidth = (superview?.frame.width ?? 16) - 16
    super.layout()
}

Next part is the one I am not sure about:下一部分是我不确定的部分:

func tableViewColumnDidResize(_ notification: Notification) {
    tableView.reloadData()
}

There should be a better API to recalculate row heights.应该有更好的 API 来重新计算行高。 I hope you or someone else can suggest something :)我希望你或其他人可以提出一些建议:)

These three adjustments result in proper recalculation of the cell height:这三个调整导致正确重新计算单元高度: 在此处输入图片说明

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM