热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

macOS开发中NSWindow,NSWindowController,NSView,NSViewController的关系

macOS使用的Cocoa框架,的确没有iOS使用的CocoaTouch那么智能好用。有些地方逻辑很奇怪,还有一些看似很正常的功能它却没有提供,还需要自定义。这里就有一个很头疼
    macOS使用的Cocoa框架,的确没有iOS使用的Cocoa Touch那么智能好用。有些地方逻辑很奇怪,还有一些看似很正常的功能它却没有提供,还需要自定义。这里就有一个很头疼的问题,关于这四个类的问题,他们之间到底是什么关系,如果摆脱了storyboard如何用代码实现?今天就来简单介绍一下。

    Xcode所提供的默认模板包括一个WindowController,还有一个ViewController,在ViewController中还有一个View,我们的控件一般都写在这个View中。而起始,storyboard把一个逻辑给简化了,关于Window,WindowController,View和ViewController,这四个类可以说是相互依存的。

    如果我们不使用storyboard,那么程序就会去读取AppDelegate中的代码(如果是用默认模板的话,把storyboard删除之后要记得在设置中把默认storyboard删除)。我们应用程序显示的第一个窗口就需要在此定义。由于Cocoa框架严格遵守着MVC模式,因此,要想在屏幕上显示一个窗口,那么一定就要拥有模型,视图和对应的控制器。那么,既然是要生成一个窗口,我们就需要一个NSWindow或其子类的实例。NSWindow有这样一个初始化函数:

public convenience init(contentViewController: NSViewController)

这里的意思是说,我们要一个窗口,那么窗口里究竟显示什么东西,是需要一个ViewController说了算的,所以我们还需要一个ViewController,而ViewController有这样一个构造函数:

public init?(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?)

    既然有了视图控制器,那一定是用来显示视图的,那视图在哪里呢?一般是用xib文件(编译之后就成为nib文件)来编辑的,所以调用这个方法就可以加载nib文件。当然,如果你的View是用代码定义的,那在这里给两个参数传空就可以了,然后操作NSViewController的一个属性来改变它的视图:

open var view: NSView

    之后,有了window,我们还得需要一个控制器来把这个窗口显示在屏幕上(目前为止所有的数据还都是内存数据而已,我们还需要调用显示方法),所以就用到了NSWIndowController,它提供了一个构造函数:

public init(window: NSWindow?)

    这样就齐全了,我们可以看到,NSWindowController里会包含一个它要控制的NSWindow,而NSWindow需要一个NSViewController来管理具体显示的视图,最后还需要一个具体的NSView。当我们准备齐全这些以后,就可以调用NSWindowController的一个方法,显示窗口:

@IBAction open func showWindow(_ sender: Any?)

    关于这里的sender,官方的解释是动作的发起者,一般是应用程序代理,但是本人尝试,其实填什么都好像不影响结果,哪怕是nil,也可以正常显示。具体的含义还有待继续摸索。

    还有就是关于storyboard的建议,其实在做macOS开发的时候,storyboard并不好用,不像在iOS开发时那样得心应手,所以还是建议把视图的设计用xib,然后关于控制器的部分用代码来书写。但是也并不建议直接把storyboard删掉,因为用它来编辑状态栏的下拉菜单还是非常方便地,所以本人的做法是在storyboard中只留一个file menu,把其他的视图和控制器都删除掉。当然这样的话,在项目设置处的入口storyboard就必须还得是Main才行。

    接下来用一个具体的例子来说明上面的这一系列问题。我们制作一个简单的应用程序,它的主界面有两个按钮,当点击第一个按钮的时候创建一个新的窗口,当点击第二个按钮的时候也创建一个新的窗口,同时还关闭主窗口。

    分析上面的要求,我们肯定是需要3套内容,每一套里都应该含有一个WIndowController,一个Window,一个ViewController和一个View。

    首先,创建一个Cocoa工程


    然后建立工程:


    删除storyboard里的视图和控制器:


    再删除已经作废的ViewController源文件:


    之后,我们创建三个ViewController以及xib文件,Command+N,选择Cocoa Class,输入mainViewController,勾选xib文件:


    然后用同样的方法生成sub1ViewController和sub2ViewController:


    对于sub1和sub2,我们只是能够区分就好,所以在xib里随便拖个控件什么的,能认清楚就行,而对于mainViewController.xib,我们需要两个按钮,并且还要关联到mainViewController.swift中点击方法,这里不再赘述,完成之后的效果如下:


    我们来重点编辑这两个函数,这里有一点需要注意的是,WindowController必须持久存在,否则会造成窗口闪退的现象,所以,我们要确保WindowController时刻存在一个引用,这样它才不会被ARC回收掉,那么最好的办法就是让他成为一个成员变量,这样就可以保持引用。而至于其他的对象,在WindowController内部会保持连接,所以只要WindowController在,它们就一定在,所以我们用局部变量来作为一个“接力手”就可以了。

    比如说我们要在btn1Click(_:)方法中显示视图1,那么首先要有一个ViewController来加载对应的xib文件,然后要创建一个窗口关联它,再把它给到WindowController中就可以了,具体代码如下:

