How to Create Scrollable Table in Skspritekit

What is the right way to create scrollable table in SKSpriteKit?

An approach I use is:

  1. In the scene's didMoveToView: method, set up a UIScrollView instance. Set all the properties you need, and make sure to also set its delegate, set hidden=YES and add its panGestureREcognizer to the view gesture recognizer collection.

  2. In willMoveFromView: tear it down and clean it up.

  3. In the scrollview delegate, implement scrollViewDidScroll:. Here you can use the scroll view's contentOffset point to adjust a node's position.

I have implemented an offsetable node that I use as a root node for things I need to scroll. All it does is implement a contentOffset property - you can see it here: CATOffsetNode Gist.

So in scrollViewDidScroll: all I need to do is:

-(void)scrollViewDidScroll:(UIScrollView *)scrollView
{
// I don't need to flip scrollView.contentOffset.Y because my scrollview
// Y coordinate is flipped. See below.
self->_gameListNode.contentOffset = scrollView.contentOffset;
}

Then I add all the items I need to scroll as children for _gameListNode.

The result is a scrollable game list, which offers a native scrollview experience, including bounciness and swipe acceleration etc.

Here is an example implementation you can check out: ScrollKit on GitHub.

Edit: note about scrollview contentOffset Y direction:

UIView and SpriteKit Y coordinates go in opposite directions (UIView's origin is top left, and Y increases downwards), and I prefer to stick to one coordinate system. So I've made a custom scrollview class that flips the Y direction in the initializer. E.g.:

-(id)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame])
{
CGAffineTransform verticalFlip = CGAffineTransformMakeScale(1,-1);
self.transform = verticalFlip;
}
return self;
}

This is not necessary (and you don't even need to subclass UIScrollView to achieve this) but without this you would have to manually flip contentOffset.Y in the scrollview delegate.

Spritekit - Tableview scrolling cut off

Your issue is in the line:

override func didMove(to view: SKView) {
gameTableView.frame = CGRect(x:14,y:100, width: frame.maxX / 1.08, height: frame.maxY)
...

This happened because your gameTableView height is bigger than the scene height:

Sample Image

To solve you can try to decrease the height of your table for example:

gameTableView.frame = CGRect(x:14,y:100, width: frame.maxX / 1.08, height: frame.maxY/2)

Scroll View in SpriteKit

You have several options here.

It is possible to integrate UI elements into SpriteKit, but instead of adding children to self (your scene), you would add subviews to self.view. You would of course, work with the view now instead of a scene so theres a different way of positioning elements which you would have to work out. Theres quite a few examples of how to do this, here's one that might help you.

An alternative is using something like Scroll Kit, which helps you integrate a UIScrollView into SpriteKit very conveniently!

Also check out this question which might be exactly what you're looking for. Hope this helps!

SpriteKit and UIScrollView and HUD

I answered a similar question here: What is the right way to create scrollable table in SKSpriteKit?

To summarize - I suggest building upon ScrollKit. On top of that I recommend flipping the UIScrollView Y-coordinates to match SpriteKit's coordinate system, as well as using a custom SKNode (example of such a node here) that allows to set its contentOffset.

Sprite Kit Table View

UIKit elements like UITableView, UIView, etc cannot be used as children of SpriteKit nodes. You can place a UITableView on top of a SKView/UIView but that comes out as crude. I would suggest you use a normal UIViewController to display your Game Center scores.

However, if you still need to display the scores using SpriteKit, there is a component called ScrollNode which might do the trick. You can find it here.

Swift SKSpritenodes in a UIScrollView

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

Scrolling Game Scene with SpriteKit

We built something like this into our SKATiledMap. The trick is to add the object you want to follow to the background you want to scroll. This will also keep the background on screen.

-(void)update
{
if (self.autoFollowNode)
{
self.position = CGPointMake(-self.autoFollowNode.position.x+self.scene.size.width/2, -self.autoFollowNode.position.y+self.scene.size.height/2);

//keep map from going off screen
CGPoint position = self.position;

if (position.x > 0)
position.x = 0;

if (position.y > 0)
position.y = 0;

//self.mapHeight*self.tileWidth gives you the size of the map in points
if (position.y < -self.mapHeight*self.tileWidth+self.scene.size.height)
position.y = -self.mapHeight*self.tileWidth+self.scene.size.height;
if (position.x < -self.mapWidth*self.tileWidth+self.scene.size.width)
position.x = -self.mapWidth*self.tileWidth+self.scene.size.width;

self.position = CGPointMake((int)(position.x), (int)(position.y));
}
}

self in this case is the background and autoFollowNode is the players. You could just use self.size.width instead of self.mapHeight*self.tileWidth Hopefully that makes sense and his helpful.



Related Topics



Leave a reply



Submit