1

Okay I'm trying to add a SKSpriteNode to an UIScrollView, how can I do that?

Benja0906
  • 1,437
  • 2
  • 15
  • 25
  • Possible duplicate of [How do I add a scroll view to my gameScene.swift, that will scroll through a series of sprites?](http://stackoverflow.com/questions/35000529/how-do-i-add-a-scroll-view-to-my-gamescene-swift-that-will-scroll-through-a-ser) – Knight0fDragon Jan 27 '16 at 17:27

3 Answers3

3

What have you tried so far?. As a general stack overflow rule you should always post code if you expect help.

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()

How to create a vertical scrolling menu in spritekit?

https://github.com/crashoverride777/SwiftySKScrollView

Community
  • 1
  • 1
crashoverride777
  • 10,581
  • 2
  • 32
  • 56
  • It is better to include some example to show how your class should be used. Most of the times, answers like this ( link only answers) are flagged and deleted. – Whirlwind Jan 27 '16 at 16:25
  • I know, but this is a very broad question, not one about a specific bit of code. The sample code would be way to long – crashoverride777 Jan 27 '16 at 16:28
  • Source code is recommended, but code does not have to always be posted, as long as the question is to a specific problem that would not lead to a generalized answer. A good example is this question. The OP has a specific problem, that is: How to add a `SKSpriteNode` to a `UIScrollView`? There is only a few ways this could be answered, – Knight0fDragon Jan 27 '16 at 17:22
  • Yeah but this few ways are very long, I can post some code but it will be like 1 page long. – crashoverride777 Jan 27 '16 at 17:30
2

You are running into a problem I struggled with for a while. The fundamental problem is that you cannot add SKNodes to a UIKit view, and you cannot add UIKit views to SKNodes. Views are not nodes, and nodes are not views, sadly this will not work. While you can add UIKit elements to the SKScene's view property, that is as far as you can take it. I struggled with this for quite a while before I finally got it.

I still mix UIKit and SpriteKit, btw, but I do so in a limited fashion and it never relies on adding nodes to views or vice versa.

Your best bet is to create an SKNode of some sort that is as big as you need it to be to hold all of your SKSpriteNodes and then adjust its position on screen during the touchesMoved: function. (Essentially create your own SpriteKit based scroll view).

zeeple
  • 5,509
  • 12
  • 43
  • 71
2

You can use an SKCameraNode and a scene that's bigger than your viewport. That's basically the SpriteKit version of a scroll view.

Chris Slowik
  • 2,859
  • 1
  • 14
  • 27