How do I delegate from one Scene another in SpriteKit? - sprite-kit

I need some help setting up delegation in SpriteKit. the function in second scene is not called.
But I just cannot figure out what I am doing wrong.
Many thanks in advance
protocol receivingStringDelegate:class {
func didReceiveString(message:String)
}
class GameScene: SKScene {
weak var gamescene_transmissiondelegate: receivingStringDelegate?
private func sendString(message:String){
gamescene_transmissiondelegate?. didReceiveString(message)
}
}
class SecondScene: SKScene, receivingStringDelegate {
// here I get stuck, what to do?
var gameScene : GameScene = GameScene()
override func didMove(to view: SKView) {
gameScene.gamescene_transmissiondelegate = self
}
func didReceiveString(message:String) {
print ("hi there", message)
}
}

I woudld do it with scene properties vs a global class
class ConnectionScene: SKScene {
private var player1pos: CGPoint = CGPoint.zero
private var player2pos: CGPoint = CGPoint.zero
///some code in this scene sets player positions
func startMultiplayerGame() {
//this assumes your scene is setup in the editor not programmatically
if let gameScene = GameScene(fileNamed: "GameScene") {
gameScene.scaleMode = .aspectFill
gameScene.player1pos = player1pos
gameScene.player2pos = player2pos
self.view?.presentScene(multiplayerScene, transition: SKTransition.reveal(with: .down, duration: 1.0))
}
}
}
class GameScene: SKScene {
var player1pos: CGPoint = CGPoint.zero
var player2pos: CGPoint = CGPoint.zero
override func sceneDidLoad() {
//sceneDidLoad happens first
super.sceneDidLoad()
//do setup that doesn't require passed in variables in here
//this isn't set from connectScene yet so won't have the values
print("player1pos \(player1pos)")
print("player2pos \(player2pos)")
}
override func didMove(to view: SKView) {
//didMove happens last and already has the variables assigned
//now do setup that requires these variables
//this now has value from connectScene
print("player1pos \(player1pos)")
print("player2pos \(player2pos)")
}
}

Related

How do I use the same instance in a delegate protocol pattern?

