Set title for watch kit view - swift

How do I set the title for the current view in my watch kit (swift) app programmatically?
By identifier I mean the three "hashtags"

You can set the title on the current Interface Controller, not a View. You just need to call setTitle() on the Interface Controller itself.
One feasible method, where you can set the title for your controller is the awake(withContext:) function.
class WatchController: WKInterfaceController {
override func awake(withContext context: Any?) {
self.setTitle("Title")
}
}

Related

Using NSPageController in History Mode (Content Mode) and no View Controllers?

I'm building an app with three views and I want the user to be able to swipe between them but also navigate between them by using custom buttons, like browsing in safari but only with three available views. The user can navigate between them in a somewhat random order and I provide back and forward buttons in a toolbar.
After reading Apple's documentation I believe history mode is the option I'm looking for as I can provide the views separately and navigation is not predefined like in book mode, however when I implement it it adds my view to the arrangedObjects property but the view itself doesn't change. I've correctly wired the page controller view programmatically to a subview of the app's contentView and I can see said view correctly displayed while debugging the view hierarchy.
The "Page Controller Managed View" is set as NSPageController.view programatically.
When I try using navigateForward(to:) the view gets added correctly to the arrangedObjects and the selectedIndex is correctly set but the NSPageController.view doesn't change at all (remains a blank NSView like the loaded view from storyboard). This is my code:
class MainViewController: NSViewController {
// MARK - Instance Properties
var notificationObservers: Array<NSObjectProtocol> = []
var pageController: NSPageController?
// MARK - Interface Builder Outlets
#IBOutlet var mainContentView: NSView? // Named "Page Controller Managed View" in storyboard
override func viewDidLoad() {
super.viewDidLoad()
pageController = (NSStoryboard.main!.instantiateController(
withIdentifier: "MainPageController") as! NSPageController)
pageController!.delegate = AppDelegate
pageController!.view = mainContentView!
notificationObservers.append(
NotificationCenter.default.addObserver(
forName: NSApplication.didFinishLaunchingNotification,
object: NSApp,
queue: .main,
using: { [weak self] (notification) in
// If the 'arrangedObjects' property is 0 it means no other object has set the first view
if self?.pageController!.arrangedObjects.count == 0 {
// Set the first view
self.pageController!.navigateForward(to: AppDelegate.firstView.nsView)
}
})
)
}
deinit {
// Perform cleanup of any Apple Notification Center notifications to which we may have registered
notificationObservers.forEach { (observer) in
NotificationCenter.default.removeObserver(observer)
}
}
}
This is the view hierarchy while running the app:
What am I doing wrong? Why is the page controller view not being updated with the navigateForward(to:) view?
This could shorten the amount of code I have to use by a lot compared to using view controllers (Book mode) so any help is appreciated.

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.

How to make embedded view controller part of the responder chain?