//

//  mainViewController.swift


import Cocoa


class mainViewController: NSViewController {


    override func viewDidLoad() {

        super.viewDidLoad()

        // Do view setup here.

    }

    open var windowController: NSWindowController?

    var sub1WindowController: NSWindowController?

    @IBAction func btn1Click(_ sender: NSButton) {

        // 创建视图控制器,加载xib文件

        let sub1ViewCOntroller= NSViewController(nibName: "sub1ViewController", bundle: Bundle.main)

        // 创建窗口,关联控制器

        let sub1Window = sub1ViewController != nil ? NSWindow(contentViewController: sub1ViewController!) : nil

        // 初始化窗口控制器

        sub1WindowController = NSWindowController(window: sub1Window)

        // 显示窗口

        sub1WindowController?.showWindow(nil)

    }

    var sub2WindowController: NSWindowController?

    @IBAction func btn2Click(_ sender: NSButton) {

        // 同上

        let sub2ViewCOntroller= NSViewController(nibName: "sub2ViewController", bundle: Bundle.main)

        let sub2Window = sub2ViewController != nil ? NSWindow(contentViewController: sub2ViewController!) : nil

        sub2WindowController = NSWindowController(window: sub2Window)

        sub2WindowController?.showWindow(nil)

        // 加上一句关闭当前窗口

        windowController?.close()

    }

    

}


    需要说明的一点是,由于关闭窗口是WindowController管的,所以要想在ViewController里操作的话,就需要传入进来才行,所以这里的成员windowController就是如此。

    虽然我们这个逻辑实现了,但是现在打开应用程序还是没有窗口的,因为我们主窗口还没有显示出来,所以我们还需要在应用程序加载完毕后加载主窗口,所以还要在AppDelegate中对mainView实现一个相同的功能:

//

//  AppDelegate.swift


import Cocoa


@NSApplicationMain

class AppDelegate: NSObject, NSApplicationDelegate {


    var mainWindowController: NSWindowController?


    func applicationDidFinishLaunching(_ aNotification: Notification) {

        // Insert code here to initialize your application

        let mainViewController_ = mainViewController(nibName: "mainViewController", bundle: Bundle.main)

        let mainWindow = mainViewController_ != nil ? NSWindow(contentViewController: mainViewController_!) : nil

        mainWindowController = NSWindowController(window: mainWindow)

        mainViewController_?.windowController = mainWindowController

        mainWindowController?.showWindow(nil)

    }


    func applicationWillTerminate(_ aNotification: Notification) {

        // Insert code here to tear down your application

    }



}


    其实说来说去,这几行代码都是完全一样的,只是用在了不同的地方而已,我们有三个窗口,所以就需要三套这样体系的文件,当然也就需要三套用于加载的代码。主窗口要一开始就显示,所以写在应用程序代理中,而两个子窗口是点击按钮以后显示,所以写在的按钮的实现文件中。

    关于这四个类的简单说明基本就到这里,当然本实例只是为了说明用法,所以代码风格上来说并不规范,在实际开发的时候,我们还是应该对这些代码进行更高层次的封装,也要对相对应的初始化函数进行改写,但是说到底实现的功能都是这些,本质上是不变的。