I recently asked a question on how to use a protocol delegate pattern here: How do you use the Delegate Protocol Pattern with SpriteKit?
And while I got an answer that got me a long way, I don't get it working all the way, and I think it has to do with the fact that I don't (and can't I think) use the same instance when changing SKViews. Let me explain.
I have one UIViewController and two SKScenes. The UIViewController present the two scenes by rotating the device; landscape load SKScene 1 and portrait load SKScene 2 like this:
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
print("TRIGGERED")
if UIDevice.current.orientation.isLandscape {
print("Landscape")
presentView(name: "GameScene")
} else {
print("Portrait")
presentView(name: "GameScene2")
}
super.viewWillTransition(to: size, with: coordinator)
}
func presentView(name: String) {
if let view = self.view as! SKView? {
if let scene = SKScene(fileNamed: name) {
scene.scaleMode = .aspectFill
view.presentScene(scene)
}
view.ignoresSiblingOrder = true
view.showsFPS = true
view.showsNodeCount = true
}
}
and in SKScene 1:
protocol MyProtocol {
func myProtocolFunc(someString: String)
}
class GameScene: SKScene{
var myDelegate: MyProtocol!
and in SKScene 2:
class GameScene2: MyProtocol {
private var label: SKLabelNode?
func myProtocolFunc(someString: String) {
label = SKLabelNode(fontNamed: "Chalkduster")
label!.text = someString
label!.position = CGPoint(x: 0, y: 0)
addChild(label!)
}
The SKlabel isn't updated with the delegate value however. An like I said I think this is because I create a new instance of scene when loading SKScene 2. And that instance don't have the delegate. I'm not sure how to work around this though since I need to have a new instance of SKScene to load the other scene.
It might be worth for you to read into the coordinator pattern https://www.hackingwithswift.com/articles/71/how-to-use-the-coordinator-pattern-in-ios-apps

Losing reference to GameViewController from inside SpriteKit when moving between scenes - want to move between SKScene and UITableView

I have a simple (multi scene) spritekit game and would like to implement and display a settings screen using a UITableViewContoller. I am close but keep losing my reference to the games view controller when i change scene, which means i can only segue to the UITableViewController on the first scene when my game starts. I'm hoping someone can help. I setup the UITableView in the storyboard and setup a segue to the tableview from my GameViewController in there.
I have the following set up in my GameViewController file:
override func viewDidLoad() {
super.viewDidLoad()
if let view = self.view as! SKView? {
// Load the SKScene
if let scene = SKScene(fileNamed: "MenuScene") {
// Set the scale mode to scale to fit the window
scene.scaleMode = .aspectFill
//I'VE ADDED THIS
if let gameScene = scene as? MenuScene {
gameScene.viewController = self
}
// Present the scene
view.presentScene(scene)
}
view.ignoresSiblingOrder = true
}
}
func showSettings() {
performSegue(withIdentifier: "openSettings", sender: self)
}
which gives me a reference to the GameViewController from my SKScene. I then load the UITableViewController from my SKScene in spritekit when a user touches an SKLabelNode. A simpler version of my MenuScene code looks something like this:
class MenuScene: SKScene {
var viewController: GameViewController?
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?)
{
let touch:UITouch = touches.first!
let positionInScene = touch.location(in: self)
let touchedNode = self.atPoint(positionInScene)
if let name = touchedNode.name
{
let transition:SKTransition = SKTransition.fade(withDuration: 0.75)
var scene:SKScene = SKScene()
if (name == "settingsBTN"){
viewController?.showSettings()
}
else if (name == "randomBTN"){
scene = StarScene(size: self.size)
scene.backgroundColor = SKColor.black
self.view?.presentScene(scene, transition: transition)
}
}
}
}//end of MenuScene
The table view is presented fine when i tap the settingsBTN when first starting the game. however, if i visit another scene in my game (e.g. by pressing randomBTN) and return back to the menu scene i can no longer segue to the Settings view as i have lost the reference to the viewcontroller (it shows as nil) so i can't call the showSettings method in the GameViewController that performs the segue (presumably because i set up the reference to the view controller in the view controller itself).
I can't figure out how to maintain the reference to my GameViewController so i can present the settings screen at any point. Please could someone help.
There are two simple methods to call a GameViewController function from other classes: through a delegate or by making the class instance static.
// DELEGATE
//
// GameViewController.swift
protocol GameView_Delegate
{
func show_ui()
}
class GameViewController: UIViewController, GameView_Delegate
{
override func viewDidLoad()
{
// ...
if let view = self.view as! SKView?
{
if let scene = SKScene(fileNamed: "MenuScene")
{
scene.scaleMode = .aspectFill
// pass GameViewController reference
scene.gv_delegate = self
}
view.presentScene(scene)
}
// ...
}
func show_ui()
{
// show ui
// ...
}
}
// MenuScene.swift
class MenuScene: SKScene
{
var gv_delegate : GameView_Delegate!
func settings()
{
// call function show_ui from GameViewController
gv_delegate.show_ui()
}
func game_scene()
{
let transition = SKTransition.fade(with: UIColor.white, duration: 1.0)
let next_scene = GameScene()
next_scene.anchorPoint = CGPoint(x: 0.5, y: 0.5)
next_scene.scaleMode = self.scaleMode
next_scene.size = self.size
// before moving to next scene, we must pass the delegate, so we
// don't lose the connection with the GameViewController
next_scene.gv_delegate = gv_delegate
self.view?.presentScene(next_scene, transition: transition)
}
}
// GameScene.swift
class GameScene: SKScene
{
var gv_delegate : GameView_Delegate!
func settings()
{
// call function show_ui from GameViewController
gv_delegate.show_ui()
}
func main_scene()
{
let transition = SKTransition.fade(with: UIColor.white, duration: 1.0)
let next_scene = MainScene()
next_scene.anchorPoint = CGPoint(x: 0.5, y: 0.5)
next_scene.scaleMode = self.scaleMode
next_scene.size = self.size
// before moving to next scene, we must pass the delegate, so we
// don't lose the connection with the GameViewController
next_scene.gv_delegate = gv_delegate
self.view?.presentScene(next_scene, transition: transition)
}
}
When you enter a new scene, for example GameScene, the old scene MainScene in our case is deallocated (all variables emptied, all references destroyed) and a new GameScene instance is created and displayed on screen. If afterwards you want to exit that scene and enter MainScene, a new instance of that class will be created with the GameViewController reference empty. We should reference it again or pass the reference before exiting the scene (as in my example).
// STATIC
//
// GameViewController.swift
class GameViewController: UIViewController
{
static var shared : GameViewController!
override func viewDidLoad()
{
super.viewDidLoad()
GameViewController.shared = self
// ...
}
func show_ui()
{
// show ui
// ...
}
}
// MenuScene.swift
class MenuScene: SKScene
{
func settings()
{
// call function show_ui from GameViewController
GameViewController.shared.show_ui()
}
}
Read pros/cons of each method and chose the one that suits your app the best. Also, avoid using UIKit inside SpriteKit.

SKEmitterNode disappears when added to a new SKScene

I have a SKEmitterNode that's running on a SKScene and I want to move it to the next SKScene without interrupting the particles.
Normally I'd do it like the following:
let scene: SKScene = GameScene(size: self.size)
let transition = SKTransition.crossFade(withDuration: 0.5)
self.view!.presentScene(scene, transition: transition)
And then on the next SKScene:
override func didMove(to view: SKView) {
particleEmitter.removeFromParent()
addChild(particleEmitter)
}
This works perfectly fine however in this situation I don't want to use a transition when moving to the next SKScene. I've tried it without a transition like:
let scene: SKScene = GameScene(size: self.size)
self.view!.presentScene(scene)
And the SKEmitterNode disappears as soon as the new SKScene is presented even though I've removed it from the last SKScene and added it as child to the new one.
My question is why is the SKEmitterNode dissapearing and how can I get it to work without using a transition between the SKScene's. Any help would be much appreciated, thanks.
Note: Using a SKTransition with a duration of 0 also works but this causes a noticeable 'flash' during the transition.
There shouldn't be any flashes and such. If I understand you correctly, what you need to do is to create an emitter in a global scope. Before presenting the next scene, you should remove the emitter from its parent. When you are in the next scene, you add emitter to it. I just tried and it works for me without any lag, flashes or something.
Here is the code...There are two scenes...A GameScene:
import SpriteKit
let emitter = SKEmitterNode(fileNamed: "Fireflies")
class GameScene: SKScene {
override func didMove(to view: SKView) {
backgroundColor = .black
if let emitterNode = emitter {
addChild(emitterNode)
}
print("Game Scene")
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let scene = WelcomeScene(fileNamed: "WelcomeScene") {
scene.scaleMode = .aspectFill
emitter?.removeFromParent()
self.view?.presentScene(scene)
}
}
}
and a WelcomeScene:
import SpriteKit
class WelcomeScene:SKScene{
override func didMove(to view: SKView) {
backgroundColor = .black
print("Welcome Scene")
if let emitterNode = emitter {
addChild(emitterNode)
}
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let scene = GameScene(fileNamed: "GameScene") {
scene.scaleMode = .aspectFill
emitter?.removeFromParent()
self.view?.presentScene(scene)
}
}
}
For the emitter I've used standard fireflies template.

Add SpriteNodes and Remove Based on Time or click using SpriteKit

I am new to swift and looking to build my first basic game. The game I have in mind involves sprites generating at random and then disappearing based on time or a click if the click is within the time allocated. So far I have created the basic framework and am still messing around with design. My problem comes in where I can't seem to remove the sprite based on time (its generating fine). Any help is appreciated and thanks in advance😊
Below is the framework I've built up so far.
import SpriteKit
var one = SKSpriteNode()
class GameScene: SKScene {
override func didMoveToView(view: SKView) {
/* Setup your scene here */
let myFunction = SKAction.runBlock({()in self.addOne()})
let wait = SKAction.waitForDuration(5)
let remove = SKAction.runBlock({() in self.removeOne()})
self.runAction(SKAction.sequence([myFunction, wait, remove]))
}
func addOne() {
let oneTexture = SKTexture(imageNamed: "blue button 10.png")
let one = SKSpriteNode(texture: oneTexture)
one.position = CGPoint(x: CGRectGetMidX(self.frame) - 100, y: CGRectGetMidY(self.frame) + 250)
one.zPosition = 1
self.addChild(one)
}
func removeOne() {
one.removeFromParent()
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
/* Called when a touch begins */
}
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
}
}
It doesn't disappear because your create a new SpiteNode, but try to remove the old one, do it like this:
var one : SKSpriteNode! //instead of creating it without data, just define the type(not necessary, but I would do it)
class GameScene: SKScene {
override func didMoveToView(view: SKView) {
/* Setup your scene here */
let myFunction = SKAction.runBlock({()in self.addOne()})
let wait = SKAction.waitForDuration(5)
let remove = SKAction.runBlock({() in self.removeOne()})
self.runAction(SKAction.sequence([myFunction, wait, remove]))
}
func addOne() {
let oneTexture = SKTexture(imageNamed: "blue button 10.png")
one = SKSpriteNode(texture: oneTexture) //removed the let, so you dont create a new "one"
one.position = CGPoint(x: CGRectGetMidX(self.frame) - 100, y: CGRectGetMidY(self.frame) + 250)
one.zPosition = 1
self.addChild(one)
}
func removeOne() {
one.removeFromParent()
}
}

