在基于视图的 NSTableView 中,NSTextField 被 NSImageView 裁剪
In a view-based NSTableView, the NSTextField is clipped by an NSImageView
长话短说:
在 macOS 10.11 中,基于视图的 NSTableView 在文本字段下方包含一个 NSTextField 和一个 NSImageView,其中一些行有图像而其他行没有,一些文本在 table 向下滚动然后向上滚动后被剪裁.
滚动前:
滚动后:
当我说 "clipped" 时,这意味着文本视图处于自动布局定义的最小高度,此时应该展开它。
请注意,此错误行为仅发生在 macOS 10.11 Mavericks 中。 macOS 10.12 Sierra 不存在此类问题。
上下文
macOS 10.11+,基于视图 NSTableView。
每一行都有一个文本框,文本框下方有一个图像视图。
所有元素都在 IB 中设置了自动布局约束。
目标
文本视图必须垂直调整其高度 - 图像视图应相应移动,并保持粘在文本字段的底部。
当然,行本身也必须调整其高度。
有时没有图像可显示,在这种情况下图像视图不应该可见。
正常工作时应该是这样的:
当前实施
该行是 NSTableCellView 的子类。
在单元格中,文本字段设置有属性字符串。
在 tableView(_ tableView: NSTableView, heightOfRow row: Int) -> CGFloat
中,我制作了一个虚拟 textView,用于查找实际文本字段的高度,并 return 相应地修改了行高。我还检查是否有要显示的图像,如果是,我将图像高度添加到行高。
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
}
}
}
问题
滚动 table 视图时,一旦图像视图中的一行或多行有图像,就会出现显示问题。
剪辑文本
有时文本会被剪裁,可能是因为空图像视图遮住了文本的底部:
正常
剪辑
重要提示:调整 table 的大小会触发重绘并修复显示问题...
我试过的
小区重用
我认为主要问题是因为 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 仍然会阻塞文本视图。
无论如何,有时在第一次显示时文本会在滚动完成之前被剪裁...
核心动画层
我认为单元格可能需要分层支持才能正确重新显示,所以我在滚动视图、table视图、table单元格上启用了 CoreAnimation 层,等。但我几乎看不出有什么区别。
A/B 测试
如果我完全删除 imageView 并只处理文本字段,一切正常。拥有 imageView 绝对是这里出现问题的原因。
自动布局
我试过在不使用自动布局的情况下处理行内容的显示,但无济于事。我也尝试过设置不同的约束条件,但显然我不喜欢自动布局,而且没能找到一个很好的替代方案来替代我当前的约束条件。
备选方案:带有图像的 NSAttributedString
我尝试删除图像视图,并将图像添加到文本视图中属性字符串的末尾。但是当它工作时,结果通常是丑陋的——而且在大多数情况下它只是不起作用(例如,当图像不能及时下载以添加到属性字符串时,使单元格根本没有文本或图像而且高度不对)。
问题
我做错了什么?我该如何解决这些问题?我的猜测是我应该更改自动布局约束,但我没有看到可行的解决方案。
或者您会采用完全不同的方式吗?
文本字段在 IB 中有 "Preferred Width" 字段,它允许调整固有大小与 auto-layout 计算大小的相关性。
将此 IB 属性 值更改为 "First Runtime Layout Width" 或 "Explicit" 通常有助于解决类似问题。
这解决了我过去的很多问题。
长话短说:
在 macOS 10.11 中,基于视图的 NSTableView 在文本字段下方包含一个 NSTextField 和一个 NSImageView,其中一些行有图像而其他行没有,一些文本在 table 向下滚动然后向上滚动后被剪裁.
滚动前:
滚动后:
当我说 "clipped" 时,这意味着文本视图处于自动布局定义的最小高度,此时应该展开它。
请注意,此错误行为仅发生在 macOS 10.11 Mavericks 中。 macOS 10.12 Sierra 不存在此类问题。
上下文
macOS 10.11+,基于视图 NSTableView。
每一行都有一个文本框,文本框下方有一个图像视图。
所有元素都在 IB 中设置了自动布局约束。
目标
文本视图必须垂直调整其高度 - 图像视图应相应移动,并保持粘在文本字段的底部。
当然,行本身也必须调整其高度。
有时没有图像可显示,在这种情况下图像视图不应该可见。
正常工作时应该是这样的:
当前实施
该行是 NSTableCellView 的子类。
在单元格中,文本字段设置有属性字符串。
在 tableView(_ tableView: NSTableView, heightOfRow row: Int) -> CGFloat
中,我制作了一个虚拟 textView,用于查找实际文本字段的高度,并 return 相应地修改了行高。我还检查是否有要显示的图像,如果是,我将图像高度添加到行高。
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
}
}
}
问题
滚动 table 视图时,一旦图像视图中的一行或多行有图像,就会出现显示问题。
剪辑文本
有时文本会被剪裁,可能是因为空图像视图遮住了文本的底部:
正常
剪辑
重要提示:调整 table 的大小会触发重绘并修复显示问题...
我试过的
小区重用
我认为主要问题是因为 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 仍然会阻塞文本视图。
无论如何,有时在第一次显示时文本会在滚动完成之前被剪裁...
核心动画层
我认为单元格可能需要分层支持才能正确重新显示,所以我在滚动视图、table视图、table单元格上启用了 CoreAnimation 层,等。但我几乎看不出有什么区别。
A/B 测试
如果我完全删除 imageView 并只处理文本字段,一切正常。拥有 imageView 绝对是这里出现问题的原因。
自动布局
我试过在不使用自动布局的情况下处理行内容的显示,但无济于事。我也尝试过设置不同的约束条件,但显然我不喜欢自动布局,而且没能找到一个很好的替代方案来替代我当前的约束条件。
备选方案:带有图像的 NSAttributedString
我尝试删除图像视图,并将图像添加到文本视图中属性字符串的末尾。但是当它工作时,结果通常是丑陋的——而且在大多数情况下它只是不起作用(例如,当图像不能及时下载以添加到属性字符串时,使单元格根本没有文本或图像而且高度不对)。
问题
我做错了什么?我该如何解决这些问题?我的猜测是我应该更改自动布局约束,但我没有看到可行的解决方案。
或者您会采用完全不同的方式吗?
文本字段在 IB 中有 "Preferred Width" 字段,它允许调整固有大小与 auto-layout 计算大小的相关性。
将此 IB 属性 值更改为 "First Runtime Layout Width" 或 "Explicit" 通常有助于解决类似问题。
这解决了我过去的很多问题。