【发布时间】:2017-05-31 05:14:53
【问题描述】:
TL;DR:
在 macOS 10.11 中,基于视图的 NSTableView 在文本字段下方包含一个 NSTextField 和一个 NSImageView,其中一些行有图像,而另一些行没有,一些文本在向下滚动表格后被剪切。
滚动前:
滚动后:
当我说“剪裁”时,它意味着文本视图处于自动布局定义的最小高度,此时应该展开它。
请注意,此错误行为仅发生在 macOS 10.11 Mavericks 中。 macOS 10.12 Sierra 没有此类问题。
上下文
macOS 10.11+,基于视图 NSTableView。
每一行都有一个文本字段,以及文本字段下方的图像视图。
所有元素都在 IB 中设置了自动布局约束。
目标
文本视图必须垂直调整其高度 - 图像视图应相应移动,并保持粘在文本字段的底部。
当然,行本身也必须适应它的高度。
有时没有要显示的图像,在这种情况下,图像视图不应该是可见的。
这是它正常工作时的渲染方式:
当前实现
该行是 NSTableCellView 的子类。
在单元格中,文本字段设置有属性字符串。
在tableView(_ tableView: NSTableView, heightOfRow row: Int) -> CGFloat 中,我创建了一个虚拟文本视图,用于查找实际文本字段的高度,并相应地返回修改后的行高。我还检查是否有要显示的图像,如果是这种情况,我将图像高度添加到行高。
let ns = NSTextField(frame: NSRect(x: 0, y: 0, width: defaultWidth, height: 0))
ns.attributedStringValue = // the attributed string
var h = ns.attributedStringValue.boundingRect(with: ns.bounds.size, options: [.usesLineFragmentOrigin, .usesFontLeading]).height
if post.hasImage {
h += image.height
}
return h + margins
在tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView?我准备了实际的单元格内容:
getUserAvatar { img in
DispatchQueue.main.async {
cell.iconBackground.layer?.backgroundColor = self.prefs.colors.iconBackground.cgColor
cell.iconBackground.rounded(amount: 6)
cell.iconView.rounded(amount: 6)
cell.iconView.image = img
}
}
cell.usernameLabel.attributedStringValue = xxx
cell.dateLabel.attributedStringValue = xxx
// the textView at the top of the cell
cell.textLabel.attributedStringValue = xxx
// the imageView right under the textView
if post.hasImage {
getImage { img in
DispatchQueue.main.async {
cell.postImage.image = img
}
}
}
问题
滚动 tableView 时,只要一行或几行在图像视图中有图像,就会出现显示问题。
剪辑文本
有时文本被剪裁,可能是因为空的图像视图掩盖了文本的底部:
正常
剪辑
重要提示:调整表格大小会触发重绘并修复显示问题...
我尝试过的
细胞重复使用
我认为主要问题是由于 tableView 重用了单元格。
所以我默认隐藏tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView?中的图像字段,只有在有图像要设置时才取消隐藏,我在主线程上进行:
guard let cell = tableView.make(withIdentifier: "xxx", owner: self) as? PostTableCellView else {
return nil
}
DispatchQueue.main.async {
cell.postImage.isHidden = true
cell.postImage.image = nil
}
// set the text view here, the buttons, labels, etc, then this async part runs:
downloadImage { img in
DispatchQueue.main.async {
cell.postImage.isHidden = false
cell.postImage.image = img
}
}
// more setup
return cell
但在某些情况下,imageView 仍然会阻塞文本视图。
无论如何,有时文本会在第一次显示时被剪裁,在任何滚动完成之前...
CoreAnimation 层
我认为可能需要对单元格进行图层支持以便正确重新显示,所以我在 scrollView、tableView、tableCell 等上启用了 CoreAnimation 图层。但我几乎看不出有什么区别。
A/B 测试
如果我完全删除 imageView 并且只处理文本字段,一切正常。拥有 imageView 绝对是造成问题的原因。
自动布局
我尝试在不使用自动布局的情况下处理行内容的显示,但无济于事。我也尝试过设置不同的约束,但显然我不擅长自动布局,并且没有找到替代当前约束的好方法。
替代方案:带有图像的 NSAttributedString
我尝试删除图像视图,并将图像添加到文本视图中属性字符串的末尾。但是当它工作时结果通常很丑陋 - 而且在大多数情况下它只是不起作用(例如,当图像无法及时下载以添加到属性字符串中时,使单元格根本没有文本或图像并且高度错误)。
问题
我做错了什么?我该如何解决这些问题?我的猜测是我应该更改自动布局约束,但我没有看到可行的解决方案。
或者也许你会完全不同地做这件事?
【问题讨论】:
-
您在谈论
NSTextView,并且在您使用的图片和代码中NSTextField。你检查ns.bounds.size在heightOfRow中是否正确? -
Arf,我的错,我前段时间在测试时从 NSTextView 更改为 NSTextField,忘记恢复了。你认为使用 NSTextField 是错误的,我应该回到 NSTextView 吗? // 哇哦,ns.bounds.size 的高度总是为零...不知道发生了什么,是因为 NSTextField 而不是 NSTextView?
-
我刚刚发现这在 macOS 10.12 Sierra 中没有问题。错误行为仅发生在 macOS 10.11 Mavericks... oO
-
您的文本字段有一个“首选宽度”。您的 IB 屏幕截图显示您已将其设置为自动。你玩过其他价值观吗?无论是在 IB 还是运行时。你也玩过noteHeightOfRowsWithIndexesChanged。自动布局可能需要再次调用您的委托。
-
有些人称之为自动布局魔法。其他人将其视为自动布局戏剧。这里真的没什么可补充的。我在我的应用程序的几个地方遇到了类似的问题,它解决了这个问题。我认为这与自动布局计算约束的顺序以及文本字段具有固有大小的事实有关。
标签: macos cocoa autolayout nstableview nstextfield