9
votes

I have added UIPinchGestureRecognizer to my scene.view to scale my content. I actually scale the parent node where all my visible contents reside. But I have problem though with scaling point. The thing is node scale from the lower-left corner. It's definitely not what I want. Do I have to write lots of code to be able to scale from the point where pinching occurs? Could you please give some hints as to what way to follow.

4
Did you change node's anchorPoint?Andrey Gordeev
It's not possible to change SKNode's anchor point.Mikayil Abdullayev

4 Answers

20
votes

I have been working on the same problem and my solution is shown below. Not sure if it is the best way to do it, but so far it seems to work. I'm using this code to zoom in and out of an SKNode that has several SKSpriteNode children. The children all move and scale with the SKNode as desired. The anchor point for the scaling is the location of the pinch gesture. The parent SKScene and other SKNodes in the scene are not affected. All of the work takes place during recognizer.state == UIGestureRecognizerStateChanged.

// instance variables of MyScene.
SKNode *_mySkNode;
UIPinchGestureRecognizer *_pinchGestureRecognizer;

- (void)didMoveToView:(SKView *)view
{
    _pinchGestureRecognizer = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(handleZoomFrom:)];
    [[self view] addGestureRecognizer:_pinchGestureRecognizer];
}

// Method that is called by my UIPinchGestureRecognizer.
- (void)handleZoomFrom:(UIPinchGestureRecognizer *)recognizer
{
     CGPoint anchorPoint = [recognizer locationInView:recognizer.view];
     anchorPoint = [self convertPointFromView:anchorPoint];

     if (recognizer.state == UIGestureRecognizerStateBegan) {

         // No code needed for zooming...

     } else if (recognizer.state == UIGestureRecognizerStateChanged) {

         CGPoint anchorPointInMySkNode = [_mySkNode convertPoint:anchorPoint fromNode:self];

         [_mySkNode setScale:(_mySkNode.xScale * recognizer.scale)];

         CGPoint mySkNodeAnchorPointInScene = [self convertPoint:anchorPointInMySkNode fromNode:_mySkNode];
         CGPoint translationOfAnchorInScene = CGPointSubtract(anchorPoint, mySkNodeAnchorPointInScene);

         _mySkNode.position = CGPointAdd(_mySkNode.position, translationOfAnchorInScene);

         recognizer.scale = 1.0;

     } else if (recognizer.state == UIGestureRecognizerStateEnded) {

         // No code needed here for zooming...

     }
}

The following are helper functions that were used above. They are from the Ray Wenderlich book on Sprite Kit.

SKT_INLINE CGPoint CGPointAdd(CGPoint point1, CGPoint point2) {
    return CGPointMake(point1.x + point2.x, point1.y + point2.y);
}

SKT_INLINE CGPoint CGPointSubtract(CGPoint point1, CGPoint point2) {
    return CGPointMake(point1.x - point2.x, point1.y - point2.y);
}

SKT_INLINE GLKVector2 GLKVector2FromCGPoint(CGPoint point) {
    return GLKVector2Make(point.x, point.y);
}

SKT_INLINE CGPoint CGPointFromGLKVector2(GLKVector2 vector) {
    return CGPointMake(vector.x, vector.y);
}

SKT_INLINE CGPoint CGPointMultiplyScalar(CGPoint point, CGFloat value) {
    return CGPointFromGLKVector2(GLKVector2MultiplyScalar(GLKVector2FromCGPoint(point), value));
}
3
votes

I have translated ninefifteen's solution for Swift and Pinch Gestures. I spent a couple days trying to get this to work on my own. Thank goodness for ninefifteen's Obj-C post! Here is the Swift version that appears to be working for me.

func scaleExperiment(_ sender: UIPinchGestureRecognizer) {


    var anchorPoint = sender.location(in: sender.view)

    anchorPoint = self.convertPoint(fromView: anchorPoint)

    let anchorPointInMySkNode = _mySkNode.convert(anchorPoint, from: self)

    _mySkNode.setScale(_mySkNode.xScale * sender.scale)

    let mySkNodeAnchorPointInScene = self.convert(anchorPointInMySkNode, from: _mySkNode)

    let translationOfAnchorInScene = (x: anchorPoint.x - mySkNodeAnchorPointInScene.x, y: anchorPoint.y - mySkNodeAnchorPointInScene.y)

    _mySkNode.position = CGPoint(x: _mySkNode.position.x + translationOfAnchorInScene.x, y: _mySkNode.position.y + translationOfAnchorInScene.y)

    sender.scale = 1.0


}
0
votes

Can't zoom I don't know why but the main problem is those SKT_INLINE. I've googled them and didn't found anything about 'em... The problem is when I copy/paste them in my project the compiler tells me I have to add an ";" right after them. I wonder if that's the reason that I can zoom.

0
votes

In Swift 4, my SKScene adds the UIPinchGestureRecognizer to the view but passes handling of the pinch gesture off to one of its SKNode children that is created in the scene's init(), due to some reasons not relevant here. Anyhow, this is ninefifteen's answer from the perspective of what s/he calls _mySkNode. It also includes a little code to limit the zoom and does not use the convenience functions listed at the bottom of his post. The @objc part of the declaration allows the function to be used in #selector().

Here is what is in my SKScene:

override func didMove(to view: SKView) {
    let pinchRecognizer: UIPinchGestureRecognizer = UIPinchGestureRecognizer(target: self.grid, action: #selector(self.grid.pinchZoomGrid))
    self.view!.addGestureRecognizer(pinchRecognizer)
}

And this is the relevant section in my SKNode:

// Pinch Management
@objc func pinchZoomGrid(_ recognizer: UIPinchGestureRecognizer){
    var anchorPoint: CGPoint = recognizer.location(in: recognizer.view)
    anchorPoint = self.scene!.convertPoint(fromView: anchorPoint)

    if recognizer.state == .began {
        // No zoom code
    } else if recognizer.state == .changed {
        let anchorPointInGrid = self.convert(anchorPoint, from: self.scene!)

        // Start section that limits the zoom
        if recognizer.scale < 1.0 {
            if self.xScale * recognizer.scale < 0.6 {
                self.setScale(0.6)
            } else {
                self.setScale(self.xScale * recognizer.scale)
            }
        } else if recognizer.scale > 1.0 {
            if self.xScale * recognizer.scale > 1.5 {
                self.setScale(1.5)
            } else {
                self.setScale(self.xScale * recognizer.scale)
            }
        }
        // End section that limits the zoom

        let gridAnchorPointInScene = self.scene!.convert(anchorPointInGrid, from: self)
        let translationOfAnchorPointInScene = CGPoint(x:anchorPoint.x - gridAnchorPointInScene.x,
                                                      y:anchorPoint.y - gridAnchorPointInScene.y)

        self.position = CGPoint(x:self.position.x + translationOfAnchorPointInScene.x,
                                y:self.position.y + translationOfAnchorPointInScene.y)
        recognizer.scale = 1.0

    } else if recognizer.state == .ended {
        // No zoom code
    }
}