watchOS 3.0 detect crown rotation in SpriteKit - sprite-kit

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.

Related

Swift macOS SegmentedControl Action not getting called

Description
I am trying to use NSSegmentedControls to transition between Child ViewControllers. The ParentViewController is located in Main.storyboard and the ChildViewControllers are located in Assistant.storyboard. Each ChildViewController has a SegmentedControl divided into 2 Segments and their primary use is to navigate between the ChildViewControllers. So they are set up as momentaryPushIn rather than selectOne. Each ChildViewController uses a Delegate to communicate with the ParentViewController.
So in the ParentViewController I added the ChildViewControllers as following:
/// The View of the ParentViewController configured as NSVisualEffectView
#IBOutlet var visualEffectView: NSVisualEffectView!
var assistantChilds: [NSViewController] {
get { return [NSViewController]() }
set(newValue) {
for child in newValue { self.addChild(child) }
}
}
override func viewDidLoad() {
super.viewDidLoad()
// Do view setup here.
addAssistantViewControllersToChildrenArray()
}
override func viewWillAppear() {
visualEffectView.addSubview(self.children[0].view)
self.children[0].view.frame = self.view.bounds
}
private func addAssistantViewControllersToChildrenArray() -> Void {
let storyboard = NSStoryboard.init(name: "Assistant", bundle: nil)
let exampleChild = storyboard.instantiateController(withIdentifier: "ExampleChild") as! ExampleChildViewController
let exampleSibling = storyboard.instantiateController(withIdentifier: "ExampleSibling") as! ExampleSiblingViewController
exampleChild.navigationDelegate = self
exampleSibling.navigationDelegate = self
assistantChilds = [exampleChild, exampleSibling]
}
So far so good. The ExampleChildViewController has an NSTextField instance. While I am in the scope of the TextField, I can trigger the action of the SegmentedControls. Its navigating forward and backward as it should. But once I leave the scope of the TextField I can still click the Segments, but they are not triggering any action. They should be able to navigate forward and backward even if the TextField is not the current "First Responder" of the application. I think I am missing something out here, I hope anyone can help me with this. I know the problem is not the NSSegmentedControl because I am seeing the same behavior with an NSButton, which is configured as Switch/Checkbox, in the SiblingViewController. I just don't have any idea anymore what I am doing wrong.
It`s my first time asking a question myself here, so I hope the way I am doing is fine for making progress with the solution. Let me know if I can do something better/different or if I need to provide more information about something.
Thanks in advance!
Additional Information
For the sake of completeness:
The ParentViewController itself is embedded in a ContainerView,
which is owned by the RootViewController. I can't imagine this does
matter in any way, but this way we are not missing something out.
I am actually not showing the navigation action, because I want to
keep it as simple as possible. Furthermore the action is not problem,
it does what I want it to do. Correct me if I am wrong with this.
Possible solutions I found while researching, which did not work for me:
Setting window.delegate of the ChildViewControllers to NSApp.windows.first?.delegate
Setting the ChildViewController to becomeFirstResponder in its func viewWillAppear()
visualEffectView.addSubview(self.children[0].view, positioned: NSWindow.OrderingMode.above, relativeTo: nil)
Related problems/topics I found while researching:
Basic segmented control not working
Adding and Removing Child View Controllers
NSSegmentedControl - Odd appearance when placed in blur view
How to set first responder to NSTextView in Swift?
How to use #selector in Swift 2.2 for the first responder
Accessing methods, actions and/or outlets from other controllers with swift
How to use Child View Controllers in Swift 4.0 programmatically
Container View Controllers
issues with container view
Control a NSTabViewController from parent View
How to detect when NSTextField has the focus or is it`s content selected cocoa
SOLUTION
let parentViewControllerInstance = self.parent as! ParentViewController
segmentedControl.target = parentViewControllerInstance
In my case I just had to set the delegate as the target of the sendAction method.
Background
Ok, after hours of reading the AppKit Documentation I am now able to answer my own question.
First, debugging the UI showed that the problem was definitely not in the ViewHierarchy.
So I tried to think about the nature of NSButton and NSSegmentedControl. At some point I noticed that both are subclasses of NSControl.
class NSSegmentedControl : NSControl
class NSButton : NSControl
The AppKit Documentation says:
Discussion
Buttons are a standard control used to initiate actions within your app. You can configure buttons with many different visual styles, but the behavior is the same. When clicked, a button calls the action method of its associated target object. (...) You use the action method to perform your app-specific tasks.
The bold text points to the key of the solution – of its associated target object. Typically I define the action of an control item like this:
button.action = #selector(someFunc(_:))
This causes the NSControl instance to call this:
func sendAction(_ action: Selector?, to target: Any?) -> Bool
Parameter Description from the documentation:
Parameters
theAction
The selector to invoke on the target. If the selector is NULL, no message is sent.
theTarget
The target object to receive the message. If the object is nil, the application searches the responder chain for an object capable of handling the message. For more information on dispatching actions, see the class description for NSActionCell.
In conclusion the NSControl instance, which was firing the action method (in my case the NSSegmentedControl), had no target to send its action to. So it was only able to send its action method across the responder chain - which obviously has been nil while the first responder was located in another view.

MTKView delegate does not work [duplicate]

