I'm currently experimenting with MapKit and SpriteKit, however I have run into a problem.
When I try to convert map coordinates to a point on my screen (in my GameScene), I get an error: Unexpectedly found nil while unwrapping an optional value
The following code produces the error and is in a function that (for testing purposes) runs when the screen is tapped.
monster.position = (mapView?.convert(spawnLocation, toPointTo: view))!
monster is an SKSpriteNode declared at the top of my GameScene. spawnLocation is just a set of coordinates (I checked and they should be visible on the screen). mapView is declared at the top of my GameScene like so: var mapView = GameViewController().map (I believe my problem is here)
Checking if mapView contains a value or not results in nothing being printed to the console:
if (mapView != nil) {
print("not nil")
monster.position = (mapView?.convert(spawnLocation, toPointTo: view))!
}
My map shows up when I run the app, however nothing happens when the above code is executed. I think my problem is with the way I am currently accessing the map from the GameViewController, but I'm not sure how to fix it.
To clarify: my map is declared in my GameViewController and I need to access it from my GameScene so that I can convert the coordinates to a point in the view.
NOTE: I may just be using the convert function wrong.
GameViewController:
#IBOutlet var parentView: UIView!
#IBOutlet var map: MKMapView!
#IBOutlet var skview: SKView!
let locationManager = CLLocationManager()
override func viewDidLoad() {
super.viewDidLoad()
map.delegate = self
map.showsScale = false
map.showsPointsOfInterest = false
map.showsUserLocation = true
map.showsBuildings = true
map.isZoomEnabled = false
map.isScrollEnabled = false
map.isPitchEnabled = false
map.isRotateEnabled = false
locationManager.requestWhenInUseAuthorization()
if (CLLocationManager.locationServicesEnabled()) {
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.startUpdatingLocation()
}
let region = MKCoordinateRegionMakeWithDistance((locationManager.location?.coordinate)!, 400, 400)
map.setRegion(region, animated: false)
parentView.addSubview(map)
parentView.addSubview(skview)
GIDSignIn.sharedInstance().uiDelegate = self
}
So after continuing to work on my issue, I took another look at this question (I had already tried the solution in the answer - it didn't work).
I noticed that my problem might be that the scene in which I was trying to access the MKMapView/GameViewController from wasn't the first scene.
When I added the following to my code, it printed nil:
print("view controller", self.gameViewController)
I then looked at the code responsible for transitioning from the first scene to the one I was having issues with, and I added a line:
let signedInTransition = SKTransition.fade(withDuration: 1)
let nextScene = GameScene(size: scene!.size)
nextScene.scaleMode = .aspectFill
nextScene.gameViewController = self.gameViewController /* ADDED THIS LINE */
scene?.view?.presentScene(nextScene, transition: signedInTransition)
Turns out my problem was that my scene didn't actually have a GameViewController and therefore
let gameViewController:GameViewController!
never actually contained a value.
Related
So I writing a program in swift, and am having trouble understanding the proper communication between GameScene and GameViewController:
Basically I have 'savefiles' which are plists, when loaded using a function "self.loadFromPlist(fileName: )" are loaded by clearing the previous array of nodes, and drawings a set of SKShapeNodes based on attributes from the dictionary elements of the loaded Plist
I needed a file browser, which I've implemented from a Cocoapod (FileBrowser), that must be called in the "GameViewController"
My Problem is this:
When calling my 'loadFromPlist' function from inside 'viewDidLoad' like this (to initialize the project) - the function works and the gamescene is drawn:
override func viewDidLoad() {
super.viewDidLoad()
if let view = self.view as! SKView? {
// Load the SKScene from 'GameScene.sks'
if let scene = SKScene(fileNamed: "GameScene") {
// Set the scale mode to scale to fit the window
scene.scaleMode = .aspectFill
// Present the scene
view.presentScene(scene)
let gameScene = scene as! GameScene
gameScene.loadFromPlist(scoreFileName: gameScene.defaultScore)
gameScene.gameViewControllerDelegate = self
}
view.ignoresSiblingOrder = true
view.showsFPS = true
view.showsNodeCount = true
}
}
When I try to do same thing in my own function in the GameViewController (called view a GameviewDelete in GamScene), the code executes the line and I can follow it with the debugger into the 'loadFromPlist' function in the gamescene, it executes the for loop drawing shapes etc, but it just does nothing.
func callloadplist() {
var loadedfilename2:String!
let file = FileBrowser()
present(file, animated: true, completion: nil)
if let scene = GameScene(size: view.frame.size) as? GameScene {
var strippedfilename: String?
file.didSelectFile = { (file: FBFile) -> Void in
let loadedfilename = file.displayName
scene.filebrowserselectedfilename = loadedfilename
if let view = self.view as! SKView? {
// Load the SKScene from 'GameScene.sks'
if let myskscene = SKScene(fileNamed: "GameScene") {
// Set the scale mode to scale to fit the window
let gameScene = myskscene as! GameScene
let strippedfilename = String(loadedfilename.dropLast(6))
print(strippedfilename)
gameScene.loadFromPlist(scoreFileName: "output")
}
}
}
}
}
So the function call working inside of 'viewDidLoad()' and also as 'self.loadFromPlist(fileName: )' in GameScene.swift, but cannot be called from a custom method/ function I've made in GameViewControllers -- Any Insights?
Thanks
So file.didSelectFile is setting up a closure to respond to the event of the file being selected. This was then running the function called on a different instance of a 'GameScene' Object.
I was able to refine my question and answer it here:
Swift - SKSprite Kit - Change Gamescene Variables from FileBrowser Closure
In Swift, it appears we infer types within the class however outside of functions. I understand that if a variable is only declared within a function then it will only live within that given scope. Isn't it best practice to instantiate objects outside of functions so that we can reference the same object as we program a viewController while also avoiding the possibility of crashes? And if not, then what is the purpose of inferring variables at the top of viewControllers and then instantiating the object within a function?
Here is my example code I'm following from a tutorial. Notice how mapView is inferred at the top of the viewController but instantiated in the loadView method. Wouldn't this make the mapView object only accessible to the loadView function but not to other methods:
import Foundation
import UIKit
import MapKit
import CoreLocation
class MapViewController: UIViewController, MKMapViewDelegate, CLLocationManagerDelegate {
var mapView: MKMapView!
var problemChild: Int!
override func viewWillDisappear(_ animated: Bool) {
print("the view disappeared")
}
override func loadView() {
mapView = MKMapView()
view = mapView
mapView.delegate = self
mapView.isPitchEnabled = true
// let atlLongLat = MKCoordinateRegion.init(center: CLLocationCoordinate2D.init(latitude: CLLocationDegrees.init(33.7490), longitude: CLLocationDegrees.init(84.3880)), span: MKCoordinateSpan.init(latitudeDelta: 33.7490, longitudeDelta: 84.3880))
//mapView.setRegion(atlLongLat, animated: true)
mapView.showsPointsOfInterest = true
mapView.showsBuildings = true
mapView.showsCompass = true
mapView.showsTraffic = true
let locationManager = CLLocationManager()
locationManager.delegate = self
let locationAuthStatus = CLLocationManager.authorizationStatus()
if locationAuthStatus == .notDetermined {
locationManager.requestWhenInUseAuthorization()
}
mapView.showsUserLocation = true
let segmentedControl = UISegmentedControl.init(items: ["Standard", "Hybrid", "Satellite"])
segmentedControl.selectedSegmentIndex = 0
segmentedControl.translatesAutoresizingMaskIntoConstraints = false
segmentedControl.backgroundColor = UIColor.yellow
view.addSubview(segmentedControl)
let zoomButtonFrame = CGRect.init(x: 0, y: 0, width: view.bounds.width, height: 400)
let zoomButton = UIButton.init(frame: zoomButtonFrame)
zoomButton.backgroundColor = UIColor.green
zoomButton.setTitle("Where Am I?", for: .normal)
zoomButton.setTitleColor(UIColor.black, for: .normal)
zoomButton.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(zoomButton)
let guide = view.safeAreaLayoutGuide
let topConstraint = segmentedControl.topAnchor.constraint(equalTo: guide.topAnchor, constant: 8)
let zoomButtonTopConstraint = zoomButton.topAnchor.constraint(equalTo: segmentedControl.bottomAnchor, constant: 559)
let margins = view.layoutMarginsGuide
let zoomButtonLeadingConstraint = zoomButton.leadingAnchor.constraint(equalTo: margins.leadingAnchor)
let leadingConstraint = segmentedControl.leadingAnchor.constraint(equalTo: margins.leadingAnchor)
let trailingConstraint = segmentedControl.trailingAnchor.constraint(equalTo: margins.trailingAnchor)
let zoomButtonTrailingConstraint = zoomButton.trailingAnchor.constraint(equalTo: margins.trailingAnchor)
topConstraint.isActive = true
leadingConstraint.isActive = true
trailingConstraint.isActive = true
zoomButtonTopConstraint.isActive = true
zoomButtonLeadingConstraint.isActive = true
zoomButtonTrailingConstraint.isActive = true
segmentedControl.addTarget(self, action:#selector(mapTypeChanged(segControl:)), for: .valueChanged)
zoomButton.addTarget(self, action: #selector(zoomButtonTapped(zoomButt:)), for: .touchUpInside)
}
#objc func mapTypeChanged(segControl: UISegmentedControl) {
switch segControl.selectedSegmentIndex {
case 0:
mapView.mapType = .standard
case 1:
mapView.mapType = .mutedStandard
case 2:
mapView.mapType = .satelliteFlyover
default:
break
}
}
#objc func zoomButtonTapped(zoomButt: UIButton){
let b: Int = problemChild
print(b)
for _ in 1...5 {
print("Pinging Your Location...")
if zoomButt.backgroundColor == UIColor.green{
print("this button's background color is green man.")
}
}
}
func mapViewWillStartLocatingUser(_ mapView: MKMapView) {
//adding this here to get used to the idea of protocols
}
}
Thank you in advance and I apologize for sounding like a noob but I'd really like to understand.
The scope of a variable is set by its definition, not its assignment. mapView is a property of MapViewController. Therefore it is accessible everywhere in MapViewController. That's unrelated to when it is assigned.
View controllers are a bit unusual because they are often initialized from storyboards, but some pieces cannot be initialized until viewDidLoad (because they reference pieces from the storyboard). That said, this is not the best code. It would have been better written this way:
class MapViewController: UIViewController, MKMapViewDelegate, CLLocationManagerDelegate {
let mapView = MKMapView()
...
And the line mapView = MKMapView() should be removed from loadView. The way it's written works, but it's not as clear or as safe as it should be. (loadView is only called one time in a view controller's life cycle. This wasn't always true, but has been true for longer than Swift has been around.)
When I say that this is not "as safe as it should be," I mean that if something were to access mapView between init and loadView (which can happen for various reasons), this would crash. By carefully managing things, you can avoid that, but it's safer just to avoid ! types when you can.
Variables declared outside of functions the way mapView is are instance variables. The scope of the variable is all the code that's available to the instance, so it's acceptable to reference the object from other functions.
By initializing it inside loadView, the object reference is only valid after that assignment executes but that's different from visibility of the variable.
You asked:
Isn't it best practice to instantiate objects outside of functions so that we can reference the same object as we program a viewController ... ?
If you’re asking the question of “don’t we favor properties over local variables”, the answer is “no”. In defensive programming, we favor local variables except in those cases where we actually need view controller level scope, in which case we use a property. Local variables are free of unintended data sharing/mutation. Properties, because they are shared amongst all the methods, entail a (admittedly modest) risk that it might be accidentally changed elsewhere. Now, where you need to reference the object in a variety of methods, such as the case with mapView, then a property is what you need. But if we don’t need to reference it elsewhere, such as the locationManager, then we stick with local variables if we can.
And if not, then what is the purpose of inferring variables at the top of viewControllers and then instantiating the object within a function?
First, we’re not “inferring variables” at the top of the view controller. We’re simply “declaring properties”, declaring that this will accessible throughout the view controller, regardless of where it is ultimately instantiated.
Regarding the practice of declaring a property up front, but only later instantiating the object and setting the property later within a function like viewDidLoad (or in your example, in loadView), this is not an unreasonable practice. It keeps the instantiation and configuration all together in one place.
If this really bothered you, then yes, you could go ahead and instantiate the object up where you declare the property. But if you were doing that, I might move the configuration of that object there, too, using a closure to both instantiate and configure the object:
class MapViewController: UIViewController {
lazy var mapView: MKMapView = {
let mapView = MKMapView()
mapView.delegate = self
mapView.isPitchEnabled = true
mapView.showsPointsOfInterest = true
mapView.showsBuildings = true
mapView.showsCompass = true
mapView.showsTraffic = true
mapView.showsUserLocation = true
return mapView
}()
...
override func loadView() {
view = mapView
if CLLocationManager.authorizationStatus() == .notDetermined {
CLLocationManager().requestWhenInUseAuthorization()
}
...
}
}
The only trick is that because the closure initializing mapView refers to self, we need to instantiate it lazily.
All of that having been said, I’m not concerned as others about the practice of just declaring the property up front, but instantiating/configuring it later in viewDidLoad/loadView. In fact, this is very common. In storyboards, for example, we decouple the process into three phases:
we declare of the property in the view controller and hook up the outlet in Interface Builder;
when the storyboard scene is instantiated, the storyboard instantiates the map view for us; and
we’ll do any additional programmatic configuration of the map view in viewDidLoad.
You said:
Here is my example code I'm following from a tutorial. Notice how mapView is inferred at the top of the view controller but instantiated in the loadView method. Wouldn't this make the mapView object only accessible to the loadView function but not to other methods:
class MapViewController: UIViewController, MKMapViewDelegate, CLLocationManagerDelegate {
var mapView: MKMapView!
...
override func loadView() {
mapView = MKMapView()
view = mapView
...
let locationManager = CLLocationManager()
...
}
}
No, just because we instantiated mapView in loadView does not limit its scope to that method. In fact, because we declared it as a property earlier, the opposite is true. It is accessible throughout the view controller methods. Do not conflate the declaration of a variable/property with its instantiation.
In this example, locationManager is a local variable (because it’s only used locally within this method), but mapView is a property of the view controller class. The mapView is instantiated and configured in loadView, but we make it a property of the view controller because the mapTypeChanged method needs access to it.
By the way, this technique of programmatically setting the root view of the view controller in loadView is a very uncommon practice nowadays. That is only for programmatically create views. Instead, often we use a storyboard (or in rare cases, a NIB/XIB), add the map view there, hook up the #IBOutlet in Interface Builder, and then viewDidLoad (not to be confused with loadView) could reference that outlet property:
class MapViewController: UIViewController, MKMapViewDelegate, CLLocationManagerDelegate {
#IBOutlet var mapView: MKMapView!
...
override func viewDidLoad() {
super.viewDidLoad()
// perhaps reference the map view created in the storyboard to configure it, e.g.
mapView.isPitchEnabled = true
...
let locationManager = CLLocationManager()
...
}
}
For more information, see Displaying and Managing Views with a View Controller. Also see the View Controller Programming Guide.
I have a GMSMapView in my app. The app with an open map screen takes 150 mb in memory.
At some point I add a lot of polygons. After that the application takes 230 mb.
After calling the method [GMSMapView clear] all polygons disappear, but app still takes 230 mb in memory.
Pointers to Polygons are not stored anywhere else.
How to make the map clear the memory an why it does not happen after calling 'clear' method?
Well, this is pain in a**... I not found the official docs.
My solution:
Swift 3.0
1) Do not use Storyboard, create mapView in code:
#IBOutlet weak fileprivate var mapViewContainer: UIView!
weak fileprivate var mapView: GMSMapView? {
didSet {
//you can config and customise your map here
self.configureMapView()
}
}
//If you use clustering
fileprivate var clusterManager: ClusterManager?
fileprivate var clusterRenderer: ClusterRenderer?
2) Create viewController configure function & call it before viewDidLoad:
func configure() {
mapView = GMSMapView()
mapView?.delegate = self
}
3) Layout & add mapView to your UI:
override func viewDidLoad() {
super.viewDidLoad()
if let mapView = mapView {
mapViewContainer.addSubview(mapView)
//Create constraints as you wish
mapView.fillView()
mapViewContainer.layoutSubviews()
}
}
4) Now create function:
func releaseMemory() {
if
let mapView = mapView,
let clusterManager = clusterManager {
clusterManager.clearItems()
mapView.clear()
mapView.delegate = nil
mapView.removeFromSuperview()
}
clusterManager = nil
clusterRenderer = nil
mapView = nil
}
You call it in ViewDidDisappear, or call delegate...
releaseMemory() function clear memory, not so smart, but it works.
You might try storing the polygon objects somewhere and then call polygon.map = nil on all of them, delete the polygon references and then call the map view clear
I'm trying to make a function that I can call on to update my UILabel's scoreLabel.text. However, I get an error whenever I try to change it. What's confusing to me is that I don't receive an error when changing it inside viewDidLoad(). Everywhere else returns the following error:
In the console I also get error:
fatal error: unexpectedly found nil while unwrapping an Optional value.
So what I've been lead to believe is that when calling my function to update the text, the view hasn't loaded the UILabel yet. But I'm certain that this function is called only once the view has loaded.
Things I've checked/tried for:
That my IBOutlet is properly connected
That my function is being called
Using both scoreLabel.text and self.scoreLabel.text
Using a Strong and Weak outlet connection
I am also positive that changeTextLabel is being called after scoreLabel is loaded into memory. But again my error seems to say otherwise.
Here's a complete markup of my code. I've removed some irrelevant details for readability:
import UIKit
import SpriteKit
class GameViewController: UIViewController {
#IBOutlet weak var scoreLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
//This is where all my other code was
print(scoreLabel.text)
scoreLabel.text = "Testing" //This line can be run
}
func changeTextLabel() {
print("changeTextLabel called")
if self.scoreLabel != nil {
self.scoreLabel.text = "yay"
} else {
print("scoreLabel is nil") //This is called every time
}
}
}
Thanks for your time
Edit:GameScene.swift
This is only the part that should be of concern
func projectileDidCollideWithMonster(projectile: SKSpriteNode, monster: SKSpriteNode) {
print("Hit")
let storyBoard = UIStoryboard(name: "Main", bundle: nil)
let object = storyBoard.instantiateViewController(withIdentifier: "GameViewController") as! GameViewController
object.changeTextLabel()
projectile.removeFromParent()
monster.removeFromParent()
}
I dont fully know your project setup but your approach seems complicated, its not what you should do in SpriteKit games. So I dont think its a good idea to tell you how to fix your current problem.
You should create your UI, such as labels, buttons etc using SpriteKit APIs (SKLabelNodes, SKSpriteNodes, SKNodes etc) directly in the relevant SKScene(s).
Your GameViewController should only really handle presenting SKScenes. so there should be next to no code there apart from loading the 1st SKScene.
If you have multiple SKScenes (MenuScene, ShopScene, SettingScene etc) your approach will fall apart because the score label will show in all SKScenes. GameViewController presents all your SKScenes, so whats added to GameViewController shows in all SKscenes. That means you have to remove/add labels and other UI for each scene and it will be chaos.
So to create a score label you should do this directly in the relevant scene. I like to use the lazy var approach to keep the setup code for the label in the same spot.
class GameScene: SKScene {
lazy var scoreLabel: SKLabelNode = {
let label = SKLabelNode(fontNamed: "HelveticaNeue")
label.text = "SomeText"
label.fontSize = 22
label.fontColor = .yellow
label.position = CGPoint(x: self.frame.midX, y: self.frame.midY)
return label
}()
override func didMove(to view: SKView) {
addChild(scoreLabel)
}
}
Than you can update it like this directly in the collision code you have.
scoreLabel.text = "NewText"
Now when you change from 1 SKScene to another SKScene you dont have to worry about removing this label, SpriteKit will do it for you.
You could also use xCodes level editor to add this label visually, similar to storyboards. This brings me to my final suggestion, which is that storyboards are not really used in SpriteKit games. Storyboards are for ViewControllers and in SpriteKit you are working with SKScenes.
Hope this helps
The problem is with this line,
let game = GameViewController()
game.changeTextLabel()
You should create GameViewController object using XIB or Storyboard.
In your case it is just creating object using .swift file, formally class.
You should create your object using View which is having IBOutlet connection with scoreLabel.
Try (If using Storyboard),
let storyboard = UIStoryboard(name: "YourStoryBoard", bundle: nil)
let gameVC = storyboard.instantiateViewController(withIdentifier: "GameViewController") as! UIViewController
or (If using XIB)
var gameVC = GameViewController(nibName: "GameViewController", bundle: nil)
Update you changeTextLabel with following (Ref),
func changeTextLabel() {
print(self.view)
print("changeTextLabel called")
if self.scoreLabel != nil {
self.scoreLabel.text = "yay"
} else {
print("scoreLabel is nil") //This is called every time
}
}
Replace this line
self.scoreLabel.text = "yay"
With
self.scoreLabel?.text = "yay"
Make these changes and try again:
func changeTextLabel() {
print("changeTextLabel called")
if self.scoreLabel != nil {
self.scoreLabel.text = "yay"
}
}
func projectileDidCollideWithMonster(projectile: SKSpriteNode, monster: SKSpriteNode) {
print("Hit")
let game = STORYBOARD.instantiateViewControllerWithIdentifier("STORYBOARD_IDENTIFIER") as! GameViewController
game.changeTextLabel()
projectile.removeFromParent()
monster.removeFromParent()
}
It happens because you are not getting memory of label in any other function.It happens because of so many reasons.
One reason could be #IBOutlet weak var scoreLabel: UILabel!
Try to create a strong variable outlet.
#IBOutlet strong var scoreLabel: UILabel!
Second reason could be that you are calling changeTextLabel before the label gets memory. So call it later.
I'm making an a game in XCode 7 using Swift 2. I have a variable that I want to pass from the start screen (which is an UIViewController) to the game scene (which is an SKScene). I want the player to select a character in a UIView and play with it in the SKScene. I also want the score that's in the SKScene to show in the game-over screen that's an UIView. I've seen tutorials for passing data between two UIViewControllers and between two SKScenes, but none of them work for this case.
How can I pass a variable from an UIViewController to a SKScene (and vice versa)?
I ran over this same problem yesterday.
Swift 5.2, xcode 12 and targeting iOS 14. Searched high and low. Eventually found the userData attribute of the SKScene.
Note: The app is based on SwiftUI and the SKScene is inside a UIViewRepresentable:
struct SpriteKitContainer: UIViewRepresentable {
typealias UIViewType = SKView
var skScene: SKScene!
#Binding var timerData : ModelProgressTimer
Not that I am passing a binding into the View - this contains a number of data items I wanted to make available to the scene.
I placed code in two places to populate skScene.userData:
func makeUIView(context: Context) -> SKView {
self.skScene.scaleMode = .resizeFill
self.skScene.backgroundColor = .green
debugPrint("setting user data")
self.skScene.userData = [:]
self.skScene.userData!["color"] = UIColor(timerData.flatColorTwo!)
self.skScene.userData!["running"] = timerData.isRunning
self.skScene.userData!["percent"] = timerData.percentComplete
let view = SKView(frame: .zero)
view.preferredFramesPerSecond = 60
// view.showsFPS = true
// view.showsNodeCount = true
view.backgroundColor = .clear
view.allowsTransparency = true
return view
}
func updateUIView(_ view: SKView, context: Context) {
self.skScene.userData = [:]
self.skScene.userData!["running"] = timerData.isRunning
self.skScene.userData!["color"] = UIColor(timerData.flatColorTwo!)
self.skScene.userData!["percent"] = timerData.percentComplete
view.presentScene(context.coordinator.scene)
}
Then, inside the skScene object, I am able to retrieve the userData values:
var item: SKShapeNode! = nil
override func sceneDidLoad() {
let scaleUp = SKAction.scale(by: 2.0, duration: 1.0)
let scaleDown = SKAction.scale(by: 0.5, duration: 1.0)
scaleUp.timingMode = .easeInEaseOut
scaleDown.timingMode = .easeInEaseOut
let sequence = SKAction.sequence([scaleUp,scaleDown])
actionRepeat = SKAction.repeatForever(sequence)
item = SKShapeNode(circleOfRadius: radius)
addChild(item)
}
override func didChangeSize(_ oldSize: CGSize) {
item.fillColor = self.userData!["color"] as! UIColor
item.strokeColor = .clear
let meRunning = self.userData!["running"] as! Bool
if meRunning {
item.run(actionRepeat)
} else {
item.removeAllActions()
}
}
This draws a circle that "pulses" if the "running" boolean is set in the userdata (a Bool value).
If you haven't already found the answer, I did find a way to do this. I was doing something very similar.
In my case I have a UIView that gets called as a pause menu from my scene. I have made the class and variable names a little more ambiguous so they can apply to your situation as well.
class MyView: UIView {
var scene: MyScene!
var button: UIButton!
init(frame: CGRect, scene: MyScene){
super.init(frame: frame)
self.scene = scene
//initialize the button
button.addGestureRecognizer(UITapGestureRecognizer(target: self, action: "buttonTapped"))
}
func buttonTapped(){
self.removeFromSuperview() // this removes my menu from the scene
scene.doTheThing() // this calls the method on my scene
}
Here are the relevant parts of my scene.
class MyScene: SKScene {
func doTheThing(){
//this is a function on the scene. You can pass any variable you want through the function.
}
}
In your situation, it sounds like the first screen is a UIView and the second screen is the SKScene.
You may want to make your SKScene first, pause it, and then add the UIView in front of the Scene. Once the character is selected, you can remove the UIView and add your character into the scene.
I hope that this answers your question. If it doesn't let me know.
From the level select scene:
let scene = GameScene(fileNamed:"GameScene")
scene?.userData = [:]
scene?.userData!["level"] = 21
self.view?.presentScene(scene!)
From within the game scene:
let level = self.userData!["level"] as! Int