似乎从 Catalina 开始,在最近几个版本的 macOS 中,使用一些系统控件的时候,系统会自动在视图层级中插入 NSVisualEffectView 来充当控件的背景,如 NSScrollView 这些容器视图。

你也许跟我一样,压根就没注意到有这一变动,直到在某些需求下需要修改背景颜色、实现类边栏的半透明效果时,才发现在视图层级中多出了一个不透明的 NSVisualEffectView

被插入的 NSVisualEffectView 拥有不透明的材料

按照以前的方法,我们想都不用想就直接把 NSScrollViewNSClipView 以及顶层的 NStableView 或者 NSCollectionView 的背景颜色设置为透明色,或者设置 drawBackground 来隐藏掉背景的绘制。

当你满怀欣喜的编译运行,等着透明背景出现的时候,却发现背景还在:

然而,什么都没有发生

让事情更加懊恼的是,查文档也好,改属性也罢,你找不到任何公开的方法来修改或隐藏它。

变通方法

作为一个合格的 AppKit 开发者,我们自然而然的会想到重写。didAddSubview(_:) 也许是最佳的隐藏它的地方:

class ScrollView: NSScrollView {
    override func didAddSubview(_ subview: NSView) {
        super.didAddSubview(subview)

        if subview is NSVisualEffectView {
            subview.isHidden = true
        }
    }
}

但别在这里用 removeFromSuperView() 方法将其移除,你会发现他会重新被插入到视图层级中。

可算是透明了

但是,这么做的效果看起来总是差了什么,特别是你要在上面显示某些选中项的时候:

发现了吗?左边的选中高亮是纯色不透明的,而右边系统 Spotlight 的高亮却带有一丝通透。

如果我们要实现这种通透效果,我们需要自行重写高亮的绘制,禁用掉默认的绘制,然后通过插入一个 materialselection,并且 isEmphasizedtrueNSVisualEffectView 来实现这种效果。

还记得 App Store 以及 Finder 侧边栏高亮选中项的效果吗?其实他们也是通过同样的 NSVisualEffectView 实现的,只不过,它们的 isEmphasized 属性被设置成了 false

其实我们有个更好的方法

在大多数情况下,我们会实现这种半透明效果往往是在侧边栏上,或者这种可以使用 NSTableView 实现的列表型视图。这种时候,我们其实可以直接使用 NSTableView 自带的 source list 样式来实现上面说到的所有内容:

// for macOS 11 Big Sur
tableView.style = .sourceList

// for macOS 10.15 Catalina and older
tableView.selectionHighlightStyle = .sourceList

这么做的时候,我们甚至都不需要去隐藏掉上面说的被插入的 NSVisualEffectView,也不用自己去实现选中项的高亮通透效果,这些都是 source list 的默认效果。

可惜的是,如果你的设计带有头部或者尾部视图,又想用 NSTableView 来实现的话,你在数据源的处理上将会非常的痛苦。在这种情况下,也许用一开始的方法搭配 NSCollectionView 会更加容易。