This question already has answers here:
Swift can't call protocol method via delegate
(2 answers)
Closed 4 years ago.
I am writing a simple metal app.
I tried to separate view and render. So I write a MTKViewDelegate class to to do render work and set the view's delegate to it. But it does not work. But when I make the view controller instance as the delegate, it works.
blow is my code
class ViewController: UIViewController,MTKViewDelegate {
override func viewDidLoad() {
super.viewDidLoad()
let view = self.view as! MTKView
//view.delegate = self //this works,print 'drawing'
view.delegate = ViewController() //this does not work, not print anyting
print("view did load")
}
func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) {
print("change size")
}
func draw(in view: MTKView) {
print("drawing")
}
}
Why this happens?
update
What I really meant was ,why when I set current instance of ViewController as view's delegate , it worked, but when I create a new instance, it did not work.
What I really want to do is making a render class to do render work as MTKView's delegate. But it did not work, so I test it with ViewController.
Thanks for matt's answer,I knew the reason is that , the delegate property of MTKView is weak referenced, so it could not hold the new created instance of MTKViewDelegate. The new created instance is recycled by garbage colletor.
What I need was just that the delegate property of MTKViewDelegate is 'weak'.When people do not notice this, the situation is confusing.
So,I think my question is valuable, and it's not like question Swift can't call protocol method via delegate. I think I should not be blocked to ask questions again.
The problem is that this line:
view.delegate = ViewController()
...makes a second ViewController, makes it the delegate, and then throws it away.
You (the class containing this code) are the ViewController you want to be the delegate. So you need to say:
view.delegate = self
It works when you say that because self is a persistent object. The other ViewController() comes into existence and just vanishes again; it has no persistence so it can't do anything.
Why do you think it's wrong to say view.delegate = self? It's normal and it works.

Set title for watch kit view

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")
}
}

Adding SKScene to UIViewController gives me errors

What I want
An infinite background that randomly adds objects (in my cases little stars) to the middle of the screen. Then those objects needs to move out of the screen with a certain speed. This way I want to give the user the feeling they are moving "into space", seeing stars they are passing.
What I have
A single view application with a UIViewController class.
Problem
I think the right way is to add a SKScene which will do the task I want. However, adding the SKScene class next to UIViewController gives me an error: "multiple inheritance from classes 'UIViewController' and 'SKScene'". I need to add the SKScene to an existence UIViewController, in order to call some important functions. When searching for this on Stackoverflow I came across this: SKScene in UIViewController But I can not call certain functions like "override func update(current time: CFTimeInterval){}", because the answer doesn't show how to implement the SKScene class. This answer deletes the whole UIViewController class: Adding an SKScene to a UIViewController?. The other answers are not helping me.
Question
What is the correct way of adding a SKScene class to an existence UIViewController class, so I can accomplish what I want?
Edit: Came across this on 9gag and its exactly what I want: http://9gag.com/gag/aWmZAPZ Those stars are coming from left top, the respawning should be in the middle of the screen at my app.
Certainly, it's possible. Instead of directly add SKScene to UIViewController, you need to warp SKScene by SKView first, and then add SKView to UIViewController
import UIKit
import SpriteKit
class GameViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Configure the view.
// Core:- convert the base view/UIView of UIViewController to SKView
let rootView = self.view as! SKView
// skView.showsFPS = true
// skView.showsNodeCount = true
/* Sprite Kit applies additional optimizations to improve rendering performance */
rootView.ignoresSiblingOrder = true
rootView.frameInterval = 1
//init your SKScene
let rootMenuScene = YourScene(size: rootView.bounds.size)
rootMenuScene.scaleMode = .resizeFill
//warp in SKView
rootView.presentScene(rootMenuScene)
}
...
}

Accessing UINavigationController from rootVC Subview (subview loaded from Nib)

The main ViewController is embedded in a UINavigationController subclass, and the VC has a subview that is loaded from a nib. The subview is called MenuView, and contains UIButtons that will link to other VCs.
To keep my main ViewController less unruly, I have put all these buttons into a subview that loads from a nib that animates the menu opening and closing.
However, I would like to present other view controllers from these, sometimes "Modally", sometimes "Show". What I have done seems to work, but I just want to know if this is alright, or if I have caused some unwanted effects that I'm unaware of (like a strong reference cycle that would cause a memory leak, or something). Or is there a better way to do this?
Some code:
In MenuView.swift
class MenuView: UIView {
var navigationController = CustomNavigationController()
func combinedInit(){
NSBundle.mainBundle().loadNibNamed("MenuViewXib", owner: self, options: nil)
addSubview(mainView)
mainView.frame = self.bounds
}
#IBAction func optionsAction(sender: AnyObject) {
self.navigationController.performSegueWithIdentifier("presentOptions", sender: self)
}
In ViewController.swift
menuView.navigationController = self.navigationController as! CustomNavigationController
Short answer: No, it is not alright to access a view controller from within some view in the hierarchy, because that would break all the MVC rules written.
UIView objects are meant to display UI components in the screen and are responsible for drawing and laying out their child views correctly. That's all there is. Nothing more, nothing less.
You should handle those kind of interactions between views and controllers always in the controller in which the view in question actually belong. If you need to send messages from a view to its view controller, you can make use of either the delegate approach or NSNotificationCenter class.
If I were in your shoes, I would use a delegate when view needs some information from its view controller. It is more understandable than using notification center as it makes it much easier to keep track of what's going on between. If the view controller needs some information from a view (in other words, the other way around), I'd go with the notification center.
protocol MenuViewDelegate: class {
func menuViewDidClick(menuView: MenuView)
}
class MenuView: UIView {
var weak delegate: MenuViewDelegate?
#IBAction func optionsAction(sender: AnyObject) {
delegate?.menuViewDidClick(self)
}
}
Let's look at what's going on at the view controller side:
class MenuViewController: UIViewController, MenuViewDelegate {
override func viewDidLoad() {
...
self.menuView.delegate = self
}
func menuViewDidClick(menuView: MenuView) {
navigationController?.performSegueWithIdentifier("presentOptions", sender: self)
}
}
For more information about communication patterns in iOS, you might want to take a look at this great article in order to comprehend how they work.