0

The goal is to scroll a SKScene with the same behavior as a UIScrollView.

Other questions are similar, but the answers did not help.

This code illustrates how UIScrollView + SKView will show content when the SKView subview is 2000 points tall, yet shows nothing if the subview is 2050 points tall.

The height matters because if a SKScene contains a map of nodes that is 3000 points tall, for instance, we need the scene rendered in the UIScrollView (by presenting the scene inside a SKView, which itself is a subview of the UIScrollView).

class TestViewController: UIViewController, UIScrollViewDelegate {

    @IBOutlet weak var scrollView: UIScrollView!

    override func viewDidLoad() {
        // Call super
        super.viewDidLoad()

        // Make scene (then someone call the police)
        let scene = TestScene(size: UIScreen.main.bounds.size)

        // Allow other overlays to get presented
        definesPresentationContext = true

        // Set background color
        scrollView.backgroundColor = UIColor.clear

        // Create content view for scrolling since SKViews vanish with height > 2050
        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
        let skFrame = CGRect(x: 0, y: 0, width: UIScreen.main.bounds.size.width, height: contentHeight)
        let skView = SKView(frame: skFrame)

        // Configure <scrollView>
        scrollView.insertSubview(skView, at: 0)
        scrollView.addSubview(contentView)
        scrollView.delegate = self
        scrollView.contentSize = contentFrame.size

        // Present scene
        skView.presentScene(scene)
    }

    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        scene.scrollBy(offset: scrollView.contentOffset.y)
    }
}
Crashalot
  • 33,605
  • 61
  • 269
  • 439
  • I can see that gray view with CGFloat(2050) height also – Prashant Tukadiya Aug 30 '18 at 04:49
  • The View is added in the view hierarchy correctly. Can't see it because the background color is not set for SKView. Run the app and see the Debug view hierarchy from the Debug area. Also, the scrolling will indicate the length of the whole view which is caused by the SKView height. – JAHID HASAN POLASH Aug 30 '18 at 04:51
  • defining scroll view content size may solve your problem @Crashalot – teja_D Aug 30 '18 at 05:42
  • @PrashantTukadiya are you running against a simulator or device? Try running against iPhone 5 simulator (iOS 10). What happens when you choose taller heights like 5000? – Crashalot Aug 30 '18 at 06:26
  • @teja_D we tried that before, but it didn't work. :( – Crashalot Aug 30 '18 at 06:47
  • @Crashalot - I haven't found any documentation, but it appears to be a limit for `SKView` (or maybe the backing layer?). It has nothing to do with the scroll view... add it to the view instead, and you'll see the same result - no backgroundColor when height is > 2000. Note: When I add a label as a subview of your `testView` at the bottom, testView *is* still scrollable all the way down - even when height is set to 3000... just no background fill color. – DonMag Aug 30 '18 at 13:17
  • as mentioned in the other question, the framebuffer, or the canvas size, is limited to 2048x2048. It is actually (2048x2048) * retina mode, and I do not remember, but I think 6+ does not account for 6144x6144 canvas sizes, it may still have to use the 4096x4096 – Knight0fDragon Aug 31 '18 at 16:17
  • Possible duplicate of [SpriteKit: why does SKView disappear at one height but appears at another height? What's the maximum height for SKView?](https://stackoverflow.com/questions/52108374/spritekit-why-does-skview-disappear-at-one-height-but-appears-at-another-height) – Knight0fDragon Aug 31 '18 at 16:18
  • Voting to close this once since you have another similar question – Knight0fDragon Aug 31 '18 at 16:19
  • Also, if it is working on devices and not simulator, then metal doesnt adhere to these rules, only opengl. Haven't tested metal devices to see if they work. – Knight0fDragon Aug 31 '18 at 16:23
  • @Knight0fDragon actually could you post the buffer response as an answer to the other question? that was useful information. this one will get changed to reflect the difficulty in scrolling a SKScene, which is a common question and isn't well answered elsewhere (hence the struggles here!). thanks again for your help! – Crashalot Aug 31 '18 at 16:33
  • @Knight0fDragon this way you'll get credit for answering two questions, and hopefully no one else wastes more time on reproducing UIScrollView scroll effects for SKScenes! – Crashalot Aug 31 '18 at 16:34
  • @Knight0fDragon question edited. please post the solution (don't add the SKView to the UIScrollView -- duh!) so you can claim the points. hopefully other people can reuse this same code and not waste time on simulating UIScrollView scrolling for SpriteKit menus. thanks again! – Crashalot Aug 31 '18 at 16:53
  • Your question is not that much different though because you are still talking about the size. – Knight0fDragon Sep 04 '18 at 18:12
  • BTW, does not make sense to be native in sprite kit. ScrollViews are designed for UIKit. You should be using UIKit in conjunction with SpriteKit. This is why we do not have gesture or gamekit support native to SpriteKit either. Each API serves a specific purpose. – Knight0fDragon Sep 04 '18 at 18:25

2 Answers2

1

See SpriteKit: why does SKView disappear at one height but appears at another height? What's the maximum height for SKView? in regards to your size problem.

As for what to do about your scroll view, since you are limited to size on openGL rendering, what you need to do is underlay your scene behind your scrollview (do not make it a subview) then pass the scroll coordinate position to the scene, and set the scenes position accordingly. Now this may disable touch on your scene, so you are going to have to set it up to hit the view behind it.

Knight0fDragon
  • 16,609
  • 2
  • 23
  • 44
  • Sorry, this question was edited to reflect the actual issue. (The question was simplified to what seemed like the root cause.) Could you please post this answer on the other question, and then list as this answer your point about not adding the SKView to the UIScrollView? – Crashalot Aug 31 '18 at 19:10
1

These two classes demonstrate how to scroll SKScene menus or scenes with UIScrollView behavior.

Thanks to @KnightofDragon for catching the key flaw in the code.

It's frustrating Apple doesn't provide this functionality natively, but hopefully no one else has to waste days experimenting with the best way to implement menu scrolling in SpriteKit!

The UIViewController assumes the UIScrollView has been added via Storyboard.

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)
    }
}
Crashalot
  • 33,605
  • 61
  • 269
  • 439