macOS application like Photos.app on macOS - swift

I'm trying to create a macOS app like Photos.app. The NSWindowController has a toolbar with a segmented control. When you tap on the segmented control, it changes out the the NSViewController within the NSWindowController.
What I have so far is an NSWindowController with an NSViewController. I have subclassed NSWindowController where I have the method that gets called whenever the user taps on the segmented control.
Essentially, whatever segment is clicked, it will instantiate the view controller that is needed and set it to the NSWindowController's contentViewController property.
Is this the correct way of doing it?
Also, the NSWindowController, I am thinking, should have properties for each of the NSViewControllers it can switch to that get lazy loaded (loaded when the user taps them and they get held around to be re-used to prevent re-initializing).
Code:
import Cocoa
class MainWindowController: NSWindowController
{
var secondaryViewController:NSViewController?
override func windowDidLoad()
{
super.windowDidLoad()
// Implement this method to handle any initialization after your window controller's window has been loaded from its nib file.
}
#IBAction func segmentedControlDidChange(_ sender: NSSegmentedControl)
{
print("Index: \(sender.selectedSegment)")
if sender.selectedSegment == 3 {
if secondaryViewController == nil {
let viewController = storyboard?.instantiateController(withIdentifier: "SecondaryViewController") as! NSViewController
secondaryViewController = viewController
}
self.window?.contentViewController = self.secondaryViewController
}
}
}
I'm new to macOS development, however, I've been doing iOS for quite some time. If there is a better way, I'd like to know about it. Thanks!!!

to move the tab/segmented-control to the titlebar, you need:
add toolbar to window, and add the controls to the toolbar,
hide title:
class TopLevelWindowController: NSWindowController {
override func windowDidLoad() {
super.windowDidLoad()
if let window = window {
// reminder like style
// window.titlebarAppearsTransparent = true
window.titleVisibility = .hidden
// window.styleMask.insert(.fullSizeContentView)
}
}
}
now, toolbar will be merged into the top bar position.

Related

How to reference a View from within a Window Controller?

I'm having a Window Controller with a toolbar. I also have a View Controller containing some views. How do I reference a view from the View Controller within my Window Controller? I'm still learning macOS development and I'm missing the bigger picture how code is structured and classes are meant to interact.
My concrete problem right now is this: Using XCode 9.4.1 I have a window with a toolbar and a button in it. That's how my WindowsController.swift looks like:
import Cocoa
class WindowController: NSWindowController {
override func windowDidLoad() {
super.windowDidLoad()
window?.titleVisibility = .hidden
}
#IBAction func startExport(_ sender: NSButton) {
print("Start Export")
}
}
In the ViewControllerScene there's a WKWebView that's loading a web page. When the button in the toolbar is pressed, I want to call that Web Views takeSnapshot method. So I need a reference in WindowsController.swift to that Web View, but control-dragging the Web View from the storyboard to WindowsController.swift in the assistant editor doesn't let me create that outlet.
This:
let vc = contentViewController as? ViewController
will take you to your view controller.

NSTabViewController add NSToolbarItems

I'd like to use the NSTabViewController for switching through 6 different Tabs with the toolbar style.
All tabs have in common that they show different aspects of a Customer entity.
Now I want to add aditional NSToolbarItems to the toolbar of the NSTabViewController? But I haven't found a way to access the toolbar.
I also would like to add Space between the ToolbarItems.
Is there a way to do so?
Or how can I add my ViewController from the Storyboard to a NSTabView without using NSTabViewController?
Regards
Oliver
In the meantime I've tried another approach that I thought was more promising but lead to another strange behaviour:
I've created a new NSViewController and put a NSTabView inside. In order to load my already existing ViewControllers I used this
override func viewDidLoad() {
super.viewDidLoad()
let customerController = self.storyboard?.instantiateController(withIdentifier: NSStoryboard.SceneIdentifier("CustomerVCID")) as! CustomerViewController
let servicesController = self.storyboard?.instantiateController(withIdentifier: NSStoryboard.SceneIdentifier("ServicesVCID")) as! ServicesController
customerController.customer = self.customer
servicesController.customer = self.customer
self.tabView.tabViewItems[0].view = customerController.view
self.tabView.tabViewItems[1].view = servicesController.view
}
That indeed worked, but now all my NSButtons that have actions will cause my application to crash.
There is only one toolbar per window. So your NSTabViewController shares it.
Select toolbar mode of NSTabViewController
Override NSWindowController and add your items
Example:
override func windowDidLoad() {
super.windowDidLoad()
window?.toolbar?.insertItem(withItemIdentifier: .print, at: 0)
}
You can always access your toolbar via following path view->window->toolbar
Your only issue is that there is one delegate per NSToolbar. Which means you have to create your custom NSToolbarItem inside NSTabViewController delegate.
override func toolbar(_ toolbar: NSToolbar, itemForItemIdentifier itemIdentifier: NSToolbarItem.Identifier, willBeInsertedIntoToolbar flag: Bool) -> NSToolbarItem? {
if itemIdentifier == .export {
return ExportToolbarItem.new()
} else {
return super.toolbar(toolbar, itemForItemIdentifier: itemIdentifier, willBeInsertedIntoToolbar: flag)
}
}
Remember your are required to call super. This is because underlying method wants to create bindings to view controller.
In case you need actionable buttons in toolbar just add them without calling super.

