I want to zoom in out a CCNode by pinching and panning the screen. The node has a background which is very large but the portionof it shown on the screen. That node also contains other sprites.
What I have done by now is that first I register UIPinchGestureRecognizer
UIPinchGestureRecognizer * pinchRecognizer = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:#selector(handlePinchFrom:)];
[[[CCDirector sharedDirector] view] addGestureRecognizer: pinchRecognizer];
-(void)handlePinchFrom:(UIPinchGestureRecognizer *) pinch
{
if(pinch.state == UIGestureRecognizerStateEnded) {
prevScale = 1;
}
else {
CGFloat dscale = [self scale] - prevScale + pinch.scale;
if(dscale > 0)
{
deltaScale = dscale;
}
CGAffineTransform transform = CGAffineTransformScale(pinch.view.transform, deltaScale, deltaScale);
[pinch.view setTransform: transform];
// [_contentNode setScale:deltaScale];
prevScale = pinch.scale;
}
}
The problem is that it scalw whole UIView not the CCNode. I have also tried to by setting the scale of my _contentNode.
**EDIT
I ave also tried this
- (void)handlePinchGesture:(UIPinchGestureRecognizer*)aPinchGestureRecognizer
{
if (pinch.state == UIGestureRecognizerStateBegan || pinch.state == UIGestureRecognizerStateChanged) {
CGPoint midpoint = [pinch locationInView:[CCDirector sharedDirector].view];
CGSize winSize = [CCDirector sharedDirector].viewSize;
float x = midpoint.x/winSize.width;
float y = midpoint.y/winSize.height;
_contentNode.anchorPoint = CGPointMake(x, y);
float scale = [pinch scale];
_contentNode.scale *= scale;
pinch.scale = 1;
}
}
But it zoom from the bottom left of the screen.
I had the same problem. I use CCScrollView, that contains CCNode that larger than device screen. I want scroll and zoom it, but node shouldnt scroll out of screen, and scale smaller than screen. So, i create my subclass of CCScrollView, where i handle pinch. It has some strange glitches, but it works fine at all.
When pinch began i set anchor point of my node to pinch center on node space. Then i need change position of my node proportional to shift of anchor point, so moving anchor point doesn't change nodes location on view:
- (void)handlePinch:(UIPinchGestureRecognizer*)recognizer
{
if (recognizer.state == UIGestureRecognizerStateEnded) {
_previousScale = self.contentNode.scale;
}
else if (recognizer.state == UIGestureRecognizerStateBegan) {
float X = [recognizer locationInNode:self.contentNode].x / self.contentNode.contentSize.width;
float Y = [recognizer locationInNode:self.contentNode].y / self.contentNode.contentSize.height;
float positionX = self.contentNode.position.x + self.contentNode.boundingBox.size.width * (X - self.contentNode.anchorPoint.x);
float positionY = self.contentNode.position.y + self.contentNode.boundingBox.size.height * (Y - self.contentNode.anchorPoint.y);
self.contentNode.anchorPoint = ccp(X, Y);
self.contentNode.position = ccp(positionX, positionY);
}
else {
CGFloat scale = _previousScale * recognizer.scale;
if (scale >= maxScale) {
self.contentNode.scale = maxScale;
}
else if (scale <= [self minScale]) {
self.contentNode.scale = [self minScale];
}
else {
self.contentNode.scale = scale;
}
}
}
Also i need change CCScrollView min and max scroll, so my node never scroll out of view. Default anchor point is (0,1), so i need shift min and max scroll proportional to the new anchor point.
- (float) maxScrollX
{
if (!self.contentNode) return 0;
float maxScroll = self.contentNode.boundingBox.size.width - self.contentSizeInPoints.width;
if (maxScroll < 0) maxScroll = 0;
return maxScroll - self.contentNode.boundingBox.size.width * self.contentNode.anchorPoint.x;
}
- (float) maxScrollY
{
if (!self.contentNode) return 0;
float maxScroll = self.contentNode.boundingBox.size.height - self.contentSizeInPoints.height;
if (maxScroll < 0) maxScroll = 0;
return maxScroll - self.contentNode.boundingBox.size.height * (1 - self.contentNode.anchorPoint.y);
}
- (float) minScrollX
{
float minScroll = [super minScrollX];
return minScroll - self.contentNode.boundingBox.size.width * self.contentNode.anchorPoint.x;
}
- (float) minScrollY
{
float minScroll = [super minScrollY];
return minScroll - self.contentNode.boundingBox.size.height * (1 - self.contentNode.anchorPoint.y);
}
UIGestureRecognizerStateEnded doesn't have locationInNode: method, so i added it by category. It just return touch location on node space:
#import "UIGestureRecognizer+locationInNode.h"
#implementation UIGestureRecognizer (locationInNode)
- (CGPoint) locationInNode:(CCNode*) node
{
CCDirector* dir = [CCDirector sharedDirector];
CGPoint touchLocation = [self locationInView: [self view]];
touchLocation = [dir convertToGL: touchLocation];
return [node convertToNodeSpace:touchLocation];
}
- (CGPoint) locationInWorld
{
CCDirector* dir = [CCDirector sharedDirector];
CGPoint touchLocation = [self locationInView: [self view]];
return [dir convertToGL: touchLocation];
}
#end
Related
I set Google Map in view in UIScrollView. everything OK but i can not pinch my map. How to fix it?
Salaam Reza jaan.
In your case you can use something like this. Cheers.
Edit: this is a better way to do it. Behbakht shid, direh inja :P
- (void) scaleSelfWith:(UIPinchGestureRecognizer *)gestureRecognizer{
if ([gestureRecognizer numberOfTouches] >1) {
//getting width and height between gestureCenter and one of my finger
float x = [gestureRecognizer locationInView:self].x - [gestureRecognizer locationOfTouch:1 inView:self].x;
if (x<0) {
x *= -1;
}
float y = [gestureRecognizer locationInView:self].y - [gestureRecognizer locationOfTouch:1 inView:self].y;
if (y<0) {
y *= -1;
}
//set Border
if (gestureRecognizer.state == UIGestureRecognizerStateBegan) {
xDis = self.bounds.size.width - x*2;
yDis = self.bounds.size.height - y*2;
}
//double size cause x and y is just the way from the middle to my finger
float width = x*2+xDis;
if (width < 1) {
width = 1;
}
float height = y*2+yDis;
if (height < 1) {
height = 1;
}
self.bounds = CGRectMake(self.bounds.origin.x , self.bounds.origin.y , width, height);
[gestureRecognizer setScale:1];
[[self layer] setBorderWidth:2.f];
}
}
I have a UIImageView i am performing pinch zoom & move image on full zoom.I am able to move the image but the issue is even if the image is not zoomed in it still moves on the screen.Please tell me how can i prevent it?
Here is the code for that
#define MINIMUM_SCALE 0.5
#define MAXIMUM_SCALE 6.0
#property CGPoint translation;
- (void)pan:(UIPanGestureRecognizer *)gesture {
static CGPoint currentTranslation;
static CGFloat currentScale = 0;
if (gesture.state == UIGestureRecognizerStateBegan) {
currentTranslation = _translation;
currentScale = self.view.frame.size.width / self.view.bounds.size.width;
}
if (gesture.state == UIGestureRecognizerStateEnded || gesture.state == UIGestureRecognizerStateChanged) {
CGPoint translation = [gesture translationInView:self.view];
_translation.x = translation.x + currentTranslation.x;
_translation.y = translation.y + currentTranslation.y;
CGAffineTransform transform1 = CGAffineTransformMakeTranslation(_translation.x , _translation.y);
CGAffineTransform transform2 = CGAffineTransformMakeScale(currentScale, currentScale);
CGAffineTransform transform = CGAffineTransformConcat(transform1, transform2);
self.view.transform = transform;
}
}
- (void)pinch:(UIPinchGestureRecognizer *)gesture {
if (gesture.state == UIGestureRecognizerStateEnded || gesture.state == UIGestureRecognizerStateChanged) {
// NSLog(#"gesture.scale = %f", gesture.scale);
CGFloat currentScale = self.view.frame.size.width / self.view.bounds.size.width;
CGFloat newScale = currentScale * gesture.scale;
if (newScale < MINIMUM_SCALE) {
newScale = MINIMUM_SCALE;
}
if (newScale > MAXIMUM_SCALE) {
newScale = MAXIMUM_SCALE;
}
CGAffineTransform transform1 = CGAffineTransformMakeTranslation(_translation.x, _translation.y);
CGAffineTransform transform2 = CGAffineTransformMakeScale(newScale, newScale);
CGAffineTransform transform = CGAffineTransformConcat(transform1, transform2);
self.view.transform = transform;
gesture.scale = 1;
}
}
**PS:**ImageView should only move when image is zoomed otherwise it should not be moveable.
I suggest a different approach:
Create a UIScrollView add the UIImageView into the scrollView's contentView and allow scrolling and zooming on the scrollView.
To prevent scrolling when not zoomed, set setScrollEnabled to NO and enable it in scrollViewDidZoom if the zoomLevel reaches a custom threshold.
So sth. like this should make make it much easier what you're trying to accomplish with a lot less code.
- (void)viewDidLoad:(BOOL)animated {
[super viewDidLoad:animated];
self.scrollView.scrollEnabled = NO;
self.scrollView.minimumZoomScale = 1;
}
- (void)scrollViewDidZoom:(UIScrollView *)scrollView {
if (scrollView.zoomLevel > 1) {
scrollView.scrollEnabled = YES;
} else {
scrollView.scrollEnabled = NO;
}
}
I re-post the code I gave you in your first question on the subject ( Pinch zoom shifting image to most left corner on iPad in iOS? ), because I think the way you do it does not work as expected ( as far as I have tested ).
The pinch zoom is supposed to zoom on the middle of the fingers, and not on the middle of the image. If you really want to apply the zoom on center, I have added a boolean property zoomOnCenter.
The boundaries checking is done in the translateBy method.
Here is a link on the xCode test project: https://drive.google.com/file/d/0B88aMtNA0z2aWVBIbGZGdVhpdWM/view?usp=sharing
Interface
#interface PinchViewController : UIViewController
#property(nonatomic,strong) IBOutlet UIView* contentView;
#property(nonatomic,assign) BOOL zoomOnCenter;
#end
Implementation
#implementation PinchViewController
{
CGPoint translation;
CGFloat scale;
CGAffineTransform scaleTransform;
CGAffineTransform translateTransform;
CGPoint previousTranslation;
CGFloat previousScale;
NSUInteger previousNumTouches;
}
-(void)viewDidLoad
{
scale = 1.0f;
scaleTransform = CGAffineTransformIdentity;
translateTransform = CGAffineTransformIdentity;
previousTranslation = CGPointZero;
previousNumTouches = 0;
UIPinchGestureRecognizer *pinch=[[UIPinchGestureRecognizer alloc]initWithTarget:self action:#selector(handlePinch:)];
[self.view addGestureRecognizer:pinch];
UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(handlePan:)];
[panGesture setMinimumNumberOfTouches:1];
[panGesture setMaximumNumberOfTouches:1];
[self.view addGestureRecognizer:panGesture];
}
-(void)handlePinch:(UIPinchGestureRecognizer*)recognizer
{
// 1 - find pinch center
CGPoint mid = self.zoomOnCenter ? CGPointZero : [self computePinchCenter:recognizer];
mid.x-= recognizer.view.bounds.size.width / 2.0f;
mid.y-= recognizer.view.bounds.size.height / 2.0f;
// 2 - compute deltas
NSUInteger numTouches = recognizer.numberOfTouches;
if ( (recognizer.state==UIGestureRecognizerStateBegan) || ( previousNumTouches != numTouches ) ) {
previousScale = recognizer.scale;
previousTranslation = mid;
previousNumTouches = numTouches;
}
CGFloat deltaScale = ( recognizer.scale - previousScale ) * scale;
previousScale = recognizer.scale;
CGPoint deltaTranslation = CGPointMake(mid.x-previousTranslation.x, mid.y-previousTranslation.y);
previousTranslation = mid;
deltaTranslation.x/=scale;
deltaTranslation.y/=scale;
// 3 - apply
scale+=deltaScale;
if (scale<0.01) scale = 0.01; else if (scale>10) scale = 10;
scaleTransform = CGAffineTransformMakeScale(scale, scale);
[self translateBy:deltaTranslation];
}
- (void)handlePan:(UIPanGestureRecognizer *)recognizer
{
if (recognizer.state==UIGestureRecognizerStateBegan) previousTranslation = CGPointZero;
CGPoint recognizerTranslation = [recognizer translationInView:self.contentView];
CGPoint deltaTranslation = CGPointMake(recognizerTranslation.x - previousTranslation.x,recognizerTranslation.y - previousTranslation.y);
previousTranslation = recognizerTranslation;
[self translateBy:deltaTranslation];
}
-(void)translateBy:(CGPoint)delta
{
CGSize contentSize = self.contentView.bounds.size;
CGSize viewSize = self.view.bounds.size;
CGSize scaledViewSize = viewSize;
scaledViewSize.width/=scale;
scaledViewSize.height/=scale;
CGPoint maxTranslation = CGPointMake( (contentSize.width-scaledViewSize.width) / 2.0f , (contentSize.height-scaledViewSize.height) / 2.0f );
if ( contentSize.width*scale < viewSize.width ) {
delta.x=0;
translation.x = 0;
} else {
translation.x+=delta.x;
if ( translation.x < - maxTranslation.x ) {
translation.x = - maxTranslation.x;
}
else if ( translation.x > maxTranslation.x ) {
translation.x = maxTranslation.x;
}
}
if ( contentSize.height*scale < viewSize.height ) {
delta.y=0; translation.y = 0;
} else {
translation.y+=delta.y;
if ( translation.y < - maxTranslation.y ) {
translation.y = - maxTranslation.y;
}
else if ( translation.y > maxTranslation.y ) {
translation.y = maxTranslation.y;
}
}
translateTransform = CGAffineTransformMakeTranslation(translation.x,translation.y);
self.contentView.transform = CGAffineTransformConcat(translateTransform,scaleTransform);
}
-(CGPoint)computePinchCenter:(UIPinchGestureRecognizer*)recognizer
{
// 1 - handle up to 3 touches
NSUInteger numTouches = recognizer.numberOfTouches;
if (numTouches>3) numTouches = 3;
// 2 - Find fingers middle point - with (0,0) being the center of the view
CGPoint pt1,pt2,pt3,mid;
switch (numTouches) {
case 3:
pt3 = [recognizer locationOfTouch:2 inView:recognizer.view];
case 2:
pt2 = [recognizer locationOfTouch:1 inView:recognizer.view];
case 1:
pt1 = [recognizer locationOfTouch:0 inView:recognizer.view];
}
switch (numTouches) {
case 3:
mid = CGPointMake( ( ( pt1.x + pt2.x ) / 2.0f + pt3.x ) / 2.0f, ( ( pt1.y + pt2.y ) / 2.0f + pt3.y ) / 2.0f );
break;
case 2:
mid = CGPointMake( ( pt1.x + pt2.x ) / 2.0f, ( pt1.y + pt2.y ) / 2.0f );
break;
case 1:
mid = CGPointMake( pt1.x, pt1.y);
break;
}
return mid;
}
#end
You can check the frame of your imageView and if it is same it means imageview has not been zoomed So you can disable the pan gesture if frame of imageview is same ......and if frame has changed then you can enable the pan gesture.....
Using a UIPanGestureRecognizer in my view controller, I'm trying to draw a view (ArrowView) at an angle based upon the touch location. I'm trying to use CGAffineTransformRotate to rotate the view based up the angle between the first touch and the current touch, but this isn't working unless the view has already been drawn by at lease 20 or more pixels. Also, when drawing, the view doesn't always line up under my finger. Is this the correct approach for this situation? If not, does anyone recommend a better way of accomplishing this? If so, what am I doing wrong?
ViewController.m
#implementation ViewController {
ArrowView *_selectedArrowView;
UIColor *_selectedColor;
CGFloat _selectedWeight;
CGPoint _startPoint;
}
- (void)viewDidLoad {
[super viewDidLoad];
_selectedColor = [UIColor yellowColor];
_selectedWeight = 3;
UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(panHandler:)];
[self.view addGestureRecognizer:pan];
}
- (void) panHandler: (UIPanGestureRecognizer *) sender {
if (sender.state == UIGestureRecognizerStateBegan) {
//Instantiate the arrow
CGPoint touchPoint = [sender locationInView:sender.view];
_startPoint = touchPoint;
_selectedArrowView = [[ArrowView alloc] initWithFrame:CGRectMake(touchPoint.x, touchPoint.y, 0, 25) withColor:_selectedColor withWeight:_selectedWeight];
_selectedArrowView.delegate = self;
[self.view addSubview:_selectedArrowView];
[self.view bringSubviewToFront:_selectedArrowView];
} else if (sender.state == UIGestureRecognizerStateChanged) {
//"Draw" the arrow based upon finger postion
CGPoint touchPoint = [sender locationInView:sender.view];
[_selectedArrowView drawArrow:_startPoint to:touchPoint];
}
}
#end
ArrowView.m
- (void) drawArrow: (CGPoint) startPoint to: (CGPoint) endPoint {
startPoint = [self convertPoint:startPoint fromView:self.superview];
endPoint = [self convertPoint:endPoint fromView:self.superview];
if (_initialAngle == -1000 /*Initially set to an arbitrary value so I know when the draw began*/) {
_initialAngle = atan2(startPoint.y - endPoint.y, startPoint.x - endPoint.x);
[self setPosition:0];
} else {
CGFloat ang = atan2(startPoint.y - endPoint.y, startPoint.x - endPoint.x);
ang -= _initialAngle;
self.transform = CGAffineTransformRotate(self.transform, ang);
CGFloat diff = (endPoint.x - self.bounds.size.width);
NSLog(#"\n\n diff: %f \n\n", diff);
self.bounds = CGRectMake(0, 0, self.bounds.size.width + diff, self.bounds.size.height);
_endPoint = CGPointMake(self.bounds.size.width, self.bounds.size.height);
[self setNeedsDisplay];
}
}
- (void) setPosition: (CGFloat) anchorPointX {
CGPoint layerLoc;
if (anchorPointX == 0) {
layerLoc = CGPointMake(self.layer.bounds.origin.x, self.layer.bounds.origin.y + (self.layer.bounds.size.height / 2));
} else {
layerLoc = CGPointMake(self.layer.bounds.origin.x + self.layer.bounds.size.width, self.layer.bounds.origin.y + (self.layer.bounds.size.height / 2));
}
CGPoint superLoc = [self convertPoint:layerLoc toView:self.superview];
self.layer.anchorPoint = CGPointMake(anchorPointX, 0.5);
self.layer.position = superLoc;
}
- (CGFloat) pToA: (CGPoint) touchPoint {
CGPoint start;
if (_dotButtonIndex == kDotButtonFirst) {
start = CGPointMake(CGRectGetMaxX(self.bounds), CGRectGetMaxY(self.bounds));
} else {
start = CGPointMake(CGRectGetMinX(self.bounds), CGRectGetMinY(self.bounds));
}
return atan2(start.y - touchPoint.y, start.x - touchPoint.x);
}
Link to project on GitHub: Project Link
Figured it out.
I had to make it have an initial width so the angle would work.
_initialAngle = atan2(startPoint.y - endPoint.y, startPoint.x - (endPoint.x + self.frame.size.width));
I have a small problem. In this tutorial How To Make a Tile-Based Game with Cocos2D 2.X used cocos2d V2.0, I wanna make this in cocos2d V3.0. So, it doesn't work! Thanks! (I don't speak english)
I think problem in this line - self.position = viewPoint;
#property (strong) CCTiledMap *tileMap;
#property (strong) CCTiledMapLayer *background;
#property (strong) CCSprite *player;
- (id)init
{
// Apple recommend assigning self with supers return value
self = [super init];
if (!self) return(nil);
// Enable touch handling on scene node
self.userInteractionEnabled = YES;
self.tileMap = [CCTiledMap tiledMapWithFile:#"TileMap.tmx"];
self.background = [_tileMap layerNamed:#"Background"];
[self addChild:_tileMap z:-1];
CCTiledMapObjectGroup *objectGroup = [_tileMap objectGroupNamed:#"Objects"];
NSAssert(objectGroup != nil, #"tile map has no objects object layer");
NSDictionary *spawnPoint = [objectGroup objectNamed:#"SpawnPoint"];
int x = [spawnPoint[#"x"] integerValue];
int y = [spawnPoint[#"y"] integerValue];
_player = [CCSprite spriteWithImageNamed:#"Player.png"];
_player.position = ccp(x,y);
[self addChild:_player];
[self setViewPointCenter:_player.position];
// done
return self;
}
- (void)setViewPointCenter:(CGPoint) position {
CGSize winSize = [CCDirector sharedDirector].viewSize;
int x = MAX(position.x, winSize.width/2);
int y = MAX(position.y, winSize.height/2);
x = MIN(x, (_tileMap.mapSize.width * _tileMap.tileSize.width) - winSize.width / 2);
y = MIN(y, (_tileMap.mapSize.height * _tileMap.tileSize.height) - winSize.height/2);
CGPoint actualPosition = ccp(x, y);
CGPoint centerOfView = ccp(winSize.width/2, winSize.height/2);
CGPoint viewPoint = ccpSub(centerOfView, actualPosition);
self.position = viewPoint;
}
-(void)touchBegan:(UITouch *)touch withEvent:(UIEvent *)event
{
CGPoint touchLocation = [touch locationInView:touch.view];
touchLocation = [[CCDirector sharedDirector] convertToGL:touchLocation];
touchLocation = [self convertToNodeSpace:touchLocation];
CGPoint playerPos = _player.position;
CGPoint diff = ccpSub(touchLocation, playerPos);
if ( abs(diff.x) > abs(diff.y) ) {
if (diff.x > 0) {
playerPos.x += _tileMap.tileSize.width;
} else {
playerPos.x -= _tileMap.tileSize.width;
}
} else {
if (diff.y > 0) {
playerPos.y += _tileMap.tileSize.height;
} else {
playerPos.y -= _tileMap.tileSize.height;
}
}
CCLOG(#"playerPos %#",CGPointCreateDictionaryRepresentation(playerPos));
// safety check on the bounds of the map
if (playerPos.x <= (_tileMap.mapSize.width * _tileMap.tileSize.width) &&
playerPos.y <= (_tileMap.mapSize.height * _tileMap.tileSize.height) &&
playerPos.y >= 0 &&
playerPos.x >= 0 )
{
[self setPlayerPosition:playerPos];
}
[self setViewPointCenter:_player.position];
NSLog(#"%#", NSStringFromCGPoint(touchLocation));
}
-(void)setPlayerPosition:(CGPoint)position {
_player.position = position;
}
The problem is that the area of the user interaction is by default bound to the contentSize of the scene (screen size).
When calling your setViewPointCenter method, you are moving the scene position out of this area where the touch event is not handled.
You have to extend this area of the contentSize of the tile map like that :
[self setContentSize:[_tileMap contentSize]];
self.userInteractionEnabled = YES;
My goal is to resize UIView with a handle - its subview. I got my project working perfectly to accomplish that: the handle has a PanGestureRecognizer and its handler method resizes the view (parent) using the following method:
-(IBAction)handleResizeGesture:(UIPanGestureRecognizer *)recognizer {
CGPoint touchLocation = [recognizer locationInView:container.superview];
CGPoint center = container.center;
switch (recognizer.state) {
case UIGestureRecognizerStateBegan: {
deltaAngle = atan2f(touchLocation.y - center.y, touchLocation.x - center.x) -
CGAffineTransformGetAngle(container.transform);
initialBounds = container.bounds;
initialDistance = CGPointGetDistance(center, touchLocation);
break;
}
case UIGestureRecognizerStateChanged: {
CGFloat scale = CGPointGetDistance(center, touchLocation)/initialDistance;
CGFloat minimumScale = self.minimumSize/MIN(initialBounds.size.width,
initialBounds.size.height);
scale = MAX(scale, minimumScale);
CGRect scaledBounds = CGRectScale(initialBounds, scale, scale);
container.bounds = scaledBounds;
[container setNeedsDisplay];
break;
}
case UIGestureRecognizerStateEnded:
break;
default:
break;
}
}
Please note that I use center and bounds properties because frame is NOT reliable when transform is applied to my view.
However, my requirement is really to resize the view in ANY direction - not only proportionally as the code does. The problem is that I am not finding the correct methods or approaches how this handle may resize its superview's bounds (width or height) so it always sticks to the corner while finger is dragging it around.
Here is my project if it is easier to see what I mean.
Updated solution as suggested by an answer below works very well but once transform is applied (e.g. in viewDidLoad I have container.transform = CGAffineTransformMakeRotation(90);) it does not:
case UIGestureRecognizerStateBegan: {
initialBounds = container.bounds;
initialDistance = CGPointGetDistance(center, touchLocation);
initialDistanceX = CGPointGetDistanceX(center, touchLocation);
initialDistanceY = CGPointGetDistanceY(center, touchLocation);
break;
}
case UIGestureRecognizerStateChanged: {
CGFloat scaleX = abs(center.x-touchLocation.x)/initialDistanceX;
CGFloat scaleY = abs(center.y-touchLocation.y)/initialDistanceY;
CGFloat minimumScale = self.minimumSize/MIN(initialBounds.size.width, initialBounds.size.height);
scaleX = MAX(scaleX, minimumScale);
scaleY = MAX(scaleY, minimumScale);
CGRect scaledBounds = CGRectScale(initialBounds, scaleX, scaleY);
container.bounds = scaledBounds;
[container setNeedsDisplay];
break;
}
where
CG_INLINE CGFloat CGPointGetDistanceX(CGPoint point1, CGPoint point2) {
return (point2.x - point1.x);
}
CG_INLINE CGFloat CGPointGetDistanceY(CGPoint point1, CGPoint point2) {
return (point2.y - point1.y);
}
You are setting the same scale parameter in your call to CGRectScale(initialBounds, scale, scale); try this:
case UIGestureRecognizerStateChanged: {
CGFloat scaleX = abs(center.x-touchLocation.x)/initialDistance;
CGFloat scaleY = abs(center.y-touchLocation.y)/initialDistance;
CGFloat minimumScale = self.minimumSize/MIN(initialBounds.size.width, initialBounds.size.height);
scaleX = MAX(scaleX, minimumScale);
scaleY = MAX(scaleY, minimumScale);
CGRect scaledBounds = CGRectScale(initialBounds, scaleX, scaleY);
container.bounds = scaledBounds;
[container setNeedsDisplay];
break;
You may also consider to store initialDistanceX and initialDistanceY.
Use UIPinchGestureRecognizer & UIPanGestureRecognizer.
Try this code
//--Create and configure the pinch gesture
UIPinchGestureRecognizer *pinchGestureRecognizer = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:#selector(pinchGestureDetected:)];
[pinchGestureRecognizer setDelegate:self];
[container.superview addGestureRecognizer:pinchGestureRecognizer];
//--Create and configure the pan gesture
UIPanGestureRecognizer *panGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(panGestureDetected:)];
[panGestureRecognizer setDelegate:self];
[container.superview addGestureRecognizer:panGestureRecognizer];
For UIPinchGestureRecognizer:
- (void)pinchGestureDetected:(UIPinchGestureRecognizer *)recognizer
{
UIGestureRecognizerState state = [recognizer state];
if (state == UIGestureRecognizerStateBegan || state == UIGestureRecognizerStateChanged)
{
CGFloat scale = [recognizer scale];
[recognizer.view setTransform:CGAffineTransformScale(recognizer.view.transform, scale, scale)];
[recognizer setScale:1.0];
panGesture = YES;
}
}
For UIPanGestureRecognizer :
- (void)panGestureDetected:(UIPanGestureRecognizer *)recognizer
{
UIGestureRecognizerState state = [recognizer state];
if (panGesture==YES)
{
if (state == UIGestureRecognizerStateBegan || state == UIGestureRecognizerStateChanged)
{
CGPoint translation = [recognizer translationInView:recognizer.view];
[recognizer.view setTransform:CGAffineTransformTranslate(recognizer.view.transform, translation.x, translation.y)];
[recognizer setTranslation:CGPointZero inView:recognizer.view];
}}
}
I'd not control it with a subview. It gets messy when you apply transform to the outer view.
Simply add two view to the ViewController, and hook them up with some code.
I've altered the project, find test-001 at GitHub.
ViewController code can be empty for this.
The view you want to transform TransformableView needs a scale property, and a method to apply it (if you want to add other transformations as well).
#interface TransformableView : UIView
#property (nonatomic) CGSize scale;
-(void)applyTransforms;
#end
#implementation TransformableView
-(void)applyTransforms
{
// Do whatever additional transform you want (e.g. concat additional rotations / mirroring to the transform).
self.transform = CGAffineTransformMakeScale(self.scale.width, self.scale.height);
}
#end
And the DraggableCorner can manage the rest (with some basic math).
#class TransformableView;
#interface DraggableCorner : UIView
#property (nonatomic, weak) IBOutlet TransformableView *targetView; // Hook up in IB.
-(IBAction)panGestureRecognized:(UIPanGestureRecognizer*) recognizer;
#end
CG_INLINE CGPoint offsetOfPoints(CGPoint point1, CGPoint point2)
{ return (CGPoint){point1.x - point2.x, point1.y -point2.y}; }
CG_INLINE CGPoint addPoints(CGPoint point1, CGPoint point2)
{ return (CGPoint){point1.x + point2.x, point1.y + point2.y}; }
#interface DraggableCorner ()
#property (nonatomic) CGPoint touchOffset;
#end
#implementation DraggableCorner
-(IBAction)panGestureRecognized:(UIPanGestureRecognizer*) recognizer
{
// Location in superview.
CGPoint touchLocation = [recognizer locationInView:self.superview];
// Began.
if (recognizer.state == UIGestureRecognizerStateBegan)
{
// Finger distance from handler.
self.touchOffset = offsetOfPoints(self.center, touchLocation);
}
// Moved.
if (recognizer.state == UIGestureRecognizerStateChanged)
{
// Drag.
self.center = addPoints(touchLocation, self.touchOffset);
// Desired size.
CGPoint distanceFromTargetCenter = offsetOfPoints(self.center, self.targetView.center);
CGSize desiredTargetSize = (CGSize){distanceFromTargetCenter.x * 2.0, distanceFromTargetCenter.y * 2.0};
// -----
// You can put limitations here, simply clamp `desiredTargetSize` to some value.
// -----
// Scale needed for desired size.
CGSize targetSize = self.targetView.bounds.size;
CGSize targetRatio = (CGSize){desiredTargetSize.width / targetSize.width, desiredTargetSize.height / targetSize.height};
// Apply.
self.targetView.scale = targetRatio;
[self.targetView applyTransforms];
}
}
Set the classes in IB, and hook up the IBAction and the IBOutlet of DraggableView.