I am developing a Mac app using storyboards. I have a window that presents an NSViewController as its contents, which contains a "container view controller" that embeds an NSSplitViewController.
The expected behaviour is for the NSSplitViewController to be part of the responder chain, such that a menu item that triggers the toggleSidebar action on the first responder actually collapses the item of the NSSplitViewController that's marked as a sidebar.
However, this simply does not happen and the menu item remains disabled. So my question is, how can-I get the NSSplitViewController to be part of the responder chain?
I noticed that maybe some of these solutions have worked, but i adapted a more general purpose answer from https://stackoverflow.com/a/30938725/6938357.
I made an extension on NSViewController to look for supplemental targets. Works on NSSplitViewController as well as any general NSViewController with child(ren).
extension NSViewController {
open override func supplementalTarget(forAction action: Selector, sender: Any?) -> Any? {
if let target = super.supplementalTarget(forAction: action, sender: sender) {
return target
}
for child in children {
var target = NSApp.target(forAction: action, to: child, from: sender) as? NSResponder
if target?.responds(to: action) == false {
target = child.supplementalTarget(forAction: action, sender: sender) as? NSResponder
}
if target?.responds(to: action) == true {
return target
}
}
return nil
}
}
If you only want this to search on a single view controller, put this implementation there instead. This extension applies to all NSViewControllers and its subclasses.
Check out the nextReponsder property of NSResponder. This property defines the responder chain. It's normally set automatically to follow the responder change defined by the Cocoa framework, but you can alter it to insert/skip/divert the chain in a different direction.
For example, at some point (don't ask me when), Cocoa started including the window's controller in the responder chain. So that my apps work consistently on all versions of macOS, I'll include code like this my window's controller:
- (void)windowDidLoad
{
// Sent when the controller's window has been loaded from the nib
[super windowDidLoad];
NSWindow* window = self.window;
// Make sure this window controller is in the responder chain
NSResponder* nextResponder = window.nextResponder; // get our window's next responder
if (nextResponder!=self)
{
// running earlier OS X that does not include the window controller in the chain: patch us in
self.nextResponder = nextResponder;
window.nextResponder = self;
}
-windowDidLoad, -viewDidLoad, and -awakeFromNib are all good places to adjust the responder chain so they include, or exclude, whatever objects you want.
I ended up getting this to work (in Swift 4) by adding my view controller to the window delegate. After that, my view controller was part of the responder chain (which made application menu items work in my view controller).
//Step 1: Add NSWindowDelegate to the controller
class MyViewController: NSViewController, NSWindowDelegate{
override func viewDidLoad() {
super.viewDidLoad()
//Step 2: Add the view controller to the window delegate
if let window = NSApp.windows.first{
window.delegate = self
}
}
}
I hope that helps someone else. :)

watchOS 3.0 detect crown rotation in SpriteKit

So as of watchOS 3.0 you are now able to get the rotation of the digital crown. I managed to use the crownDidRotate function in an InterfaceController.
But I can't get the rotation of the crown from inside a SKScene Class.
Can anybody help me with this I'm pretty lost right now?
Thanks.
To get those crownDidRotate calls in your interface controller, you had to adopt the WKCrownDelegate protocol in your interface controller, and set your interface controller as the delegate of its crownSequencer.
To get crownDidRotate calls in some other class, adopt the WKCrownDelegate protocol in that class, and set an instance of that class as the delegate of your interface controller's crownSequencer.
Presumably you already have some code like this to set up your SpriteKit scene:
class InterfaceController: WKInterfaceController {
#IBOutlet var spriteGizmo: WKInterfaceSKScene!
override func awake(withContext context: AnyObject?) {
super.awake(withContext: context)
let scene = MyScene(fileNamed: "MyScene")
spriteGizmo.presentScene(MyScene(fileNamed: "MyScene"))
}
}
If you've declared WKCrownDelegate conformance in your MyScene class, just add a line to set it as the delegate of the interface controller's crown sequencer:
let scene = MyScene(fileNamed: "MyScene")
spriteGizmo.presentScene(MyScene(fileNamed: "MyScene"))
crownSequencer.delegate = scene
(Alternatively, you may set your WKInterfaceSKScene's scene in the Storyboard. In that case, you can still reference the WKInterfaceSKScene from your interface controller with an IBOutlet. Then in awake(withContext:), you can access the scene through that outlet and set it as the crown delegate.)
In watchOS 3 just any object object can get digital crown events by setting them as a delegate:
let crownSequencer = WKExtension.shared().rootInterfaceController!.crownSequencer
crownSequencer.delegate = self
crownSequencer.focus()
Then read back the value by implementing:
func crownDidRotate(_ crownSequencer: WKCrownSequencer?, rotationalDelta: Double)
It is important to call the focus(), especially for controllers whose UI fits the screen and do not need actual scrolling.

Menubar with Storyboard - validateMenuItem not get called

I'm trying to setup a menubar Application using storyboard but my validateMenuItem method not get called.
I will try to explain what i did.
First i dragged a Menu Item in my Application Scene. Then one Object for my MenuController. Created a MenuController (MenuController.swift) and filled it with code. Back in my storyboard I set my Menu delegate to MenuController and MenuController Outlet to Menu. (I'm not totally sure whether i have set the delegates correctly.)
When i start the app, the menu icon appears and the first item title is set to test. But when i'm clicking the icon the validateMenuItem method not get called.
MenuController.swift
import Cocoa
class MenuController: NSObject {
var statusItem = NSStatusBar.systemStatusBar().statusItemWithLength(-1)
#IBOutlet weak var statusMenu: NSMenu!
#IBOutlet weak var item1: NSMenuItem!
override func awakeFromNib() {
print("awakeFromNib")
self.item1.title = "Test"
let icon = NSImage(named: "menubarIcon")
statusItem.image = icon
statusItem.menu = statusMenu
}
override func validateMenuItem(menuItem: NSMenuItem) -> Bool {
print("validateMenuItem")
return true
}
}
Storyboard Menu Delegates
(source: picr.de)
Storyboard MenuController Delegates
(source: picr.de)
Has anybody an idea?
Greets from Austria!
The menu/UI validation mechanism does not query the menu's delegate but uses the item's target to determine the enabled state instead.
If the target is not explicitly set, it walks the responder chain.
To get basic validation, you have to make sure that the following things are setup:
"Auto Enables Items" is checked in Interface Builder (on by default)
You control-dragged the menu's action to the first responder
The menu's action is implemented in a class that is part of the responder chain (e.g. a view controller that manages a set of actions for your UI)
A basic implementation of validateUserInterfaceItem: could look like the following for an item with an action selector called test:
func validateUserInterfaceItem(anItem: NSValidatedUserInterfaceItem) -> Bool
{
if anItem.action() == Selector("test:") {
print("validating item \(anItem)")
return true
}
return true
}