How to create a vertical scrolling menu in spritekit?

I'm looking to create a shop in my game (In SpriteKit) with buttons and images, but I need the items to be scrollable so the player can scroll up and down the shop (Like a UITableView but with multiple SKSpriteNodes and SKLabelNodes in each cell). Any idea how I can do this in SpriteKit?
The second answer as promised, I just figured out the issue.
I recommend to always get the latest version of this code from my gitHub project incase I made changes since this answer, link is at the bottom.
Step 1: Create a new swift file and paste in this code
import SpriteKit
/// Scroll direction
enum ScrollDirection {
case vertical // cases start with small letters as I am following Swift 3 guildlines.
case horizontal
}
class CustomScrollView: UIScrollView {
// MARK: - Static Properties
/// Touches allowed
static var disabledTouches = false
/// Scroll view
private static var scrollView: UIScrollView!
// MARK: - Properties
/// Current scene
private let currentScene: SKScene
/// Moveable node
private let moveableNode: SKNode
/// Scroll direction
private let scrollDirection: ScrollDirection
/// Touched nodes
private var nodesTouched = [AnyObject]()
// MARK: - Init
init(frame: CGRect, scene: SKScene, moveableNode: SKNode) {
self.currentScene = scene
self.moveableNode = moveableNode
self.scrollDirection = scrollDirection
super.init(frame: frame)
CustomScrollView.scrollView = self
self.frame = frame
delegate = self
indicatorStyle = .White
scrollEnabled = true
userInteractionEnabled = true
//canCancelContentTouches = false
//self.minimumZoomScale = 1
//self.maximumZoomScale = 3
if scrollDirection == .horizontal {
let flip = CGAffineTransformMakeScale(-1,-1)
transform = flip
}
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
// MARK: - Touches
extension CustomScrollView {
/// Began
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch in touches {
let location = touch.locationInNode(currentScene)
guard !CustomScrollView.disabledTouches else { return }
/// Call touches began in current scene
currentScene.touchesBegan(touches, withEvent: event)
/// Call touches began in all touched nodes in the current scene
nodesTouched = currentScene.nodesAtPoint(location)
for node in nodesTouched {
node.touchesBegan(touches, withEvent: event)
}
}
}
/// Moved
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch in touches {
let location = touch.locationInNode(currentScene)
guard !CustomScrollView.disabledTouches else { return }
/// Call touches moved in current scene
currentScene.touchesMoved(touches, withEvent: event)
/// Call touches moved in all touched nodes in the current scene
nodesTouched = currentScene.nodesAtPoint(location)
for node in nodesTouched {
node.touchesMoved(touches, withEvent: event)
}
}
}
/// Ended
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch in touches {
let location = touch.locationInNode(currentScene)
guard !CustomScrollView.disabledTouches else { return }
/// Call touches ended in current scene
currentScene.touchesEnded(touches, withEvent: event)
/// Call touches ended in all touched nodes in the current scene
nodesTouched = currentScene.nodesAtPoint(location)
for node in nodesTouched {
node.touchesEnded(touches, withEvent: event)
}
}
}
/// Cancelled
override func touchesCancelled(touches: Set<UITouch>?, withEvent event: UIEvent?) {
for touch in touches! {
let location = touch.locationInNode(currentScene)
guard !CustomScrollView.disabledTouches else { return }
/// Call touches cancelled in current scene
currentScene.touchesCancelled(touches, withEvent: event)
/// Call touches cancelled in all touched nodes in the current scene
nodesTouched = currentScene.nodesAtPoint(location)
for node in nodesTouched {
node.touchesCancelled(touches, withEvent: event)
}
}
}
}
// MARK: - Touch Controls
extension CustomScrollView {
/// Disable
class func disable() {
CustomScrollView.scrollView?.userInteractionEnabled = false
CustomScrollView.disabledTouches = true
}
/// Enable
class func enable() {
CustomScrollView.scrollView?.userInteractionEnabled = true
CustomScrollView.disabledTouches = false
}
}
// MARK: - Delegates
extension CustomScrollView: UIScrollViewDelegate {
func scrollViewDidScroll(scrollView: UIScrollView) {
if scrollDirection == .horizontal {
moveableNode.position.x = scrollView.contentOffset.x
} else {
moveableNode.position.y = scrollView.contentOffset.y
}
}
}
This make a subclass of UIScrollView and sets up the basic properties of it. It than has its own touches method which get passed along to the relevant scene.
Step2: In your relevant scene you want to use it you create a scroll view and moveable node property like so
weak var scrollView: CustomScrollView!
let moveableNode = SKNode()
and add them to the scene in didMoveToView
scrollView = CustomScrollView(frame: CGRect(x: 0, y: 0, width: self.frame.size.width, height: self.frame.size.height), scene: self, moveableNode: moveableNode, scrollDirection: .vertical)
scrollView.contentSize = CGSizeMake(self.frame.size.width, self.frame.size.height * 2)
view?.addSubview(scrollView)
addChild(moveableNode)
What you do here in line 1 is you init the scroll view helper with you scene dimensions. You also pass along the scene for reference and the moveableNode you created at step 2.
Line 2 is where you set up the content size of the scrollView, in this case its twice as long as the screen height.
Step3: - Add you labels or nodes etc and position them.
label1.position.y = CGRectGetMidY(self.frame) - self.frame.size.height
moveableNode.addChild(label1)
in this example the label would be on the 2nd page in the scrollView. This is where you have to play around with you labels and positioning.
I recommend that if you have a lot pages in the scroll view and a lot of labels to do the following. Create a SKSpriteNode for each page in the scroll view and make each of them the size of the screen. Call them like page1Node, page2Node etc. You than add all the labels you want for example on the second page to page2Node. The benefit here is that you basically can position all your stuff as usual within page2Node and than just position page2Node in the scrollView.
You are also in luck because using the scrollView vertically (which u said you want) you dont need to do any flipping and reverse positioning.
I made some class func so if you need to disable your scrollView incase you overlay another menu ontop of the scrollView.
CustomScrollView.enable()
CustomScrollView.disable()
And finally do not forget to remove the scroll view from your scene before transitioning to a new one. One of the pains when dealing with UIKit in spritekit.
scrollView?.removeFromSuperView()
For horizontal scrolling simply change the scroll direction on the init method to .horizontal (step 2).
And now the biggest pain is that everything is in reverse when positioning stuff. So the scroll view goes from right to left. So you need to use the scrollView "contentOffset" method to reposition it and basically place all your labels in reverse order from right to left. Using SkNodes again makes this much easier once you understand whats happening.
Hope this helps and sorry for the massive post but as I said it is a bit of a pain in spritekit. Let me know how it goes and if I missed anything.
Project is on gitHub
https://github.com/crashoverride777/SwiftySKScrollView
You have 2 options
1) Use a UIScrollView
Down the road this is the better solution as you get things such as momentum scrolling, paging, bounce effects etc for free. However you have to either use a lot of UIKit stuff or do some sub classing to make it work with SKSpritenodes or labels.
Check my project on gitHub for an example
https://github.com/crashoverride777/SwiftySKScrollView
2) Use SpriteKit
Declare 3 class variables outside of functions(under where it says 'classname': SKScene):
var startY: CGFloat = 0.0
var lastY: CGFloat = 0.0
var moveableArea = SKNode()
Set up your didMoveToView, add the SKNode to the scene and add 2 labels, one for the top and one for the bottom to see it working!
override func didMoveToView(view: SKView) {
// set position & add scrolling/moveable node to screen
moveableArea.position = CGPointMake(0, 0)
self.addChild(moveableArea)
// Create Label node and add it to the scrolling node to see it
let top = SKLabelNode(fontNamed: "Avenir-Black")
top.text = "Top"
top.fontSize = CGRectGetMaxY(self.frame)/15
top.position = CGPoint(x:CGRectGetMidX(self.frame), y:CGRectGetMaxY(self.frame)*0.9)
moveableArea.addChild(top)
let bottom = SKLabelNode(fontNamed: "Avenir-Black")
bottom.text = "Bottom"
bottom.fontSize = CGRectGetMaxY(self.frame)/20
bottom.position = CGPoint(x:CGRectGetMidX(self.frame), y:0-CGRectGetMaxY(self.frame)*0.5)
moveableArea.addChild(bottom)
}
Then set up your touches began to store position of your first touch:
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
// store the starting position of the touch
let touch: AnyObject? = touches.anyObject();
let location = touch?.locationInNode(self)
startY = location!.y
lastY = location!.y
}
Then set up touches moved with the following code to scroll the node by to the limits set, at the speed set:
override func touchesMoved(touches: NSSet, withEvent event: UIEvent) {
let touch: AnyObject? = touches.anyObject();
let location = touch?.locationInNode(self)
// set the new location of touch
var currentY = location!.y
// Set Top and Bottom scroll distances, measured in screenlengths
var topLimit:CGFloat = 0.0
var bottomLimit:CGFloat = 0.6
// Set scrolling speed - Higher number is faster speed
var scrollSpeed:CGFloat = 1.0
// calculate distance moved since last touch registered and add it to current position
var newY = moveableArea.position.y + ((currentY - lastY)*scrollSpeed)
// perform checks to see if new position will be over the limits, otherwise set as new position
if newY < self.size.height*(-topLimit) {
moveableArea.position = CGPointMake(moveableArea.position.x, self.size.height*(-topLimit))
}
else if newY > self.size.height*bottomLimit {
moveableArea.position = CGPointMake(moveableArea.position.x, self.size.height*bottomLimit)
}
else {
moveableArea.position = CGPointMake(moveableArea.position.x, newY)
}
// Set new last location for next time
lastY = currentY
}
All credit goes to this article
http://greenwolfdevelopment.blogspot.co.uk/2014/11/scrolling-in-sprite-kit-swift.html
Here's the code we used to simulate UIScrollView behavior for SpriteKit menus.
Basically, you need to use a dummy UIView that matches the height of the SKScene then feed UIScrollView scroll and tap events to the SKScene for processing.
It's frustrating Apple doesn't provide this natively, but hopefully no one else has to waste time rebuilding this functionality!
class ScrollViewController: UIViewController, UIScrollViewDelegate {
// IB Outlets
#IBOutlet weak var scrollView: UIScrollView!
// General Vars
var scene = ScrollScene()
// =======================================================================================================
// MARK: Public Functions
// =======================================================================================================
override func viewDidLoad() {
// Call super
super.viewDidLoad()
// Create scene
scene = ScrollScene()
// Allow other overlays to get presented
definesPresentationContext = true
// Create content view for scrolling since SKViews vanish with height > ~2048
let contentHeight = scene.getScrollHeight()
let contentFrame = CGRect(x: 0, y: 0, width: UIScreen.main.bounds.size.width, height: contentHeight)
let contentView = UIView(frame: contentFrame)
contentView.backgroundColor = UIColor.clear
// Create SKView with same frame as <scrollView>, must manually compute because <scrollView> frame not ready at this point
let scrollViewPosY = CGFloat(0)
let scrollViewHeight = UIScreen.main.bounds.size.height - scrollViewPosY
let scrollViewFrame = CGRect(x: 0, y: scrollViewPosY, width: UIScreen.main.bounds.size.width, height: scrollViewHeight)
let skView = SKView(frame: scrollViewFrame)
view.insertSubview(skView, at: 0)
// Configure <scrollView>
scrollView.addSubview(contentView)
scrollView.delegate = self
scrollView.contentSize = contentFrame.size
// Present scene
skView.presentScene(scene)
// Handle taps on <scrollView>
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(scrollViewDidTap))
scrollView.addGestureRecognizer(tapGesture)
}
// =======================================================================================================
// MARK: UIScrollViewDelegate Functions
// =======================================================================================================
func scrollViewDidScroll(_ scrollView: UIScrollView) {
scene.scrollBy(contentOffset: scrollView.contentOffset.y)
}
// =======================================================================================================
// MARK: Gesture Functions
// =======================================================================================================
#objc func scrollViewDidTap(_ sender: UITapGestureRecognizer) {
let scrollViewPoint = sender.location(in: sender.view!)
scene.viewDidTapPoint(viewPoint: scrollViewPoint, contentOffset: scrollView.contentOffset.y)
}
}
class ScrollScene : SKScene {
// Layer Vars
let scrollLayer = SKNode()
// General Vars
var originalPosY = CGFloat(0)
// ================================================================================================
// MARK: Initializers
// ================================================================================================
override init() {
super.init()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// ================================================================================================
// MARK: Public Functions
// ================================================================================================
func scrollBy(contentOffset: CGFloat) {
scrollLayer.position.y = originalPosY + contentOffset
}
func viewDidTapPoint(viewPoint: CGPoint, contentOffset: CGFloat) {
let nodes = getNodesTouchedFromView(point: viewPoint, contentOffset: contentOffset)
}
func getScrollHeight() -> CGFloat {
return scrollLayer.calculateAccumulatedFrame().height
}
fileprivate func getNodesTouchedFromView(point: CGPoint, contentOffset: CGFloat) -> [SKNode] {
var scenePoint = convertPoint(fromView: point)
scenePoint.y += contentOffset
return scrollLayer.nodes(at: scenePoint)
}
}
I like the idea of add a SKCameraNode to scroll my menu-scene. I've founded this article really useful. You just have to change the camera position to move your menu. In Swift 4
var boardCamera = SKCameraNode()
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let location = touch.location(in: self)
let previousLocation = touch.previousLocation(in: self)
let deltaY = location.y - previousLocation.y
boardCamera.position.y += deltaY
}
}