推荐阅读
  • NOIP2000的单词接龙问题与常见的成语接龙游戏有异曲同工之妙。题目要求在给定的一组单词中,从指定的起始字母开始,构建最长的“单词链”。每个单词在链中最多可出现两次。本文将详细解析该题目的解法,并分享学习过程中的心得体会。 ... [详细]
  • [转]doc,ppt,xls文件格式转PDF格式http:blog.csdn.netlee353086articledetails7920355确实好用。需要注意的是#import ... [详细]
  • JComponentJLabel的setBorder前言用例2205262241前言setBorder(Border边框)实现自JComponentjava.awt.Insets ... [详细]
  • 检查在所有可能的“?”替换中,给定的二进制字符串中是否出现子字符串“10”带 1 或 0 ... [详细]
  • 掌握Android UI设计:利用ZoomControls实现图片缩放功能
    本文介绍了如何在Android应用中通过使用ZoomControls组件来实现图片的缩放功能。ZoomControls提供了一种简单且直观的方式,让用户可以通过点击放大和缩小按钮来调整图片的显示大小。文章详细讲解了ZoomControls的基本用法、布局设置以及与ImageView的结合使用方法,适合初学者快速掌握Android UI设计中的这一重要功能。 ... [详细]
  • 本文将带你快速了解 SpringMVC 框架的基本使用方法,通过实现一个简单的 Controller 并在浏览器中访问,展示 SpringMVC 的强大与简便。 ... [详细]
  • Spring – Bean Life Cycle
    Spring – Bean Life Cycle ... [详细]
  • 本文介绍如何使用线段树解决洛谷 P1531 我讨厌它问题,重点在于单点更新和区间查询最大值。 ... [详细]
  • 在软件开发过程中,经常需要将多个项目或模块进行集成和调试,尤其是当项目依赖于第三方开源库(如Cordova、CocoaPods)时。本文介绍了如何在Xcode中高效地进行多项目联合调试,分享了一些实用的技巧和最佳实践,帮助开发者解决常见的调试难题,提高开发效率。 ... [详细]
  • Spring框架中枚举参数的正确使用方法与技巧
    本文详细阐述了在Spring Boot框架中正确使用枚举参数的方法与技巧,旨在帮助开发者更高效地掌握和应用枚举类型的数据传递,适合对Spring Boot感兴趣的读者深入学习。 ... [详细]
  • 在本文中,我们将为 HelloWorld 项目添加视图组件,以确保控制器返回的视图路径能够正确映射到指定页面。这一步骤将为后续的测试和开发奠定基础。首先,我们将介绍如何配置视图解析器,以便 SpringMVC 能够识别并渲染相应的视图文件。 ... [详细]
  • 在前文探讨了Spring如何为特定的bean选择合适的通知器后,本文将进一步深入分析Spring AOP框架中代理对象的生成机制。具体而言,我们将详细解析如何通过代理技术将通知器(Advisor)中包含的通知(Advice)应用到目标bean上,以实现切面编程的核心功能。 ... [详细]
  • Python与R语言在功能和应用场景上各有优势。尽管R语言在统计分析和数据可视化方面具有更强的专业性,但Python作为一种通用编程语言,适用于更广泛的领域,包括Web开发、自动化脚本和机器学习等。对于初学者而言,Python的学习曲线更为平缓,上手更加容易。此外,Python拥有庞大的社区支持和丰富的第三方库,使其在实际应用中更具灵活性和扩展性。 ... [详细]
  • 本文深入探讨了 Android DrawingView 的优化技巧与实现方法,重点介绍了如何实现平滑绘制效果。通过支持常见的绘图工具和形状,以及图层变换功能,提升了用户体验。文章详细解析了绘制过程中的性能优化策略,包括减少重绘次数、使用硬件加速和优化内存管理等技术,为开发者提供了实用的参考。 ... [详细]
  • Android 图像色彩处理技术详解
    本文详细探讨了 Android 平台上的图像色彩处理技术,重点介绍了如何通过模仿美图秀秀的交互方式,利用 SeekBar 实现对图片颜色的精细调整。文章展示了具体的布局设计和代码实现,帮助开发者更好地理解和应用图像处理技术。 ... [详细]
author-avatar
crazttitan
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有