I have been trying to use UIButtons for a game interface, but I am still trying to figure out how to call methods that are inside a GameScene file through my view controller file. Is there anyway I can give my view controller a reference to the instance of the GameScene that is actually being displayed on the GameScene.sks file?
So far this is what I have tried in the view controller:
// Inside view controller
class GameViewController: UIViewController {
static var gameScene: GameScene = GameScene()
func setGameScene(scene: GameScene) {
GameViewController.gameScene = scene
}
...
This is what I did in the GameScene file.
//Inside GameScene.swift
class GameScene: SKScene {
override func didMove(to view: SKView) {
let vc = GameViewController()
vc.setGameScene(scene: self)
}
...
You want to create a computed property to your view's scene
class GameViewController: UIViewController {
var gameScene : GameScene? { return (view as? SKView).scene as? GameScene}
...
}
Now this needs to be optional, because there may be times where your scene is not GameScene, and this could cause your app to crash.
Related
If i create an instance of mapView in MainView, how can i use that instance in Container View?
class MainView: UIViewController {
var mapView = MapView()
}
class ContainerView: UIViewController {
MainView.mapView.changeCameraPosition()
}
How can i access this mapView instance in container View or is it possible?
The solution i found and also with help from Burnsi and Rob is to pass the instance as an object when adding view controller to container view:
let storyboard = UIStoryboard(name: "Main", bundle: Bundle.main)
let nextPageVC = storyboard.instantiateViewController(withIdentifier:"NextPage") as! NextPage
nextPageVC.mapView = mapView
addChild(nextPageVC)
containerView.addSubview(nextPageVC.view)
nextPageVC.view.frame = containerView.bounds
nextPageVC.didMove(toParent: self)
And i also changed the class type of the Container View and Main View in NextPageVC to PassthruView so that users can interact with the map when Container View Controller is on top:
import Foundation
import UIKit
class PassthruView: UIView {
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
// Get the hit view we would normally get with a standard UIView
let hitView = super.hitTest(point, with: event)
// If the hit view was ourself (meaning no subview was touched),
// return nil instead. Otherwise, return hitView, which must be a subview.
return hitView == self ? nil : hitView
}
}
If Main View has the Container View, then the scenario would be like this if I'm not wrong:
class MainView: UIViewController {
var mapView = MapView()
var containerView = ContainerView()
}
If that's the case, you can make a method inside ContainerView class that takes an object of MapView as a parameter and call that method inside MainView class on containerView (the object of ContainerView that you have in MainView class).
The method could be something like this:
private func changeCameraPosition(mapView: MapView) {
mapView.changeCameraPosition()
}
And call this method on containerView object in Main View class as:
containerView.changeCameraPosition(mapView: mapView)
On many of projects I use SnapKit. And on new project too. On project I have ViewController which connected with SwiftUI view:
class OfficeListViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let vc = UIHostingController(rootView: OfficeListView())
addChild(vc)
view.addSubview(vc.view)
vc.didMove(toParent: self)
vc.view.translatesAutoresizingMaskIntoConstraints = false
// Here I want to set constraints to vc
vc.snp // throws error: Value of type 'UIHostingController<OfficeView>' has no member 'snp'
}
}
struct OfficeListView: View {
var body: some View {
Text("View")
}
}
But it throws error:
Value of type 'UIHostingController' has no member 'snp'
How to correctly use SnapKit with it?
The UIHostingController is just subclass of UIViewController and it renders SwiftUI view inside regular UIView. If you want to set up constrains, then you should use vc.view as we usually do with views.
I am using the MapKit to use CLLocationManagerDelegate to get the user's location. If I were to request for the user's location in ViewController's viewDidLoad() function, then the popup appears, asking the user for the user's input. Note: the two properties required to ask for the location (Location When In Use, and Location Always And When In Use) is added to Info.plist
That is,
import UIKit
import MapKit
class ViewController: UIViewController, CLLocationManagerDelegate {
var locationManager: CLLocationManager?
override func viewDidLoad() {
super.viewDidLoad()
locationManager = CLLocationManager()
self.locationManager?.desiredAccuracy = kCLLocationAccuracyBest
self.locationManager?.requestWhenInUseAuthorization()
self.locationManager?.delegate = self
self.locationManager?.startUpdatingLocation()
}
}
The code above works fine; when the program begins, it show a popup asking the user for their location.
However, If I was to create a new class MapController and put the same code inside that class, and create a new instance of MapController inside viewDidLoad(), then the popup immediately disappears when the program is run.
That is,
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let mapController = MapController(viewController: self)
mapController.initialise()
}
}
import MapKit
class MapController: NSObject, CLLocationManagerDelegate {
private let viewController: UIViewController
private var locationManager: CLLocationManager
required init(viewController: UIViewController) {
self.viewController = viewController
locationManager = CLLocationManager()
}
func initialise() {
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest
self.locationManager.requestWhenInUseAuthorization()
self.locationManager.delegate = self
self.locationManager.startUpdatingLocation()
}
}
When the code above is run, the popup asking for the user's location immediately disappears.
My question being: why does the popup stay when the locationManager code is in the viewDidLoad(), but when the code is separated into another class, and called into viewDidLoad(), it immediately disappears. Why does this happen?
How can I separate the locationManager code into another class without the popup immediately disappearing?
It's a memory management issue. In ViewController, you create a local variable named mapController in viewDidLoad. At the end of viewDidLoad, that MapController instance goes out of scope and gets deallocated.
Instead of using a local variable in viewDidLoad, create a property.
class ViewController: UIViewController {
var mapController: MapController!
override func viewDidLoad() {
super.viewDidLoad()
mapController = MapController(viewController: self)
mapController.initialise()
}
}
But this now creates a reference cycle since MapController is keeping a strong reference to the view controller.
So you also need to change the viewController property of MapController to be weak.
I have tabbarController where i put parent viewController with container view inside.
public override func viewDidLoad() {
viewControllers = [
ParentViewController()
]
}
On init i'm initializing 2 child view controllers and adding 1st controller (that does't contain MapView) as child viewController.
At some point of time i need to switch between child controllers, and in that point app crashes
public class ParentViewController: UIViewController {
#IBOutlet weak var containerView: UIView!
let firstChildController: ViewControllerWithoutMapView
let secondChildController: ViewControllerWithMapView
init() {
firstChildController = ViewControllerWithoutMapView()
secondChildController = ViewControllerWithMapView()
super.init(nibName: "ParentViewController", bundle: nil)
}
public override func viewDidLoad() {
firstChildController.view.frame = containerView.bounds
addChildViewController(firstChildController)
firstChildController.willMoveToParentViewController(nil)
containerView.addSubview(firstChildController.view)
firstChildController.didMoveToParentViewController(self)
}
func switchChildControllers() {
secondChildController.view.frame = containerView.bounds <<<<< crash here
.....
}
}
I know about crashes that appears if you're not importing MapKit, i tried to import it everywhere - no luck.
What is the correct way to switch child viewControllers with MapView inside one of it?
I have a calculator class, a first ViewController to insert the values and a second ViewController to show the result of the calculation. Unfortunately I get a error called "Can't unwrap Optional.None" if I click the button. I know it's something wrong with the syntax, but I don't know how to improve it.
The button in the first Viewcontroller is set to "Segue: Show (e.g. Push)" in the storyboard to switch to the secondViewController if he gets tapped.
the calculator class is something like:
class Calculator: NSObject {
func calculate (a:Int,b:Int) -> (Int) {
var result = a * b
return (result)
}
}
The Viewcontroller calls the function, inserts a/b and wants to change the label which is located in the secondviewcontroller:
class ViewController: UIViewController {
#IBAction func myButtonPressed(sender : AnyObject) {
showResult()
}
var numberOne = 4
var numberTwo = 7
var myCalc = Calculator()
func showResult () {
var myResult = myCalc.calculate(numberOne, b: numberTwo)
println("myResult is \(String(myResult))")
var myVC = secondViewController()
myVC.setResultLabel(myResult)
}
And here is the code of the secondViewController
class secondViewController: UIViewController {
#IBOutlet var myResultLabel : UILabel = nil
func setResultLabel (resultValue:Int) {
myResultLabel.text = String(resultValue)
}
init(coder aDecoder: NSCoder!)
{
super.init(coder: aDecoder)
}
In Swift, everything is public by default.
Define your variables outside the classes:
import UIKit
var placesArray: NSMutableArray!
class FirstViewController: UIViewController {
//
..
//
}
and then access it
import UIKit
class TableViewController: UITableViewController {
//
placesArray = [1, 2, 3]
//
}
The problem here is that the FirstViewController has no reference to the instance of SecondViewController. Because of this, this line:
secondViewController.setResultLabel(myResult)
does nothing (except probably causing the Can't unwrap Optional.None error). There are a few ways to solve this problem. If you are using storyboard segues you can use the -prepareForSegue method of UIViewController. Here is an example:
In FirstViewController:
override func prepareForSegue(segue: UIStoryboardSegue!,sender: AnyObject!){
//make sure that the segue is going to secondViewController
if segue.destinationViewController is secondViewController{
// now set a var that points to that new viewcontroller so you can call the method correctly
let nextController = (segue.destinationViewController as! secondViewController)
nextController.setResultLabel((String(myResult)))
}
}
Note: this code will not run as is because the function has no access to the result variable. you'll have to figure that out yourself :)
I think the issue here is, you are trying to set the UI component (here, its the label : myResultLabel)
When segue is fired from first view controller, the second view has not yet been initialized. In other words, the UI object "myResultLabel" is still nil.
To solve this, you will need to create a local string variable in second controller. Now, set that string to what you are trying to display, and finally, set the actual label in "viewDidLoad()" of the second controller.
Best Regards,
Gopal Nair.