Swift Access Objects In View Controller From Window Controller

I'm just getting into development on mac os and I made a simple app for the touch bar that allows you to change the color (with a nscolorpicker) of a label that is also on the touch bar.
Now I would like to get the same effect on the actual window like so: I change the color using the picker on the touch bar and the color of the colorwell in the window changes as well.
This is the code that I currently have for the touch bar actions:
import Cocoa
#available(OSX 10.12.2, *)
class MainWindowController: NSWindowController {
#IBOutlet weak var cptHello: NSColorPickerTouchBarItem!
#IBOutlet var lblHello: NSTextField!
override func windowDidLoad() {
super.windowDidLoad()
// Implement this method to handle any initialization after your window controller's window has been loaded from its nib file.
cptHello.color = NSColor.white
setCol()
}
func setCol(){
lblHello.textColor = cptHello.color
}
#IBAction func colorPicked(_ sender: Any) {
setCol()
}
}
This piece of code resides in MainWindowController.swift which is paired with the window controller.
In the view controller, I have a single NSColorWell that I would like to change the color for inside the function "setCol()". I created an outlet in the view controller for it like so:
#IBOutlet var cwHello: NSColorWell!
So ideally what I want to achieve is something like this:
func setCol(){
lblHello.textColor = cptHello.color
ViewController.cwHello.color = cptHello.color
}
Can this be done at all?

Graphic bug in NSWindow when changing ViewController?

I am using storyboard to create a window controller with a NSToolbar.
Using the toolbar buttons I'm trying to switch the contentViewController of the window to other viewControllers.
#IBAction func switchViewControllers(sender: NSToolbarItem) {
if sender.label == "one" {
self.window?.contentViewController = self.storyboard?.instantiateControllerWithIdentifier("one") as? NSViewController
}
if sender.label == "two" {
self.window?.contentViewController = self.storyboard?.instantiateControllerWithIdentifier("two") as? NSViewController
}
}
Clicking on the toolbarItems switches the ViewControllers as expected but I am noticing some graphic glitches at the edges of the window when switching thew ViewControllers.
Am I missing something?
Startup
When switching views

tvOS - Detect when TVML is dismissed from my UIViewController

I have a view controller for my app that calls another view controller modally to cover the screen with a blur effect. Inside this other view controller, I'm displaying a TVApplicationController to display TVML content with transparent background on top of this blurred view.
let appControllerContext = TVApplicationControllerContext()
guard let javaScriptURL = NSURL(string: AppDelegate.TVBootURL) else {
fatalError("unable to create NSURL")
}
appControllerContext.javaScriptApplicationURL = javaScriptURL
appControllerContext.launchOptions["BASEURL"] = AppDelegate.TVBaseURL
appController = TVApplicationController(context: appControllerContext, window: nil, delegate: self)
appController?.navigationController.modalPresentationStyle = UIModalPresentationStyle.OverFullScreen
self.presentViewController((appController?.navigationController)!, animated: true, completion: nil)
What I want to do is, when I press the MENU button, to make the TVML content go away and to dismiss my modal blur view controller. The problem is that I'm not being able to detect the "dismissal" of the TVML content so I can close my modal view controller.
I tried to use the TVApplicationControllerDelegate to receive the messages that might come while using it but nothing helped.
I just found a workaround for this. I created a small class like this:
import UIKit
class HiddenView: UIView {
override func canBecomeFocused() -> Bool {
return true;
}
}
Then, what I did is to create an instance of this HiddenView on the ViewDidLoad of the blurred view controller and add it to the view controllers's view.
let hiddenView = HiddenView(frame: CGRectMake(0,0,10,10))
self.view.addSubview(hiddenView)
// it won't appear on the screen since it has no color/text/etc
Now, when I press the MENU button on the remote, when the TVML content is dismissed, the delegate method didUpdateFocusInContext on my blurred modal view controller is called, so I can dismiss it like this:
override func didUpdateFocusInContext(context: UIFocusUpdateContext, withAnimationCoordinator coordinator: UIFocusAnimationCoordinator) {
self.dismissViewControllerAnimated(true, completion: nil)
}
If anyone knows a better way to handle this than having to do this workaround, it would be nice to know.