最近抽空看了些 macOS 开发的资料。(自嘲下:iOS 开发都不是很会,就开始搞 macOS 开发。。)
一开始觉得 macOS 和 iOS 估计差不多。但是呢,习惯 UIKit,再去碰 Appkit 这个古老的框架。只能说两者真不是一码事。。。
官方有份 NSScrollView 的教程。写的挺详细。可以看下。
Xcode 里面的文档没有很多的解释。有些连默认值都不知道有没有。使用 UIKit 时,遇到问题,跳到文档里面,很多情况下会有相应解释等。但是 AppKit 的话,大部分都没有多少解释。估计又是一个历史包袱吧~
那么如何去创建一个 NSScrollView?
let scrollView = NSScrollView()
let scrollView = NSScrollView(frame: NSRect())
这个和 UIScrollView 几乎一致。
UIKit 中,我们可以通过设置 UIScrollView 的 contentSize 来进行可滑动的操作。但是呢,在 NSScrollView 里,并没有 contentSize 供我们设置,取而代之的是 documentView。当我们要使用一个 NSScrollView 的时候,需要把 conentView 指向 scrollView 的 documentView。
let contentView = NSView(frame: NSRect(x: 0, y: 0, 1000, height: 1000))
contentView.backgroundColor = .yellow
bgScrollView.documentView = contentView
在官方的教程中,解释的很清楚。
NSScrollView 是由 NSScrollerNSClipViewContentViewNSRulerView 构成的。这个可以选择用 IB 构建一个 NSScrollView 来查看,比较直观。
Clip View ,是一个 NSClipView。也就是 scrollView 的一个 contentView
的属性。官方的说法,负责剪切 documentView 的内容等。从 IB 的 图看到的层级,猜测 documentView 有可能就是这个 NSClipView 层级下的 view。
NSScrollView 给人的感觉,更像一个迷你窗口,然后通过滚动 documentView 来使的内容出现在窗口里面。
在使用上,一些属性配置可以见以下代码。
let scrollView = NSScrollView()
// scrollerStyle。overlay / legacy。 overlay 的效果,则是 scroller 背景透明,而 legacy 则是 独立出 scroller 的区域,看起来比较丑~~个人觉得?
scrollView.scrollerStyle = .overlay
// 滚动条的显示。用 IB 创建 scrollview 时,以下两个参数均为 true。但是 code 创建 scrollview 时,以下两个参数默认均为 false。很奇怪的设计。
scrollView.hasVerticalScroller = true
scrollView.hasHorizontalScroller = true
// 滚动条的样式:light、 dark、default。default 的话,其实就是 dark
scrollView.scrollerKnobStyle = .dark
// bounce 的效果。elasticity 是弹性的含义。automaticallowed one。
scrollView.horizontalScrollElasticity = .automatic
scrollView.verticalScrollElasticity = .automatic
由于 macOS 的坐标起点是在屏幕左下角。因此,在设置好 documentView 后,最好让 scrollView 滚到最上方的位置。为了方便,直接用 extension 增加了一个 scrollToTop 的方法。
extension NSScrollView {
/// Scroll to the ducument view top.
public
func scrollToTop() {
if let documentView = self.documentView {
if documentView.isFlipped {
documentView.scroll(.zero)
} else {
let maxHeight = max(bounds.height, documentView.bounds.height)
documentView.scroll(NSPoint(x: 0, y: maxHeight))
}
}
}
}
// 滚动到最上方
scrollView.scrollToTop()
-(void)scrollPoint:(NSPoint)aPoint
- (BOOL)scrollRectToVisible:(NSRect)aRect
NSView的enclosingScrollView属性可以获得视图的滚动条,如果视图没有滚动条则enclosingScrollView为nil。
滚动到视图顶部的代码
func scrollToPoint() {
let sframe = CGRect(x: 0, y: 0, 200, height: 200)
let scrollView = NSScrollView(frame: sframe)
let image = NSImage(named: "img.png")
let imageViewFrame = CGRect(x: 0, y: 0, (image?.size.width)!, height: (image?.size.height)!)
let imageView = NSImageView(frame: imageViewFrame)
imageView.image = image
scrollView.hasVerticalRuler = true
scrollView.hasHorizontalRuler = true
scrollView.documentView = imageView
self.view.addSubview(imageView)
//滚动到顶部位置var newScrollOrigin: NSPoint
var newScrollOrign: NSPoint
let contentView: NSClipView = scrollView.contentView
if self.view.isFlipped {
newScrollOrign = NSPoint(x: 0.0, y: 0.0)
}else{
newScrollOrign = NSPoint(x: 0, y: imageView.frame.size.height - contentView.frame.size.height)
}
contentView.scroll(to: newScrollOrign)
}
其中,isFlipped 是坐标系翻转。是 NSView 的一个属性。
当然,NSScrollView 并非只有这么简简单单几行代码。还有一些设置。比如 scrollerInsets 是设置 NSScroller 的边距等。另外,还有一些通知,可以进行一些事件的监听。
最后上个demo。
滚动条的显示控制
滚动条的 has VerticalScroller和 hasHorizontalScroller分别用来控制是否显示纵向和横向的滚动条。如果设置它们为 false,只是不显示出来,并不是禁止滚动的行为。但是大多数情况下上述两个方法并不能真正实现滚动条的完全不显示,要做到完全不显示滚动条,需要重写滚动条类的tile方法,通过设置水平和垂直方向滚动条的size中的 width和 height为0来实现。
下面的代码定义了滚动条的子类 NoScrollerScrollView,重写了它的tile方法,实现了完全隐藏滚动条。
如果要禁止一个方向的滚动,需要子类化 NSScrollview,重载它的 scrollwheel方法,判断
y轴方向的偏移量满足一定条件返回即可
class NoScrollerScrollView: NSScrollView {
//重载tile 隐藏滚动条
override func tile() {
super.tile()
var hFrame = self.horizontalScroller?.frame
hFrame?.size.height = 0
if let hframe = hFrame {
self.horizontalScroller?.frame = hframe
}
var vFrame = self.verticalScroller?.frame
vFrame?.size.width = 0
if let vframe = vFrame {
self.verticalScroller?.frame = vframe
}
}
//禁止一个方向上的滚动重载scrollView
override func scrollWheel(with event: NSEvent) {
let f = abs(event.deltaY)
if event.deltaX == 0.0 && f >= 0.01 {
return
}else if event.deltaX == 0 && f == 0.0 {
return
}
else {
super.scrollWheel(with: event)
}
}
}