GCVirtualController not displaying with SKScene - sprite-kit

The Virtual controllers appear but are then obscured by objects added to the view. I've also tried adding the virtual controllers in the UIViewController but this doesn't work either.
Is it possible to use GCVirtualController directly with SKScene?
class GameScene: SKScene {
private var _virtualController: Any?
public var virtualController: GCVirtualController? {
get { return self._virtualController as? GCVirtualController }
set { self._virtualController = newValue }
}
override func didMove(to view: SKView) {
let background = SKSpriteNode(imageNamed: ".jpg")
background.zPosition = -1
addChild(background)
let virtualConfig = GCVirtualController.Configuration()
virtualConfig.elements = [GCInputLeftThumbstick, GCInputRightThumbstick, GCInputButtonA, GCInputButtonB]
virtualController = GCVirtualController(configuration: virtualConfig)
virtualController?.connect()
}
}
It appears the issue only occurs when pushing from one ViewController to the GameViewController.
When launching to the GameViewController the issue does not occur.

The Virtual Game Controller can run on any view controller running iOS 15 At least but for the purpose of work it is best viewed in landscape but you need to complete the codes as a physical gamepad.
For the virtual game controller to appear you need to register a physical game controller and apply the functions of notification when connect and disconnect a controller as you do with physical controller exactly.
Here is a code to setup and register a virtual and physical game controller I use and it works for me.
1st you need to import the Game Controller Library
import GameController
Then you define the Virtual Controller Under your Controller Class
class GameViewController: UIViewController {
// Virtual Onscreen Controller
private var _virtualController: Any?
#available(iOS 15.0, *)
public var virtualController: GCVirtualController? {
get { return self._virtualController as? GCVirtualController }
set { self._virtualController = newValue }
}
And then you call the setupGameController Function In Your ViewDidLoad()
override func viewDidLoad() {
super.viewDidLoad()
//your code
setupGameController()
}
and here is the main function to setup your Virtual and physical game controller
func setupGameController() {
NotificationCenter.default.addObserver(
self, selector: #selector(self.handleControllerDidConnect),
name: NSNotification.Name.GCControllerDidBecomeCurrent, object: nil)
NotificationCenter.default.addObserver(
self, selector: #selector(self.handleControllerDidDisconnect),
name: NSNotification.Name.GCControllerDidStopBeingCurrent, object: nil)
if #available(iOS 15.0, *)
{
let virtualConfiguration = GCVirtualController.Configuration()
virtualConfiguration.elements = [GCInputLeftThumbstick,
GCInputRightThumbstick,
GCInputButtonA,
GCInputButtonB]
virtualController = GCVirtualController(configuration: virtualConfiguration)
// Connect to the virtual controller if no physical controllers are available.
if GCController.controllers().isEmpty {
virtualController?.connect()
}
}
guard let controller = GCController.controllers().first else {
return
}
registerGameController(controller)
}
Then to act with the virtual or physical gamepad actions you need to assign the connect and register for the game controller
as
func handleControllerDidConnect(_ notification: Notification) {
guard let gameController = notification.object as? GCController else
{
return
}
unregisterGameController()
if #available(iOS 15.0, *)
{
if gameController != virtualController?.controller
{
virtualController?.disconnect()
}
}
registerGameController(gameController)
}
func handleControllerDidDisconnect(_ notification: Notification) {
unregisterGameController()
if #available(iOS 15.0, *) {
if GCController.controllers().isEmpty
{
virtualController?.connect()
}
}
}
func registerGameController(_ gameController: GCController) {
var buttonA: GCControllerButtonInput?
var buttonB: GCControllerButtonInput?
if let gamepad = gameController.extendedGamepad
{
buttonA = gamepad.buttonA
buttonB = gamepad.buttonB
}
buttonA?.valueChangedHandler = {(_ button: GCControllerButtonInput, _ value: Float, _ pressed: Bool) -> Void in
// Put here the codes to run when button A clicked
print("Button A Pressed")
}
buttonB?.valueChangedHandler = {(_ button: GCControllerButtonInput, _ value: Float, _ pressed: Bool) -> Void in
// Put here the codes to run when button B clicked
print("Button B Pressed")
}
}
func unregisterGameController()
{
}
And Here Is A Result of the code on a very basic ship sample of Xcode

