内容简介:历届 WWDC 众多和 macOS 开发相关的视频中只要涉及到 UI 部分必会牵扯上 Storyboard,它能帮你辅助画 UI,使用 Auto Layout 进行布局,甚至你可以不用写一行代码仅仅使用它和 Core Data 就能完成一个 CURD 的功能。苹果总是极力的推荐所有的开发者(尤其是初学者)使用 Storyboard,它给人最直观的感受,配置参数也都做了调教(有时候开启一个参数需要手敲好多行代码)。但总有那么一群人就是不爱使用 Storyboard(比如我),尤其 App 涉及比较多的界面和交
前言
历届 WWDC 众多和 macOS 开发相关的视频中只要涉及到 UI 部分必会牵扯上 Storyboard,它能帮你辅助画 UI,使用 Auto Layout 进行布局,甚至你可以不用写一行代码仅仅使用它和 Core Data 就能完成一个 CURD 的功能。苹果总是极力的推荐所有的开发者(尤其是初学者)使用 Storyboard,它给人最直观的感受,配置参数也都做了调教(有时候开启一个参数需要手敲好多行代码)。
但总有那么一群人就是不爱使用 Storyboard(比如我),尤其 App 涉及比较多的界面和交互的时候感觉还是代码更可控,团队开发在代码仓库管理上也更为方便。
何奈在网络上存在的绝大多数的教程和视频都是以 Storyboard 为主,稍不留神可能就忘记设置了哪里,再或者过段时间举一反三的时候总会忘记在 Storyboard 设置了什么而无法实现同样的效果。
恰好我最近比较闲花了点时间在写 macOS App,过程中体会到了 macOS 和 iOS 两个体系存在了巨大的差异,可能是习惯了 iOS 框架的编码方式就感觉到 macOS 格格不入,想要完成一个在 iOS 上简单的界面但 macOS 上就要想尽各种办法来去实现,迫切期待在马上来临的 WWDC 2019 上宣布新的解决方案。
开始教学
打开 Xcode 选择 macOS 下面的 Cocoa App 进行下一步,填写好项目名称后勾不勾选 Use Storyboard 都没有关系,勾选就会创建一个 Main.storyboard
的文件,不勾选也会创建一个 MainMenu.xib
的文件,选择项目保存的路径就创建好第一个项目了。
无论默认的是 Main.storyboard
或 MainMenu.xib
都会一个 Main Menu 控件这里咱们先不管它,除此之外还会有一个 Window 控件,一个 macOS App 基础的层级关系是这样的
+--------------------------------------------------------------+ | NSWindow | | +--------------------------------------------------------+ | | | NSViewController | | | | +--------------------------------------------------+ | | | | | NSView | | | | | +--------------------------------------------------+ | | | +--------------------------------------------------------+ | +--------------------------------------------------------------+
选中 Window 控件后后选择删除( Main.storyboard
还需要再删除默认生成的 ViewController 控件),删除后样子是这样的
由于我们删除了默认的 NSWindow 因此需要在 Appdelegate.swift
文件创建一个:
import Cocoa @NSApplicationMain class AppDelegate: NSObject, NSApplicationDelegate { // 创建默认的 Window lazy var window: NSWindow = { let w = NSWindow(contentRect: NSMakeRect(0, 0, 640, 480), styleMask: [.titled, .resizable, .miniaturizable, .closable, .fullSizeContentView], backing: .buffered, defer: false) // 设置最小尺寸 w.minSize = NSMakeSize(320, 240) // 打开显示在屏幕的中心位置 w.center() return w }() }
默认的 window 设置好了,我们让他赶紧显示出来吧,在 applicationDidFinishLaunching
方法添加如下代码:
func applicationDidFinishLaunching(_ aNotification: Notification) { // 设置为 mainWindow 这样我们才能通过下面的代码调用 window.makeKeyAndOrderFront(nil) // 设置 mainWindow 的标题 NSApplication.shared.mainWindow?.title = "Hello world" }
注意看上面代码块的备注,这两行代码不能颠倒,否则 NSApplication.shared.mainWindow
返回的是 nil
,我们来运行一下看看,是不是就能看到一个 640x480 的窗口,标题显示的 hello world
用鼠标拖拽缩放大小看看是不是到一个保留尺寸就无法再缩小了,这个就是通过代码设置的最小尺寸。虽然 App 可以运行了内容还是空空的,根据上面画出来的层级关系还需要一个 NSViewController 充当 contentViewController,它相当于 iOS 的 rootViewController 这样说应该就很容易理解了吧。
假设 App 需要完成这样一个功能,界面有一个 Label 和 Button,点击 Button 可以改变 Label 的文字内容:
+----------------------+ +----------------------+ | | | | | Click the button | | Yeah! | | | | | | +------------------+ | -------> | +------------------+ | | | Click me | | | | Click me | | | +------------------+ | | +------------------+ | | | | | +----------------------+ +----------------------+
我们用原生 Auto Layout 布局后的代码是这样的
import Cocoa class ViewController: NSViewController { lazy var label: NSTextField = { let v = NSTextField(labelWithString: "Press the button") v.translatesAutoresizingMaskIntoConstraints = false return v }() lazy var button: NSButton = { let v = NSButton(frame: .zero) v.translatesAutoresizingMaskIntoConstraints = false return v }() override func viewDidLoad() { super.viewDidLoad() view.addSubview(label) view.addSubview(button) NSLayoutConstraint.activate([ label.centerXAnchor.constraint(equalTo: view.centerXAnchor), label.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: -20), button.centerXAnchor.constraint(equalTo: view.centerXAnchor), button.topAnchor.constraint(equalTo: label.bottomAnchor, constant: 20), button.heightAnchor.constraint(equalToConstant: 30), button.widthAnchor.constraint(equalToConstant: 100) ]) button.title = "Click me" button.target = self button.action = #selector(onClickme) } @objc func onClickme(_ sender: NSButton) { label.textColor = .red label.stringValue = "Yeah!" } }
代码看着不错的样子,还差最后一步把 ViewController 添加到 NSWindow 里吧,返回 AppDelegate.swift
文件找到上次编辑的 applicationDidFinishLaunching
方法里面在代码的末尾添加最后的代码:
func applicationDidFinishLaunching(_ aNotification: Notification) { // 设置为 mainWindow 这样我们才能通过下面的代码调用 window.makeKeyAndOrderFront(nil) // 设置 mainWindow 的标题 NSApplication.shared.mainWindow?.title = "Hello world" // 设置 contentViewController let contentViewController = ViewController() // or ViewController(nibName:nil, bundle: nil) window.contentViewController = contentViewController }
咦?!你这不对啊, NSViewController
代码在初始化的时候是不能这样的,必须通过 init(coder:)
或 init(nibName:bundle:)
这两种方法才行,这样写运行会提示 “could not load the nibName: WithoutStoryboard.ViewController in bundle (null).” 错误的!
观察的不错嘛,这点小细节都你发现了,NSViewController 类通常都是通过 init(nibName:bundle:)
进行初始化来关联 xib 界面,既然我们抛弃了 Storyboard(xib) 那怎么办呢,这里理解 NSViewController 的 生命周期 (macOS 10.10 之后版本)
+----------------------------------+ +------------------------------+ | init +-->+ loadView() | +-----------------+----------------+ +---------------+--------------+ | Storyboard(xib) | code | | +----------------------------------+ v | init(coder:) | init(nibName: | +---------------+--------------+ | | bundle:) | | viewDidLoad() | +-----------------+----------------+ +---------------+--------------+ | v +----------------------------------+ +---------------+--------------+ | viewWillDisappear() +<--+ viewWillAppear() | +-----------------+----------------+ +------------------------------+ | | updateViewConstraints() | | | | v | viewWillLayout() | +-----------------+----------------+ | | | viewDidDisappear() | | viewDidLayout() | +----------------------------------+ +------------------------------+
生命周期和 iOS 的 UIViewController 也差不多,从上面的生命周期来看只能覆写 loadView 方法才行:
class ViewController: NSViewController { override func loadView() { // 设置 ViewController 大小同 mainWindow guard let windowRect = NSApplication.shared.mainWindow?.frame else { return } view = NSView(frame: windowRect) } }
看了上面代码也就明白了为什么在 AppDelegate.swift
设置 contentViewController 需要在代码块的末尾添加了吧,如果在最开始添加那就无法获取到 mainWindow 也就无法设置 ViewController 的 frame。这里还有个小提醒 macOS 10.10 之后版本在覆写该方法如果调用了 super.loadView()
方法就会自动加载同名的 xib 文件 绝对不能填写 。
让我们在运行一次看看?
Ta-Da! 完成了!
结语
这算是一个开篇,后续还会再继续整理,尽请期待。
以上所述就是小编给大家介绍的《创建无 Storyboard(xib) 的 macOS NSWindow》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
图解服务器端网络架构
[日] 宫田宽士 / 曾薇薇 / 人民邮电出版社 / 2015-4 / 79.00元
本书以图配文,详细说明了服务器端网络架构的基础技术和设计要点。基础设计是服务器端网络架构最重要的一个阶段。本书就立足于基础设计的设计细分项目,详细介绍各细分项目的相关技术和设计要点。全书共分为5章,分别讲述进行物理设计、逻辑设计、安全设计和负载均衡设计、高可用性设计以及管理设计时所必需的技术和设计要点。一起来看看 《图解服务器端网络架构》 这本书的介绍吧!