NSView视图对象
视图与窗口
- NSViewController视图控制器负责管理NSView的生命周期,同时也会管理所有子视图控制器,以便实现不同视图的切换,同时需要区分下NSWindow和NSView的区别,视图这东西比较复杂。
- NSViewController不能独立显示,必须做为NSWindow(view)的子视图或NSWindowController(contentViewController)的属性才能显示,同时它本身又可以管理多个子视图实现显示的切换,如下:
视图生命周期
视图生命周期如下:
load阶段
主要是加载xib或storyboard文件
- loadView (1)
- viewDidLoad(2)
appear阶段
- viewWillAppear(3)
- viewDidAppear(4)
layout阶段
比如窗口伸缩等,都会触发下面的事件
- updateViewConstraints(7)
- viewWillLayout(5)
- viewDidLayout(6)
disappler阶段
- viewWillDisappear(8)
- viewDidDisapper(9)
正常显示执行顺序:1、2、3、4、5、6
添加了constraints后显示执行顺序:1,2,3,7,5,6,5,6,最后多执行的5和6是由于RunLoop界面重绘触发的
关闭执行顺序:8、9
NSView与NSViewController
采用storyboard创建项目时,默认的工程结构如下图所示,这里的View Controller是可以修改为自定义子类实现的:比如点击删除,然后拖动一个ViewController到设计面板中,再绑定Window和View。
修改NSWindowController类实现
此时可以把Main.storyboard文件中的ViewController Scene删除掉了(见上图),我们后面用程序动态修改。注意需要绑定Main.storyboard的class到自定义的类上。
class CustomWindowController: NSWindowController {override func windowDidLoad() {super.windowDidLoad()//self.createByViewController()//self.createByStoryboard()//self.createByCode()}}
接下来尝试用不同的方法实现ViewController的创建;
通过XIB创建
创建一个Cocoa类,语言选择swift再勾选上创建xib文件。
class ViewByController: NSViewController {override func viewDidLoad() {super.viewDidLoad()// Do view setup here.}
}
实例化 ViewByController
//创建一个xibfunc createByViewController(){//默认会加载与Controller同名的xib文件let viewController = ViewByController(nibName: NSNib.Name(rawValue: "ViewByController"), bundle: nil)self.contentViewController = viewController}
通过storyboard创建
只需要创建一个.storyboard文件即可,不需要swift,后续可创建一个NSViewController子类,然后替换下列代码实现:
//创建一个storyboard文件func createByStoryboard() {let sbName = NSStoryboard.Name(rawValue: "ViewByStoryboard")let storyboard = NSStoryboard(name:sbName, bundle: nil)let myViewController = storyboard.instantiateController(withIdentifier: NSStoryboard.SceneIdentifier(rawValue: "ViewByStoryboard"))self.contentViewController = myViewController as? NSViewController}
通过code编程创建
编码实现,创建一个.swift文件,不需要xib或storyboard。
class ViewByCode: NSViewController {override func loadView() {
// super.loadView()let frame = CGRect(x: 0, y: 0, width: 100, height: 100)let view = NSView(frame: frame)self.view = view}override func viewDidLoad() {super.viewDidLoad()}}
实例化 ViewByCode
//手工创建func createByCode() {let viewController = ViewByCode()self.contentViewController = viewController}
NSViewController内置了一个名为 representedObject 的属性对象,作用是用来存储 NSViewController 的模型对象。
NSView 模糊化效果
有两种方式可以实现:
- 在UI设置面板中把 Custom View 的实现类替换成 NSVisualEffectView;
- 拖动一个Visual Effect View 控件到设计视图中,然后在属性面板中修改配置属性;
自定义实现
创建NSViewController 子类
- 先删除掉main.storyboard中的视图;
- 拖动一个view controller到设计面板中,并从window controller 拖动到view controller,绑定连接;
- 创建一个NSWindowController子类;
- 设置上面拖动的view controller的类实现为自定义的子类;
class CustomViewController: NSViewController {// 添加的视图控件,做为内容显示区@IBOutlet weak var mainView: NSVisualEffectView!//当前子视图var currentController: NSViewController?override func viewDidLoad() {super.viewDidLoad()}
}
创建 NSView 子类
虽然NSViewController默认包含了一个名为view
的NSView对象,但有时也可以通过扩展自定义视图以实现更多功能,比如框架提供的:
- NSView:基类
- NSScrollView:带滚动条的视图
- ImageView:图像视图
import Cocoaclass CustomView: NSView {override func draw(_ dirtyRect: NSRect) {super.draw(dirtyRect)Swift.print("CustomView draw")}override func updateConstraints(){Swift.print("CustomView updateConstraints")super.updateConstraints()}override func display() {Swift.print("CustomView display")super.display()}override func layout() {Swift.print("CustomView layout")super.layout()}
}
管理 NSViewController 视图控制器
NSViewController除了做为UI控件的管理器,还可以做为门面用来管理子视图控制,相关的接口/属性定义如下:
- parentViewController:父视图控制器指针;
- childViewController:子视图控制器们指针;
- (void)addChildViewController:增加子视图控制器
- (void)removeFromParentViewController:从父视图控制器中删除自己;
在UI设计面板中添加几个view controller,并定义storyboard ID,如下:
接下来实现通过下拉列表切换视图显示:
加载子视图控制器
在自定义的 CustomViewController.swift 类中按名称加载子视图控制器;
func initiateControllers() {let vc1 = self.storyboard?.instantiateController(withIdentifier: NSStoryboard.SceneIdentifier(rawValue: "FirstVC")) as! NSViewControllerlet vc2 = self.storyboard?.instantiateController(withIdentifier: NSStoryboard.SceneIdentifier(rawValue: "SecondVC")) as! NSViewControllerself.addChildViewController(vc1)self.addChildViewController(vc2)}
实现子视图切换
给combo添加事件
@IBAction func switchViewAction(_ sender: NSComboBox) {let selectedIndex = sender.indexOfSelectedItemself.changeViewController(selectedIndex)}
定义 changeViewController()方法
func changeViewController(_ index: NSInteger) {if currentController != nil {currentController?.view.removeFromSuperview()}//数组越界保护guard index >= 0 && index <= self.childViewControllers.count-1 else {return}currentController = self.childViewControllers[index]self.mainView.addSubview((currentController?.view)!)//autoLayout约束let topAnchor = currentController?.view.topAnchor.constraint(equalTo: self.mainView.topAnchor, constant: 0)let bottomAnchor = currentController?.view.bottomAnchor.constraint(equalTo: self.mainView.bottomAnchor, constant: 0)let leftAnchor = currentController?.view.leftAnchor.constraint(equalTo: self.mainView.leftAnchor, constant: 0)let rightAnchor = currentController?.view.rightAnchor.constraint(equalTo: self.mainView.rightAnchor, constant: 0)NSLayoutConstraint.activate([topAnchor!, bottomAnchor!, leftAnchor!, rightAnchor!])}
编码实现视图切换效果
这里主要说明下视图切换时的效果,比如:
- modal:以模态窗口方式出现;
- sheel:从顶部滑出;
- popover:以弹出层方式出现;
- animator:动画效果,遮盖父窗口,需要实现动画类;
- show:视图切换;
自定义子视图
这个示例中,我们只定义一个视图就可以,上面添加一个按钮用于关闭本视图;
class ChildViewController: NSViewController {override func viewDidLoad() {super.viewDidLoad()}@IBAction func closeView(_ sender: Any) {if (self.presenting != nil) {self.dismiss(self)} else {self.view.window?.close();}}
}
添加切换显示样式
即给上面5个按钮添加不同的事件
@IBAction func presentAsModalAction(_ sender: NSButton){let presentVC = self.storyboard?.instantiateController(withIdentifier: NSStoryboard.SceneIdentifier(rawValue: "PresentVC")) as? NSViewControllerself.presentViewControllerAsModalWindow(presentVC!)}@IBAction func presentAsSheetAction(_ sender: NSButton){let presentVC = self.storyboard?.instantiateController(withIdentifier: NSStoryboard.SceneIdentifier(rawValue: "PresentVC")) as? NSViewControllerself.presentViewControllerAsSheet(presentVC!)}@IBAction func presentAsPopoverAction(_ sender: NSButton){let presentVC = self.storyboard?.instantiateController(withIdentifier: NSStoryboard.SceneIdentifier(rawValue: "PresentVC")) as? NSViewControllerself.presentViewController(presentVC!, asPopoverRelativeTo: sender.frame, of: self.view, preferredEdge: .minY, behavior:.transient )}@IBAction func presentAsAnimatorAction(_ sender: NSButton){let presentVC = self.storyboard?.instantiateController(withIdentifier: NSStoryboard.SceneIdentifier(rawValue: "PresentVC")) as? NSViewControllerlet animator = PresentCustomAnimator()self.presentViewController(presentVC!, animator: animator)}@IBAction func showAction(_ sender: NSButton){let presentVC = self.storyboard?.instantiateController(withIdentifier: NSStoryboard.SceneIdentifier(rawValue: "PresentVC")) as? NSViewControllerlet toVC = self.storyboard?.instantiateController(withIdentifier: NSStoryboard.SceneIdentifier(rawValue: "toVC")) as? NSViewController//增加 2个子视图控制器self.addChildViewController(presentVC!)self.addChildViewController(toVC!)//显示 presentVC 视图self.view.addSubview((presentVC?.view)!)// 从 presentVC 视图 切换到另外一个 toVC 视图self.transition(from: presentVC!, to: toVC!, options: NSViewController.TransitionOptions.crossfade , completionHandler: nil)}
自定义视图切换动画效果
自定义一个动画效果
//渐变动画
class PresentCustomAnimator: NSObject,NSViewControllerPresentationAnimator {func animatePresentation(of viewController: NSViewController, from fromViewController: NSViewController) {let bottomVC = fromViewControllerlet topVC = viewControllertopVC.view.wantsLayer = truetopVC.view.alphaValue = 0bottomVC.view.addSubview(topVC.view)topVC.view.layer?.backgroundColor = NSColor.gray.cgColorNSAnimationContext.runAnimationGroup( {context incontext.duration = 0.5topVC.view.animator().alphaValue = 1}, completionHandler:nil)}func animateDismissal(of viewController: NSViewController, from fromViewController: NSViewController) {let topVC = viewControllerNSAnimationContext.runAnimationGroup( {context incontext.duration = 0.5topVC.view.animator().alphaValue = 0}, completionHandler: { topVC.view.removeFromSuperview() } )}
}
设计器实现视图切换效果
storyboard设计
就是用UI设计的方式实现,术语称为Segue Control,方法是拖动按钮到新视图上面,这样省去了绑定事件这一步,如下图所示:
设置完成后的效果如下图所示:
自定义 segue 效果
对于动画效果,需要自定义Segue,然后设置为自定义实现类。
import Cocoaclass CustomSegue: NSStoryboardSegue {override func perform(){let sourceViewController = self.sourceController as! NSViewController;let destinationViewController = self.destinationController as! NSViewControllerlet animator = PresentCustomAnimator()sourceViewController.presentViewController(destinationViewController , animator: animator)}
}
通过上述设置,其效果同编码效果一样。
Dark模式设计
使用 NSAppearance 对象设计
override func viewDidLoad() {super.viewDidLoad()/** 设置view的appearance 效果 *//**NSAppearance.Name.Aqua : Light 默认设置NSAppearance.Name.darkAqua : Dark 模式NSAppearance.Name.vibrantDark : 仅可用于 Visual effect viewNSAppearance.Name.accessibilityHighContrastDarkAqua : 高对比的Dark 模式 (通常用于image)NSAppearance.Name.accessibilityHighContrastVibrantLight : 高对比的毛玻璃 效果 ,用于visual effec view;*//** 此次对view 的appearance进行赋值是无效的,因为 window的生命周期方法尚未执行 (具体可参考基础课程视频或项目代码)*/
// view.appearance? = NSAppearance.init(named: NSAppearance.Name.aqua)!
// print("\(view.effectiveAppearance.name.rawValue)")/** 1. 颜色硬编码设置视图背景色 : 这种情况下,无论是light 或者dark 模式,颜色都是固定的值,不会根据主题进行适配 */
// adaptedView.layer?.backgroundColor = NSColor.red.cgColor/** 2. 使用Asset 中的color 进行light /dark 之间的颜色适配: 切换light和dark时,需要重新开启应用 */adaptedView.layer?.backgroundColor = NSColor(named: "Color")?.cgColor/** 3. 使用带有语意的NSColor */
// adaptedView.layer?.backgroundColor = NSColor.labelColor.cgColor;
// adaptedView.layer?.backgroundColor = NSColor.controlBackgroundColor.cgColor;myLabel.textColor = NSColor.labelColor/** 可适配的系统颜色NSColor.systemRedNSColor.systemBlueNSColor.systemGrayNSColor.systemPink*/_ = NSImage(size: NSMakeSize(0, 0), flipped: true) { (rect) -> Bool in/** 返回值表示图片是否创建成功*/return true}}
视图手势识别
NSGestureRecognize r定义了手势识别的基本接口,即触摸板功能。可以识别以下手势:
- NSclickGestureRecognizer:点击
- NSPanGestureRecognizer:滑动
- NSPressGestureRecognizer:按住
- NSMagnificationGestureRecognizer :缩放
- NSRotationGestureRecognizer:旋转
添加手动识别功能
注意:手动识别是增加到NSView上面的,在NSViewController中包含了一个NSView对象,所以代码可以写在NSViewController中:
override func viewDidLoad() {super.viewDidLoad()//创建手势类let gr = NSMagnificationGestureRecognizer(target: self, action: #selector(ViewController.magnify(_:)))//视图增加手势识别self.view.addGestureRecognizer(gr)gr.delegate = selfself.view.window?.makeKeyAndOrderFront(self)}
实现手势识别协议
实现 NSGestureRecognizerDelegate 代理协议的magnify方法
extension ViewController: NSGestureRecognizerDelegate {@objc func magnify(_ sender: NSMagnificationGestureRecognizer) {switch sender.state {case .began:print("ClickGesture began")breakcase .changed:print("ClickGesture changed")breakcase .ended:print("ClickGesture ended")breakcase .cancelled:print("ClickGesture cancelled")breakdefault : break}}
}
这样在视图上进行上述触摸板操作,就可以识别动作了。