I've encountered the same problem, presenting UIViewController using UINavigationController's present method(no storyboards used).
navigationController.present(
GameViewController(),
animated: false
)
I fixed it by setting UINavigationController's viewControllers property to needed view controllers, instead of pushing.
navigationController.viewControllers = [
UIViewController(),
GameViewController()
]

Related

KVO on SKView is not being called

I am trying to do KVO on an property of SKView but its not working.
I have tried it with .frame and it works like a charm, so why not also on .window ?
To clarify, I am using SpriteView in a SwiftUI app and I am trying to get the bounds of the View.
What I am after is to get the following before the App starts;
print (self.convertPoint(fromView: .zero) ). When I use this in
override func didMove
I'll get Nan/NaN. However Apple Code Level Support said this about using SpriteView and getting the bounds of a view.
The reason you are receiving NaN is that you are calling these methods
before the underlying SKView has been actually presented by SwiftUI,
which is an event that you have no visibility into, and no way to call
code when it happens.
However, when this event does occur, the SKScene’s view property will
have it’s window set from nil to a UIWindow. Therefore, you could use
KVO to observe when the window property is changed, and then make your
calls to convertPoint once there is a non-nil window.
I have so far this:
override func didMove(to view: SKView) {
observe.observe(object: view )
}
class Observer:SKScene {
var kvoToken: NSKeyValueObservation?
func observe(object: SKView) {
kvoToken = object.observe(\.window , options: [ .new] ) { (object, change) in
guard let value = change.newValue else { return }
print("New value is: \(value)")
print ("NEW", self.convertPoint(fromView: .zero) )
}
}
deinit {
kvoToken?.invalidate()
}
}
I have also tried to add an observer like so :
NotificationCenter.default.addObserver(view.window, selector: #selector(test(_:)), name: NSNotification.Name(rawValue: "TestNotification"), object: nil)
The above doesn't seem to do anything. So I am kinda stuck, any help would be appreciated.
The answer (probably) ..
I couldn't read the '.window' property of UIView, so I started to look for another observable property that would change as soon as UIWindow is != nil. I think I have found it in SKScene.view.frame . I am not entirely sure that this is a 100% good answer but it works.
class Observer: NSObject {
dynamic var kvoToken: NSKeyValueObservation?
func observe(object: SKScene ) {
kvoToken = object.observe(\.view?.frame , options: [ .new] ) { (object, change) in
guard let value = change.newValue else { return }
print("New value is: \(value)")
print ("CONVERTING", object.convertPoint(fromView: .zero) )
}
}
deinit {
kvoToken?.invalidate()
}
}
override func didMove(to view: SKView) {
viewer = view.scene
observe.observe(object: viewer )
}

Using a UISegmentedControl like a UISwitch

Is it possible to use a UISegmentedControl with 3 segments as if it was a three-way UISwitch? I tried to use one as a currency selector in the settings section of my app with no luck, it keeps reseting to the first segment when I switch views and that creates a big mess.
I proceeded like that:
IBAction func currencySelection(_ sender: Any) {
switch segmentedControl.selectedSegmentIndex {
case 0:
WalletViewController.currencyUSD = true
WalletViewController.currencyEUR = false
WalletViewController.currencyGBP = false
MainViewController().refreshPrices()
print(0)
case 1:
WalletViewController.currencyUSD = false
WalletViewController.currencyEUR = true
WalletViewController.currencyGBP = false
MainViewController().refreshPrices()
print(1)
case 2:
WalletViewController.currencyUSD = false
WalletViewController.currencyEUR = false
WalletViewController.currencyGBP = true
MainViewController().refreshPrices()
print(2)
default:
break
}
}
The UISegmentedControl is implemented in the
SettingsViewController of the app to choose between currencies to
display in the MainViewController.
(Taken from a comment in #pacification's answer.)
This was the missing piece I was looking for. It provides a lot of context.
TL;DR;
Yes, you can use a three segment UISegmentedControl as a three-way switch. The only real requirement is that you can have only one value or state selected.
But I wasn't grasping why your code referred to two view controllers and some of switching views resulting in resetting the segment. One very good way to do what you want is to:
Have MainViewController present SettingsViewController. Presenting it modally means the user is only doing one thing at a time. When they are making setting changes, you do not want them adding new currency values.
Create a delegate protocol in SettingsViewController and make MainViewController conform to it. This tightly-couples changes made to the settings to the view controller interested in what those changes are.
Here's a template for what I'm talking about:
SettingsViewController:
protocol SettingsVCDelegate {
func currencyChanged(sender: SettingsViewController)
}
class SettingsViewController : UIViewController {
var delegate:SettingsVCDelegate! = nil
var currency:Int = 0
#IBAction func valueChanged(_ sender: UISegmentedControl) {
currency = sender.selectSegmentIndex
delegate.currencyChanged(sender:self)
}
}
MainViewController:
class MainViewController: UIViewController, SettingsVCDelegate {
var currency:Int = 0
let settingsVC = SettingsViewController()
override func viewDidLoad() {
super.viewDidLoad()
settingsVC.delegate = self
}
func presentSettings() {
present(settingsVC, animated: true, completion: nil)
}
func currencyChanged(sender:SettingsViewController) {
currency = sender.currency
}
}
You can also create an enum of type Int to make your code more readable, naming each value as currencyUSD, currencyEUR, and currencyGBP. I'll leave that to you as a learning exercise.
it keeps reseting to the first segment when I switch views
yes, it is. to avoid this situation you should set the correct switch value to the segmentedControl.selectedSegmentIndex every time when you load your view with UISegmentedControl.
UPD
Ok, the behavior of MainViewController can be similar to this:
final class MainViewController: UIViewController {
private var savedValue = 0
override func viewDidLoad() {
super.viewDidLoad()
}
func openSettingsController() {
let viewController = SettingsController.instantiate() // simplify code a bit. use the full controller initialization
viewController.configure(value: savedValue, onValueChanged: { [unowned self] value in
self.savedValue = value
})
navigationController?.pushViewController(viewController, animated: true)
}
}
And the SettingsViewController:
final class SettingsViewController: UIViewController {
#IBOutlet weak var segmentedControl: UISegmentedControl!
private var value: Int = 0
var onValueChanged: ((Int) -> Void)?
func configure(value: Int, onValueChanged: #escaping ((Int) -> Void)) {
self.value = value
self.onValueChanged = onValueChanged
}
override func viewDidLoad() {
super.viewDidLoad()
segmentedControl.selectedSegmentIndex = value
}
#IBAction func valueChanged(_ sender: UISegmentedControl) {
onValueChanged?(sender.selectedSegmentIndex)
}
}
The main idea that you should keep your selected value if you moving from SettingsViewController. For this thing you can create closure
var onValueChanged: ((Int) -> Void)?
that pass back to MainViewController the selected UISegmentedControl value. And in future when you will open the SettingsViewController again you just configure() this value and set it to UI.

