How can I make my level menu scrollable vertically?

I have the following level Menu (as seen below). I would like to make it vertically scrollable, resulting in a total height double that of the screen (full scroll height). How can I achieve this?

enter image description here

  • delay/sleep in Swift is not working
  • Evaluate Bool property of optional object in if statement
  • Set local notification for everyday at midnight
  • How does one trap arithmetic overflow errors in Swift?
  • Using Swift 3 Library in Swift 2 Project
  • Iterate Dictionary with dictionary data and add it to an array in swift
  • Below is the code for the image above:

    class LevelMenu: SKScene {
    
    
    let levelButtonSize = SKSpriteNode(imageNamed: "b1").size
    let levelButton1: SKSpriteNode = SKSpriteNode(imageNamed: "b1")
    
    let levelButton2: SKSpriteNode = SKSpriteNode(imageNamed: "b2")
    let levelButton3: SKSpriteNode = SKSpriteNode(imageNamed: "b3")
    let levelButton4: SKSpriteNode = SKSpriteNode(imageNamed: "b4")
    let levelButton5: SKSpriteNode = SKSpriteNode(imageNamed: "b5")
    let levelButton6: SKSpriteNode = SKSpriteNode(imageNamed: "b6")
    let levelButton7: SKSpriteNode = SKSpriteNode(imageNamed: "b7")
    let levelButton8: SKSpriteNode = SKSpriteNode(imageNamed: "b8")
    
    let levelButton9: SKSpriteNode = SKSpriteNode(imageNamed: "b9")
    let levelButton10: SKSpriteNode = SKSpriteNode(imageNamed: "b10")
    let levelButton11: SKSpriteNode = SKSpriteNode(imageNamed: "b11")
    let levelButton12: SKSpriteNode = SKSpriteNode(imageNamed: "b12")
    
    override init(size: CGSize){
        super.init(size: size)
        let bg = SKSpriteNode(imageNamed: "bg")
        backgroundImage.position = CGPoint(x: self.frame.midX, y: self.frame.midY)
        self.addChild(bg)
    
        let column1PosX = levelButtonSize.width*cDiff
        let column2PosX = levelButtonSize.width*cDiff + levelButtonSize.width*2.0
        let column3PosX = levelButtonSize.width*cDiff + levelButtonSize.width*4.0
    
        let row1PosY = self.frame.height - levelButtonSize.width*1.5
        let row2PosY = row1PosY - levelButtonSize.height - levelButtonSize.width*rDiff
        let row3PosY = row2PosY - levelButtonSize.height - levelButtonSize.width*rDiff
        let row4PosY = row3PosY - levelButtonSize.height - levelButtonSize.width*rDiff
    
        levelButton1.position = CGPoint(x: column1PosX, y:  row1PosY)
        levelButton1.zPosition = 10
        self.addChild(levelButton1)
    
    
        levelButton2.position = CGPoint(x: column2PosX, y:  row1PosY)
        self.addChild(levelButton2)
    
        levelButton3.position = CGPoint(x: column3PosX, y:  row1PosY)
        self.addChild(levelButton3)
    
        levelButton4.position = CGPoint(x: column1PosX, y: row2PosY)
        self.addChild(levelButton4)
    
        levelButton5.position = CGPoint(x: column2PosX, y: row2PosY)
        self.addChild(levelButton5)
    
        levelButton6.position = CGPoint(x: column3PosX, y: row2PosY)
        self.addChild(levelButton6)
    
        levelButton7.position = CGPoint(x: column1PosX, y: row3PosY)
        self.addChild(levelButton7)
    
    
        levelButton8.position = CGPoint(x: column2PosX, y: row3PosY)
        self.addChild(levelButton8)
    
    
        levelButton9.position = CGPoint(x: column3PosX, y: row3PosY)
        self.addChild(levelButton9)
    
        levelButton10.position = CGPoint(x: column1PosX, y: row4PosY)
        self.addChild(levelButton10)
    
        levelButton11.position = CGPoint(x: column2PosX, y: row4PosY)
        self.addChild(levelButton11)
    
        levelButton12.position = CGPoint(x: column3PosX, y: row4PosY)
        self.addChild(levelButton12)
    
    }
    

    UPDATE

    Based on Ron Myschuk’s solution, the code below show’s what I’ve been able to achieve and this link shows a .gif of the issue I am having currently, where the screen scrolls too much at the top of the menu.

    class LMScene: SKScene {
    
    let levelButtonSize = SKSpriteNode(imageNamed: "b1").size
    let levelButton1: SKSpriteNode = SKSpriteNode(imageNamed: "b1")
    
    let levelButton2: SKSpriteNode = SKSpriteNode(imageNamed: "b2")
    let levelButton3: SKSpriteNode = SKSpriteNode(imageNamed: "b3")
    let levelButton4: SKSpriteNode = SKSpriteNode(imageNamed: "b4")
    let levelButton5: SKSpriteNode = SKSpriteNode(imageNamed: "b5")
    let levelButton6: SKSpriteNode = SKSpriteNode(imageNamed: "b6")
    let levelButton7: SKSpriteNode = SKSpriteNode(imageNamed: "b7")
    let levelButton8: SKSpriteNode = SKSpriteNode(imageNamed: "b8")
    
    let levelButton9: SKSpriteNode = SKSpriteNode(imageNamed: "b9")
    let levelButton10: SKSpriteNode = SKSpriteNode(imageNamed: "b10")
    let levelButton11: SKSpriteNode = SKSpriteNode(imageNamed: "b11")
    let levelButton12: SKSpriteNode = SKSpriteNode(imageNamed: "b12")
    
    let levelButton13: SKSpriteNode = SKSpriteNode(imageNamed: "b13")
    let levelButton14: SKSpriteNode = SKSpriteNode(imageNamed: "b14")
    let levelButton15: SKSpriteNode = SKSpriteNode(imageNamed: "b15")
    let levelButton16: SKSpriteNode = SKSpriteNode(imageNamed: "b16")
    let levelButton17: SKSpriteNode = SKSpriteNode(imageNamed: "b17")
    let levelButton18: SKSpriteNode = SKSpriteNode(imageNamed: "b18")
    
    
    private var scrollCell = SKSpriteNode()
    
    private var moveAmtX: CGFloat = 0
    private var moveAmtY: CGFloat = 0
    private let minimum_detect_distance: CGFloat = 30
    private var initialPosition: CGPoint = CGPoint.zero
    private var initialTouch: CGPoint = CGPoint.zero
    private var resettingSlider = false
    
    override init(size: CGSize){
        super.init(size: size)
    
    
        scrollCell = SKSpriteNode(color: .blue, size: CGSize(width: self.size.width, height: 2*self.size.height - self.frame.width*0.24734))
        scrollCell.position = CGPoint(x: 0, y: 0)
        scrollCell.anchorPoint = CGPoint.zero
        scrollCell.zPosition = 0
        self.addChild(scrollCell)
    
        let backgroundImage = SKSpriteNode(imageNamed: "bg")
        backgroundImage.position = CGPoint(x: self.frame.midX, y: self.frame.midY)
        self.addChild(backgroundImage)
    
        let column1PosX = levelButtonSize.width/2 + self.frame.width*0.14855
        let column2PosX = 3*levelButtonSize.width/2 + 2*self.frame.width*0.14855
        let column3PosX = 5*levelButtonSize.width/2 + 3*self.frame.width*0.14855
    
    
        let row1PosY = self.frame.height - levelButtonSize.height/2 - self.frame.width*0.24734
        let row2PosY = row1PosY - levelButtonSize.height - self.frame.width*0.24734
        let row3PosY = row2PosY - levelButtonSize.height - self.frame.width*0.24734
        let row4PosY = row3PosY - levelButtonSize.height - self.frame.width*0.24734
        let row5PosY = row4PosY - levelButtonSize.height - self.frame.width*0.24734
        let row6PosY = row5PosY - levelButtonSize.height - self.frame.width*0.24734
    
        levelButton1.position = CGPoint(x: column1PosX, y:  row1PosY)
        levelButton1.zPosition = 10
        scrollCell.addChild(levelButton1)
    
    
        levelButton2.position = CGPoint(x: column2PosX, y:  row1PosY)
        levelButton2.zPosition = 10
        scrollCell.addChild(levelButton2)
    
        levelButton3.position = CGPoint(x: column3PosX, y:  row1PosY)
        levelButton3.zPosition = 10
        scrollCell.addChild(levelButton3)
    
        levelButton4.position = CGPoint(x: column1PosX, y: row2PosY)
        levelButton4.zPosition = 10
        scrollCell.addChild(levelButton4)
    
        levelButton5.position = CGPoint(x: column2PosX, y: row2PosY)
        levelButton5.zPosition = 10
        scrollCell.addChild(levelButton5)
    
        levelButton6.position = CGPoint(x: column3PosX, y: row2PosY)
        levelButton6.zPosition = 10
        scrollCell.addChild(levelButton6)
    
        levelButton7.position = CGPoint(x: column1PosX, y: row3PosY)
        levelButton7.zPosition = 10
        scrollCell.addChild(levelButton7)
    
    
        levelButton8.position = CGPoint(x: column2PosX, y: row3PosY)
        levelButton8.zPosition = 10
        scrollCell.addChild(levelButton8)
    
        levelButton9.position = CGPoint(x: column3PosX, y: row3PosY)
        levelButton9.zPosition = 10
        scrollCell.addChild(levelButton9)
    
        levelButton10.position = CGPoint(x: column1PosX, y: row4PosY)
        levelButton10.zPosition = 10
        scrollCell.addChild(levelButton10)
    
        levelButton11.position = CGPoint(x: column2PosX, y: row4PosY)
        levelButton11.zPosition = 10
        scrollCell.addChild(levelButton11)
    
        levelButton12.position = CGPoint(x: column3PosX, y: row4PosY)
        levelButton12.zPosition = 10
        scrollCell.addChild(levelButton12)
    
        levelButton13.position = CGPoint(x: column1PosX, y: row5PosY)
        levelButton13.zPosition = 10
        scrollCell.addChild(levelButton13)
    
        levelButton14.position = CGPoint(x: column2PosX, y: row5PosY)
        levelButton14.zPosition = 10
        scrollCell.addChild(levelButton14)
    
        levelButton15.position = CGPoint(x: column3PosX, y: row5PosY)
        levelButton15.zPosition = 10
        scrollCell.addChild(levelButton15)
    
        levelButton16.position = CGPoint(x: column1PosX, y: row6PosY)
        levelButton16.zPosition = 10
        scrollCell.addChild(levelButton16)
    
        levelButton17.position = CGPoint(x: column2PosX, y: row6PosY)
        levelButton17.zPosition = 10
        scrollCell.addChild(levelButton17)
    
        levelButton18.position = CGPoint(x: column3PosX, y: row6PosY)
        levelButton18.zPosition = 10
        scrollCell.addChild(levelButton18)
    
    }
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    
        if let touch = touches.first as UITouch! {
    
            if let touch = touches.first as UITouch! {
    
                self.scrollCell.removeAllActions()
                initialTouch = touch.location(in: self.scene!.view)
                moveAmtY = 0
                initialPosition = self.scrollCell.position
            }
        }
    
    }
    
    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
    
        if let touch = touches.first as UITouch! {
    
            let movingPoint: CGPoint = touch.location(in: self.scene!.view)
    
            moveAmtY = movingPoint.y - initialTouch.y
    
            scrollCell.position = CGPoint(x: initialPosition.x, y: initialPosition.y - moveAmtY)
        }
    }
    
    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
    
        checkForResettingSlider()
        yMoveActions(moveTo: -moveAmtY)
    }
    
    func checkForResettingSlider() {
    
        if resettingSlider { return }
    
        let scrollDif: CGFloat = (scrollCell.size.height - self.size.height) / 2.0
    
        if scrollCell.position.y > scrollDif {
    
            let move: SKAction = SKAction.moveTo(y: scrollDif, duration: 0.3)
            move.timingMode = .easeOut
            scrollCell.run(move, completion: { self.resettingSlider = false })
        }
    
        if scrollCell.position.y < -scrollDif {
    
            let move: SKAction = SKAction.moveTo(y: 0 - scrollDif, duration: 0.3)
            move.timingMode = .easeOut
            scrollCell.run(move, completion: { self.resettingSlider = false })
        }
    }
    
    func yMoveActions(moveTo: CGFloat) {
    
        let move: SKAction = SKAction.moveBy(x: 0, y: (moveTo * 1.5), duration: 0.3)
        move.timingMode = .easeOut
    
        self.scrollCell.run(move, completion: { self.checkForResettingSlider() })
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    }
    

    Solutions Collect From Internet About “How can I make my level menu scrollable vertically?”

    Here is some code that I use to scroll vertically, I’ve adapted it to fit your menu. It doesn’t perfectly line up to all your items, but it’ll give you somewhere to start. And it’ll show you how to figure this out on your own.

    EDIT: I’ve updated the code use these funds instead. Still declare your buttons the same way but call my createMenu func to actually create the menu. It is looped so it auto adjusts if you change the number of menu items. the only thing you have to be aware of is; if you add or remove buttons change the array at the top of createMenu accordingly. Also adjust the padding variable to how much vertical space you want between the items

    func createMenu() {
    
        let buttons = [levelButton1, levelButton2, levelButton3, levelButton4, levelButton5, levelButton6, levelButton7, levelButton8, levelButton9, levelButton10, levelButton11, levelButton12, levelButton13, levelButton14, levelButton15, levelButton16, levelButton17, levelButton18]
        let padding: CGFloat = 400
    
        let numberOfRows = CGFloat(buttons.count / 3)
    
        scrollCell = SKSpriteNode(color: .blue, size: CGSize(width: 1024, height: levelButtonSize.height * numberOfRows + padding * numberOfRows))
        scrollCell.position = CGPoint(x: 0 - self.size.width / 4, y: 0 - (scrollCell.size.height - self.size.height / 2))
        scrollCell.anchorPoint = CGPoint.zero
        scrollCell.zPosition = 0
        self.addChild(scrollCell)
    
    //        let backgroundImage = SKSpriteNode(imageNamed: "bg")
    //        backgroundImage.position = CGPoint(x: self.frame.midX, y: self.frame.midY)
    //        self.addChild(backgroundImage)
    
        let column1PosX = scrollCell.size.width / 3 / 2
        let column2PosX = scrollCell.size.width / 2
        let column3PosX = scrollCell.size.width / 3 / 2 + scrollCell.size.width / 3 * 2
        var colCount = 0
        var rowCount = 0
    
        for button in buttons {
    
            var posX: CGFloat = column2PosX
            if colCount == 0 {
                posX =  column1PosX
            }
            else if colCount == 2 {
                posX =  column3PosX
                colCount = -1
            }
    
            let indexOffset = CGFloat(rowCount) * (levelButtonSize.height + padding)
            let posY = scrollCell.size.height - levelButtonSize.height / 2 - (indexOffset + padding / 2)
            button.position = CGPoint(x: posX, y: posY)
            button.setScale(0.5)
            button.zPosition = 10
            scrollCell.addChild(button)
    
            if colCount == -1 {
                rowCount += 1
            }
            colCount += 1
        }
    }
    
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    
        if let touch = touches.first as UITouch! {
    
            self.scrollCell.removeAllActions()
            initialTouch = touch.location(in: self.scene!.view)
            moveAmtY = 0
            initialPosition = self.scrollCell.position
        }
    }
    
    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
    
        if let touch = touches.first as UITouch! {
    
            let movingPoint: CGPoint = touch.location(in: self.scene!.view)
    
            moveAmtY = movingPoint.y - initialTouch.y
    
            scrollCell.position = CGPoint(x: initialPosition.x, y: initialPosition.y - moveAmtY)
        }
    }
    
    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
    
        checkForResettingSlider()
        yMoveActions(moveTo: -moveAmtY)
    }
    
    func checkForResettingSlider() {
    
        let topPos: CGFloat = scrollCell.size.height - self.size.height / 2
        let bottomPos = 0 - (self.size.height / 2)
    
        if scrollCell.position.y > bottomPos {
    
            let move = SKAction.moveTo(y: bottomPos, duration: 0.3)
            move.timingMode = .easeOut
            scrollCell.run(move)
        }
    
        if scrollCell.position.y < -topPos {
    
            let move = SKAction.moveTo(y: -topPos, duration: 0.3)
            move.timingMode = .easeOut
            scrollCell.run(move)
        }
    }
    
    func yMoveActions(moveTo: CGFloat) {
    
        let move = SKAction.moveBy(x: 0, y: (moveTo * 1.5), duration: 0.3)
        move.timingMode = .easeOut
    
        self.scrollCell.run(move, completion: { self.checkForResettingSlider() })
    }