tvOS - Detect when TVML is dismissed from my UIViewController - swift

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.

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.

How can I refresh an SKScene embed in a previous View Controller when dismissing View Controller with ARSKView?

In my SpriteKit game, I have two View Controllers. One is my MenuViewController, which has an SKView/SKScene in it - this handles the level selection, etc.
I then have my GameSceneViewController - this has an ARSKView/SKScene in it. When a user completes multiple levels and, for example, earns 3 stars for each one, and then uses the pause menu to go back to the MenuViewController, the stars that show for each level are (obviously) not refreshed.
This is because at the moment, when a user wants to go back to the MenuViewController, I simply call:
self.dismiss(animated: true, completion: nil)
I was wondering if there is a function I could call to "refresh" the MenuSceneController when a user dismisses the GameSceneViewController.
First of all add the following method to MenuSceneController
class MenuSceneController: UIViewController {
func reloadScene() {
// write here your logic to reload the scene
}
}
Now when you dismiss GameSceneViewController you can call that method
class GameSceneViewController: UIViewController {
func goBackToMenu() {
guard let menuSceneController = presentingViewController as? MenuSceneController else {
debugPrint("Error, this view controller was not presented by a MenuSceneController")
return
}
self.dismiss(animated: true) {
menuSceneController.reloadScene()
}
}
}

macOS application like Photos.app on macOS

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.

Mac app: Button frames and images invisible after view controller transition

After a transition, the button design and image content is missing in the running app. It looks like this:
The code for my custom segue is like this:
override func perform() {
// build from-to and parent-child view controller relationships
let sourceViewController = self.sourceController as! NSViewController
let destinationViewController = self.destinationController as! NSViewController
let containerViewController = sourceViewController.parent! as NSViewController
// add destinationViewController as child
containerViewController.insertChildViewController(destinationViewController, at: 1)
//perform transition
containerViewController.transition(from: sourceViewController, to: destinationViewController, options: NSViewControllerTransitionOptions.slideLeft, completionHandler: nil)
// lose the sourceViewController, it's no longer visible
containerViewController.removeChildViewController(at: 0)
}
This is just a guess. But it looks like your window is a visual effect view/window combo. Which is fine. But I think the default behavior is for all subviews to adopt the "vibrancy" appearance. Which can have weird behaviors with images because it tries to use the alpha channels of the image to make certain effects. Have you tried setting the "allowsVibrancy" property of the image view or its parent view to NO?

Warning: Attempt to present ViewController on ViewController which is already presenting ViewController

I have a view controller with a toolbar with 3 UIButtons that open a new view controller as a popover. I created the segues in Storyboard and selected "Present as Popover". The popovers work but when the user taps on another button while a popover is currently open, I get this error:
Warning: Attempt to present <Fingerpainter.OpacityViewController: 0x79095110> on <Fingerpainter.DrawingViewController: 0x7b278000> which is already presenting <Fingerpainter.BrushSizeViewController: 0x79573770>
Is there a way to like make sure all popovers are closed before opening a new one? Here's my prepareForSegue method in the main ViewController (containing the toolbar):
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let identifier = segue.identifier ?? ""
let popoverPresentationController = segue.destinationViewController.popoverPresentationController
popoverPresentationController!.delegate = self
switch identifier {
case Storyboard.BrushSizeSegueIdentifier:
if let brushSizeViewController = popoverPresentationController?.presentedViewController as? BrushSizeViewController {
// set properties in brushSizeViewController
}
case Storyboard.OpacitySegueIdentifier:
if let opacityViewController = popoverPresentationController?.presentedViewController as? OpacityViewController {
//set properties in opacityViewController
}
case Storyboard.ColorSegueIdentity:
if let colorViewController = popoverPresentationController?.presentedViewController as? ColorViewController {
//set properties in colorViewController
}
default:
break
}
}
Is there a way to like make sure all popovers are closed before opening a new one
It's the other way around. It's your job to make sure that while the popover is present, a button that summons another popover is not tappable. You can do this by disabling the button, but more commonly, in order to coordinate the disabling of the button with the presence of the popover, it's done by adjusting the popover presentation controller's passthroughViews.
Unfortunately there's a massive and long-standing bug where even setting the passthroughViews to nil doesn't prevent toolbar buttons from being tappable. The workaround is to do it with a delay. A lot of my popover code adds this sort of thing:
if let pop = popoverPresentationController {
delay(0.1) {
pop.passthroughViews = nil
}
}
(where delay is described here: https://stackoverflow.com/a/24318861/341994).