Listening for NSWorkspace Notifications in Swift 4

The simple Swift 4 example below should stop when the computer's display goes to sleep.
class Observer {
var asleep = false
func addDNC () {
NSWorkspace.shared.notificationCenter.addObserver(forName: NSWorkspace.screensDidSleepNotification, object: nil, queue: nil, using: notificationRecieved)
}
func notificationRecieved (n: Notification) {
asleep = true
}
}
let observer = Observer ()
observer.addDNC ()
while (!observer.asleep) {}
print ("zzzz")
However, the program gets stuck in the while loop. What am I doing wrong, and what is the proper way to wait for a Notification?
I have tried using a selector (#selector (notificationRecieved), with #objc in the function declaration, of course), to no avail.
Start a template app in Xcode and modify the ViewController.swift to do this:
import Cocoa
class Observer {
var asleep = false
func addDNC () {
NSWorkspace.shared.notificationCenter.addObserver(forName: NSWorkspace.screensDidSleepNotification, object: nil, queue: nil, using: notificationRecieved)
}
func notificationRecieved (n: Notification) {
print("got sleep notification!")
asleep = true
}
}
class ViewController: NSViewController {
let observer = Observer ()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
observer.addDNC ()
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
}
The difference between your code and mine is that I'm not doing the wacky sleepy polling thing you're doing (that's going to lead to a spinning pizza cursor), and I'm also setting observer to be a property off the ViewController object, so the observer property sticks around as long as the view controller does.

Swift - How to use a closure to fire a function in a view model?

I am watching the video series
Swift Talk #5
Connecting View Controllers
url: https://talk.objc.io/episodes/S01E05-connecting-view-controllers
In this video series they remove all the prepareForSegue and use an App class to handle the connection between different view controllers.
I want to replicate this, but specifically only in my current view model; but what I don't get is how to connect view controllers through a view model (or even if you're meant to)
In their code, at github: https://github.com/objcio/S01E05-connecting-view-controllers/blob/master/Example/AppDelegate.swift
They use do this within their view controller
var didSelect: (Episode) -> () = { _ in }
This runs;
func showEpisode(episode: Episode) {
let detailVC = storyboard.instantiateViewControllerWithIdentifier("Detail") as! DetailViewController
detailVC.episode = episode
navigationController.pushViewController(detailVC, animated: true)
}
In the same way, I want to use my ViewController to use my ViewModel for a menu button press (relying on tag).
My code follows;
struct MainMenuViewModel {
enum MainMenuTag: Int {
case newGameTag = 0
}
func menuButtonPressed(tag: Int) {
guard let tagSelected = MainMenuTag.init(rawValue: tag) else {
return
}
switch tagSelected {
case .newGameTag:
print ("Pressed new game btn")
break
}
}
func menuBtnDidPress(tag: Int) {
print ("You pressed: \(tag)")
// Do a switch here
// Go to the next view controller? Should the view model even know about navigation controllers, pushing, etc?
}
}
class MainMenuViewController: UIViewController {
#IBOutlet var mainMenuBtnOutletCollection: [UIButton]!
var didSelect: (Int) -> () = { _ in }
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func mainMenuBtnPressed(_ sender: UIButton) {
let tag = (sender).tag
self.didSelect(tag)
}
}
What I don't understand is how do I connect the command
self.didSelect(tag)
to the function
func menuButtonPressed(tag: Int)
within my ViewModel
As I understand it, according to the swift talk video is that the idea is that the view controller are "plain" and that the view model handles all the major stuff, like menu button presses and then moving to different view controllers as necessary.
How do I connect the didSelect item to my viewModel function?
Thank you.
You should set didSelect property for your controller like here:
func showEpisode(episode: Episode) {
let detailVC = storyboard.instantiateViewControllerWithIdentifier("Detail") as! DetailViewController
detailVC.episode = episode
detailVC.didSelect = { episode in
// do whatever you need
// for example dismiss detailVC
self.navigationController.popViewController(animated: true)
// or call the model methods
self.model.menuButtonPressed(episode)
}
navigationController.pushViewController(detailVC, animated: true)
}

How to load tab bar controller from login view - Swift

So I made a view controller for login user with Parse. when the user login by entering the log in button, tab bar view controller load. The issue is if the user open the app and he already login, I don't want him to go without entering his login. I want the signing view controller send him to tab bar view controller.
The initial view controller is Tab bar view controller, I tried many ways to deal with this problem, but nothing seems good.
waiting for all you thoughts.
There are probably a lot of different ways you can achieve this, but one way I've dealt with this type of situation in the past is to create a custom container ViewController. This VC doesn't have a UIView of its own, but instead presents other ViewControllers as its "Views".
Here's a link to some Apple documentation that describes creating a controller of this type.
One of the benefits of this architecture is that I always have a VC in-scope that can take action to reroute my user based on events related to their account status (not logged in, offline limit reached, account suspended, first-time user, logout, etc.).
EDIT: Example Container Controller
Here's an example of how to implement a custom container controller. I'm sure there are some better ways to do a few of the features shown here, but hopefully this gives you a good start.
import UIKit
class ApplicationContainerController: UIViewController {
//MARK: - View Controller Routing Properties
private var _currentClientView:UIView? = nil
private var _currentViewController: UIViewController? = nil
//MARK: - Initialization
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
//MARK: - UIViewController Members
override func viewDidLoad() {
super.viewDidLoad()
}
override var shouldAutomaticallyForwardAppearanceMethods : Bool {
return true
}
override func viewWillAppear(_ animated: Bool) {
//Get the user and route to the appropriate VC
let yourUserObject: AnyObject? = YourDataSource.TryToGetAUser()
DispatchQueue.main.async {
self.routeUser(yourUserObject)
}
}
//MARK: - Your Custom Routing Logic
func routeUser(_ yourUserObject: AnyObject?) {
//make sure we have an existing user, or else we send them to login
guard let user = yourUserObject
else {
self.displayContentController(YourLoginViewController())
return
}
var destinationViewController:UIViewController
//please use an enum or something (instead of strings) in your code
switch user.signInStatus {
case "loginActive":
let mainMenuViewController = YourMainMenuViewController()
mainMenuViewController.user = user
destinationViewController = mainMenuViewController
case "firstLogin":
let firstLoginViewController = YourFirstLoginViewController()
firstLoginViewController.user = user
destinationViewController = firstLoginViewController
case "giveUsMoney":
let weWantMoneyViewController = YourOtherViewController()
weWantMoneyViewController.user = user
destinationViewController = weWantMoneyViewController
default:
//loginFailed or some other status we don't know how to handle
destinationViewController = YourLoginViewController()
}
if let activeViewController = self._currentViewController,
type(of: activeViewController) !== type(of: destinationViewController) {
//we have an active viewController that is not the destination, cycle
self.cycleFromCurrentViewControllerToViewController(destinationViewController)
} else {
//no active viewControllers
self.displayContentController(destinationViewController)
}
}
//MARK: - Custom Content Controller Routing Methods
private func frameForContentController() -> CGRect {
return self.view.frame
}
private func newViewStartFrame() -> CGRect {
return CGRect(x: self.view.frame.origin.x,
y: self.view.frame.origin.y + self.view.frame.size.width,
width: self.view.frame.size.width,
height: self.view.frame.size.height)
}
private func oldViewEndFrame() -> CGRect {
return CGRect(x: self.view.frame.origin.x,
y: self.view.frame.origin.y - self.view.frame.size.width,
width: self.view.frame.size.width,
height: self.view.frame.size.height)
}
/**
Transitions viewControllers, adds-to/removes-from context, and animates views on/off screen.
*/
private func cycleFromCurrentViewControllerToViewController(_ newViewController: UIViewController) {
if let currentViewController = self._currentViewController {
self.cycleFromViewController(currentViewController, toViewController: newViewController)
}
}
private func cycleFromViewController(_ oldViewController:UIViewController, toViewController newViewController:UIViewController) {
let endFrame = self.oldViewEndFrame()
oldViewController.willMove(toParentViewController: nil)
self.addChildViewController(newViewController)
newViewController.view.frame = self.newViewStartFrame()
self.transition(from: oldViewController, to: newViewController,
duration: 0.5,
options: [],
animations: { () -> Void in
newViewController.view.frame = oldViewController.view.frame
oldViewController.view.frame = endFrame
}) { (finished:Bool) -> Void in
self.hideContentController(oldViewController)
self.displayContentController(newViewController)
}
}
/**
Adds a view controller to the hierarchy and displays its view
*/
private func displayContentController(_ contentController: UIViewController) {
self.addChildViewController(contentController)
contentController.view.frame = self.frameForContentController()
self._currentClientView = contentController.view
self.view.addSubview(self._currentClientView!)
self._currentViewController = contentController
contentController.didMove(toParentViewController: self)
}
/**
Removes a previously added view controller from the hierarchy
*/
private func hideContentController(_ contentController: UIViewController) {
contentController.willMove(toParentViewController: nil)
if (self._currentViewController == contentController) {
self._currentViewController = nil
}
contentController.view.removeFromSuperview()
contentController.removeFromParentViewController()
}
}