1

Using these as guides:

SpriteKit pinch to zoom camera

SpriteKit- How to zoom-in and zoom-out of an SKScene?

I've implemented the following functions to zoom in and out of a SpriteKit scene, as well as the ability to incrementally zoom in and out of a scene using two UIButtons:

 var previousCameraScale = CGFloat()
    var count = 0

    @objc func pinchGestureAction(_ sender: UIPinchGestureRecognizer) {
      guard let camera = cadScene.camera else {
        return
      }
        
      if sender.state == .began {
        previousCameraScale = camera.xScale
      }
        
      camera.setScale(previousCameraScale * 1 / sender.scale)
    }

   @IBAction func zoomIn(_ sender: Any) {
        count = count+1
        guard let camera = cadScene.camera else {
          return
        }
        previousCameraScale = camera.xScale
        camera.setScale(previousCameraScale / CGFloat(count))
    }
    
    @IBAction func zoomOut(_ sender: Any) {
        count = count-1

        guard let camera = cadScene.camera else {
          return
        }
        previousCameraScale = camera.xScale
        camera.setScale(previousCameraScale * CGFloat(count))
    }

The pinch to zoom works great; I'm having trouble with the zoom - in and out ones, though...setting the scale to inf and -inf for some reason.

Not the greatest at math; any help appreciated!

EDIT:

Updated logic:

@IBAction func zoomIn(_ sender: Any) {
    count = count+0.1
    
    guard let camera = cadScene.camera else {
        return
    }
    previousCameraScale = camera.xScale - CGFloat(count)
    camera.setScale(previousCameraScale * 1)
    
    print("Previous camera scale \(previousCameraScale)")
}

@IBAction func zoomOut(_ sender: Any) {
    count = count-0.1
    guard let camera = cadScene.camera else {
        return
    }
    previousCameraScale = camera.xScale + CGFloat(count)

    
    if previousCameraScale < 0 {
        return
    }
    
    camera.setScale(previousCameraScale * 1)
    
    print("Previous camera scale \(previousCameraScale)")
}

@objc func pinchGestureAction(_ sender: UIPinchGestureRecognizer) {
    guard let camera = cadScene.camera else {
        return
    }
    
    if sender.state == .began {
        previousCameraScale = camera.xScale
    }
    
    camera.setScale(previousCameraScale * 1 / sender.scale)
    
    print("Previous camera scale \(previousCameraScale)")
    print("Sender scale \(sender.scale)")
    print("Camera scale \(camera.xScale)")
}
narner
  • 2,908
  • 3
  • 26
  • 63
  • 1
    Every time I get infinities in my math, it’s because I’m dividing by zero somewhere. The only division looks like it’s in the pinch gesture action. Are you sure that `sender.scale` is never zero? – adam.wulf Oct 17 '21 at 21:42
  • @adam.wulf that's the thing; I don't see that occurring in the pinch action - only in ZoomIn/ZoomOut (which are tied to UIButton's) – narner Oct 17 '21 at 21:50
  • 1
    what's the value of `camera.xScale`? in the zoom in case, the first zoom will have count = 1, so you're just setting the scale to `xScale`. Then in zoomOut, count is decremented to zero, so you set the scale to zero. – adam.wulf Oct 18 '21 at 17:58
  • @adam.wulf actually realized my logic was a bit wonky before seeing this; just updated my question with those changes! – narner Oct 18 '21 at 21:13

2 Answers2

2

Can you try the following? It combines pinch & step zoom

    // MARK: - Zoom
    var scene: SKScene! // Setup this first
    var stepper: UIStepper!
    let zoomStepsInOneDirection: Double = 25
    var cameraScaleAtRest: CGFloat!

    func setupCamera() {
        let cameraNode = SKCameraNode()
        scene.addChild(cameraNode)
        scene.camera = cameraNode
        cameraScaleAtRest = cameraNode.xScale
    }

    func setupControls() {
        let gr = UIPinchGestureRecognizer(target: self, action: #selector(pinchGestureAction(_:)))
        view.addGestureRecognizer(gr)

        let stepper = UIStepper(frame: CGRect(x: 0, y: 50, width: 400, height: 100))
        stepper.minimumValue = -zoomStepsInOneDirection
        stepper.value = 0
        stepper.maximumValue = zoomStepsInOneDirection
        stepper.addTarget(self, action: #selector(stepperPressed(_:)), for: .touchUpInside)
        view.addSubview(stepper)
        self.stepper = stepper
    }

    @objc func stepperPressed(_ sender: UIStepper) {
        assert(cameraScaleAtRest != nil)
        assert(scene.camera != nil)

        let scaleFactor = exp(sender.value / zoomStepsInOneDirection)
        // the following could also work
//        let scaleFactor = pow(2, sender.value / zoomStepsInOneDirection)

        let newScale = cameraScaleAtRest / scaleFactor
        scene.camera?.setScale(newScale)
    }

    @objc func pinchGestureAction(_ sender: UIPinchGestureRecognizer) {
        assert(scene.camera != nil)

        guard let camera = scene.camera else {
            return
        }

        switch sender.state {
        case .began:
            cameraScaleAtRest = camera.xScale
            stepper.value = 0
        case .ended:
            cameraScaleAtRest = camera.xScale
            stepper.value = 0
        default:
            let newScale = cameraScaleAtRest / sender.scale
            camera.setScale(newScale)
        }
    }

1

I don't know if this will solve the problem but you are dividing previousCameraScale multiplied by 1 by senderScale, not by 1 divided by senderScale. If you want to divide previousCameraScale by 1 / senderScale your statement should be like this:

camera.setScale(previousCameraScale * (1 / sender.scale))

This is due to precedence in math operators.

Gigi
  • 616
  • 6
  • 7
  • That does help a bit with my pinch to zoom method; so thank you! But really trying to figure out how to get zoom in / out with the buttons working well. – narner Oct 22 '21 at 15:09
  • 1
    There is no actual difference between `previousCameraScale * 1 / sender.scale` and `previousCameraScale * (1 / sender.scale) `. Operations `*` and `/` has the same priority. Both expressions result to the equal value. – kelin Oct 28 '21 at 10:41
  • 1
    Actually you could just use `previousCameraScale / sender.scale` for simplicity, the result will be again the same. – kelin Oct 28 '21 at 10:45