I added a UIPinchGestureRecognizer and UIRotationGestureRecognizer on UITextView. When I do a pinch or rotation, I change the transform property of my UITextView object. However, if I rotate it at some angle and zoom in to some scale, the text will suddenly disappear.
If I continue to rotate it, the text can reappear at some angle.
Is there any way to fix the disappearance?
Here's what I did with the gesture recognizer methods.
- (void)onPinchtextView:(UIPinchGestureRecognizer *)pinchGestureRecognizer {
CGFloat scale = pinchGestureRecognizer.scale;
if (pinchGestureRecognizer.state == UIGestureRecognizerStateChanged || pinchGestureRecognizer.state == UIGestureRecognizerStateBegan) {
self.textView.transform = CGAffineTransformScale(self.textView.transform, scale, scale);
pinchGestureRecognizer.scale = 1.f;
}
- (void)onRotatetextView:(UIRotationGestureRecognizer *)rotateGestureRecognizer {
CGFloat rotation = rotateGestureRecognizer.rotation;
if (rotateGestureRecognizer.state == UIGestureRecognizerStateChanged || rotateGestureRecognizer.state == UIGestureRecognizerStateBegan) {
self.textView.transform = CGAffineTransformRotate(self.textView.transform, rotation);
[rotateGestureRecognizer setRotation:0];
}
}
Related
In my app I have implemented pinch to zoom and panning. Both zoom and pan are working but I would like to limit panning so that the edge of the zoomed image cannot be dragged into the view leaving empty space.
The range of coordinates for different scale factors does not seem to be linear. For example, on an iPad with an image view that is 764x764, at 2x zoom, the range of the transform X coordinate for a full pan is -191 to 191. At 3x, the range is about -254 to 254.
So my question is, how do I calculate the pan limit for any given scale factor?
Here is my code for the gesture recognizers:
#interface myVC()
{
CGPoint ptPanZoom1;
CGPoint ptPanZoom2;
CGFloat fltScale;
}
#property (nonatomic, strong) UIImageView* imgView; // View hosting pan/zoom image
#end
- (IBAction) handlePan:(UIPanGestureRecognizer*) sender
{
if ( sender.state == UIGestureRecognizerStateBegan )
{
ptPanZoom2 = ptPanZoom1;
return;
}
if ( (sender.state == UIGestureRecognizerStateChanged)
|| (sender.state == UIGestureRecognizerStateEnded) )
{
CGPoint ptTrans = [sender translationInView:self.imgView];
ptPanZoom1.x = ptTrans.x + ptPanZoom2.x;
ptPanZoom1.y = ptTrans.y + ptPanZoom2.y;
CGAffineTransform trnsFrm1 = CGAffineTransformMakeTranslation(ptPanZoom1.x, ptPanZoom1.y);
CGAffineTransform trnsFrm2 = CGAffineTransformMakeScale(fltScale, fltScale);
self.imgView.transform = CGAffineTransformConcat(trnsFrm1, trnsFrm2);
}
}
- (IBAction) handlePinch:(UIPinchGestureRecognizer*) sender
{
if ( (sender.state == UIGestureRecognizerStateBegan)
|| (sender.state == UIGestureRecognizerStateChanged)
|| (sender.state == UIGestureRecognizerStateEnded) )
{
fltScale *= sender.scale;
sender.scale = 1.0;
if ( fltScale <= 1.0 )
{
fltScale = 1.0;
ptPanZoom1.x = 0.0; // When scale goes to 1, snap position back
ptPanZoom1.y = 0.0;
}
else if ( fltScale > 6.0 )
{
fltScale = 6.0;
}
CGAffineTransform trnsFrm1 = CGAffineTransformMakeTranslation(ptPanZoom1.x, ptPanZoom1.y);
CGAffineTransform trnsFrm2 = CGAffineTransformMakeScale(fltScale, fltScale);
self.imgView.transform = CGAffineTransformConcat(trnsFrm1, trnsFrm2);
}
}
I figured out the equation for determining the pan limits:
int iMaxPanX = ((self.imgView.bounds.size.width * (fltScale - 1.0)) / fltScale) / 2;
int iMinPanX = -iMaxPanX;
int iMaxPanY = ((self.imgView.bounds.size.height * (fltScale - 1.0)) / fltScale) / 2;
int iMinPanY = -iMaxPanY;
In my pan gesture handler I am allowing the pan to go slightly beyond the limits during a changed event so that the user can easily see they have reached the edge.
There is a UIView A. I put a icon on view A and try to use pan gesture to scale and rotate this view A. The scale function works fine but I can't make rotation work. The code is as following. Any help will be appreciated. thanks
- (void)scaleAndRotateWatermark:(UIPanGestureRecognizer *)gesture
{
if (gesture.state == UIGestureRecognizerStateChanged
|| gesture.state == UIGestureRecognizerStateEnded)
{
UIView *child = gesture.view;
UIView *view = child.superview;
CGPoint translatedPoint = [gesture translationInView:view];
CGPoint originCenter = child.center;
CGPoint newCenter = CGPointMake(child.centerX+translatedPoint.x, child.centerY+translatedPoint.y);
float origX = originCenter.x;
float origY = originCenter.y;
float newX = newCenter.x;
float newY = newCenter.y;
float originDis = (origX*origX) + (origY*origY);
float newDis = (newX*newX) + (newY*newY);
float scale = newDis/originDis;
view.transform = CGAffineTransformScale(view.transform, scale, scale);
// rotation calulate here
// need your help
// end of rotation
[guesture setTranslation:CGPointZero inView:_videoPreviewLayer];
}
}
In an app I am working on, I'm performing a transition where I'm using a transform to scale a containerView that holds on to the view of a UINavigationController. This transform is tied to a pan gesture recognizer, so depending on the distance of the pan, the transform changes.
On iOS7, the navigationBar is set to be translucent but dark. As I apply the transform, certain values cause the navigationBar to render oddly... see pictures
The result is that I get this flickering affect where the navigationBar changes the degree of translucency as the pan occurs. Any idea why this might be?
Thanks!
EDIT:
Here is the code that does it:
- (void)handlePanRecognizer:(UIPanGestureRecognizer *)recognizer
{
if ([recognizer isEqual:self.panGestureRecognizer])
{
if ([recognizer state] == UIGestureRecognizerStateBegan || [recognizer state] == UIGestureRecognizerStateChanged)
{
CGPoint translation = [recognizer translationInView:recognizer.view];
CGFloat xMin = CGRectGetMidX([self centerContainerFrame]);
CGFloat xValue = self.rightContainer.center.x + translation.x;
[self.rightContainer setCenter:CGPointMake(MAX(xMin, xValue), self.rightContainer.center.y)];
[recognizer setTranslation:CGPointZero inView:recognizer.view];
CGFloat normalizedTranslation = self.rightContainer.frame.origin.x / self.rightContainer.frame.size.width;
CGFloat relativeZoom = (1.0 - self.transformScaleFactor) * normalizedTranslation;
CGFloat relativeAlpha = (1.0 - self.minFadeAlpha) * normalizedTranslation;
CGAffineTransform transform = CGAffineTransformMakeScale(self.transformScaleFactor + relativeZoom, self.transformScaleFactor + relativeZoom);
CGFloat newAlpha = relativeAlpha + self.minFadeAlpha;
[self.centerContainer setTransform:transform];
[self.centerContainer setAlpha:newAlpha];
}
...etc
I am currently designing a little game as a learning project and it is basically the following, a image is rotated and scaled on viewDidLoad and another image is a direct copy of the original image.
So basically there is a image that is a little bit different from the other, the objective is to scale it back down, rotate it and move it on top of the other image with 5 pixels, 5 degrees of rotate and 5 percent scale.
I have run into an issue. I "skew" the image using the following code...
CGAffineTransform transform = CGAffineTransformMakeRotation(M_PI/2.5);
image.transform = CGAffineTransformScale(transform, 1.25, 1.25);
My pan gesture does not perform correctly after rotating the image and then scaling it 125%.
Does anyone know what could be going on here? By incorrectly I mean that it doesn't move around with my finger.. It seems to glide or go the opposite direction. Video .
My pan gesture method is below.
if (gesture.state == UIGestureRecognizerStateBegan || gesture.state == UIGestureRecognizerStateChanged) {
CGPoint translation = [gesture translationInView:image];
//if within game field
if((image.center.x + translation.x) > 50.0 && (image.center.x + translation.x) < 255.0 && (image.center.y + translation.y) > 50.0 && (image.center.y + translation.y) < 302) {
[image setCenter:CGPointMake([image center].x + translation.x, [image center].y + translation.y)]; //move it
}
}
[gesture setTranslation:CGPointZero inView:[image superview]];
if(gesture.state == UIGestureRecognizerStateEnded) [self didWin]; // not relevant to question
Does anyone know why pan performs incorrectly after I rotate and scale my image? When I comment out those first two lines of code the pan performs correctly and moves around with the users finger.
Thanks in advance for any suggestions or help!
Rotation might have changed the frame. I used cosf and sinf function to deal with it.
handlePan and handleRotate are the callback functions, in which I can control the subview of self.view. Here, you should replace image with your own view.
static CGFloat _rotation = 0;
- (void)handlePan:(UIPanGestureRecognizer*)recognizer
{
UIImageView *image = nil;
for (UIImageView *tmp in recognizer.view.subviews) { // pick the subview
if (tmp.tag == AXIS_TAG) {
image = tmp;
}
}
CGPoint translation = [recognizer translationInView:image];
// I have found any related documents yet, but these equations do work!//////
CGFloat dx = translation.x * cosf(_rotation) - translation.y*sinf(_rotation);
CGFloat dy = translation.x * sinf(_rotation) + translation.y*cosf(_rotation);
/////////////////////////////////////////////////////////////////////////////
image.center = CGPointMake(image.center.x+dx, dy+image.center.y);
[recognizer setTranslation:CGPointMake(0, 0) inView:image];
}
- (void)handleRotate:(UIRotationGestureRecognizer*)recognizer
{
UIImageView *image = nil;
for (UIImageView *tmp in recognizer.view.subviews) { // pick the subview
if (tmp.tag == AXIS_TAG) {
image = tmp;
}
}
CGFloat r = [recognizer rotation];
if ((recognizer.state == UIGestureRecognizerStateBegan || recognizer.state == UIGestureRecognizerStateChanged)
&& recognizer.numberOfTouches == 2) {
image.transform = CGAffineTransformMakeRotation(_rotation+r);
} else if (recognizer.state == UIGestureRecognizerStateEnded) {
_rotation+=r; // Record the final rotation
}
}
The solution was to change the pan code just a tiny bit..
if (gesture.state == UIGestureRecognizerStateBegan || gesture.state == UIGestureRecognizerStateChanged) {
CGPoint translation = [gesture translationInView:self.view]; //CHANGED
//if within game field
if((image.center.x + translation.x) > 50.0 && (image.center.x + translation.x) < 255.0 && (image.center.y + translation.y) > 50.0 && (image.center.y + translation.y) < 302) {
[image setCenter:CGPointMake([image center].x + translation.x, [image center].y + translation.y)]; //move it
}
}
[gesture setTranslation:CGPointZero inView:self.view];
I changed in view:self.view and translationInView:self.view];
I have a several views that I can drag around, rotate, scale. I want to make it so they can't be drug, rotated or scaled off the screen.
Dragging seems to not be an Issue as I'm not using a Transform to generate the new position and see if that new position would put the view off the screen.
When I rotate or scale I use a CGAffineTransform (CGAffineTransformedRotate or CGAffineTransformScale) and I cant seem to get what the new frame would be without actually applying it to my view.
CGRect newElementBounds = CGRectApplyAffineTransform(element.bounds, CGAffineTransformScale([[gestureRecognizer view] transform], [gestureRecognizer scale], [gestureRecognizer scale]));
CGRect elementBoundsInSuperView = [element convertRect:newElementBounds toView:[element superview]];
elementBoundsInSuperView is not the Rect that I would Expect it to be, Its way off.
I've also Tried to get the bounds in the SuperView first and then apply the transform to it, and that's not right either.
CGRect elementBoundsInSuperView = [element convertRect:element.bounds toView:[element superview]];
CGRect newElementBounds = CGRectApplyAffineTransform(newElementBounds, CGAffineTransformScale([[gestureRecognizer view] transform], [gestureRecognizer scale], [gestureRecognizer scale]));
the [gestureRecognizer view] should be the same as element.
I came up with some gesture handlers that work so the view you are manipulatoing does not go off the area you specified. My View pallet was defined by kscreenEditorSpace, 2048.
The Pan gesture just calls the calcCenterFromXposition:yPosition:fromBoundsInSuperView: method to set its center, if the center falls out of bounds it just adjusts and keeps the element in bounds
//--------------------------------------------------------------------------------------------------------
// handlePanGesture
// Description: Called when scrollView got a DoubleFinger DoubleTap Gesture
// We want to Zoom out one ZOOM_STEP.
//
//--------------------------------------------------------------------------------------------------------
- (void)handlePanGesture:(UIPanGestureRecognizer *)gestureRecognizer {
UIView *element = [gestureRecognizer view];
if ([gestureRecognizer state] == UIGestureRecognizerStateBegan ) {
[[self superview] bringSubviewToFront:self];
}
if ([gestureRecognizer state] == UIGestureRecognizerStateBegan || [gestureRecognizer state] == UIGestureRecognizerStateChanged) {
//Front and Center Mr Element!
// Find out where we are going
CGPoint translation = [gestureRecognizer translationInView:[element superview]];
CGRect elementBoundsInSuperView = [element convertRect:element.bounds toView:[element superview]];
CGFloat xPosition = CGRectGetMidX(elementBoundsInSuperView) + translation.x;
CGFloat yPosition = CGRectGetMidY(elementBoundsInSuperView) + translation.y;
CGPoint newCenter = [self calcCenterFromXposition:xPosition yPosition:yPosition fromBoundsInSuperView:elementBoundsInSuperView];
//Re position ourselves
[element setCenter:newCenter];
//set the translation back to 0 point
[gestureRecognizer setTranslation:CGPointZero inView:[element superview]];
[self setNeedsDisplay];
}
if ([gestureRecognizer state] == UIGestureRecognizerStateEnded ) {
}
}
So the handle Pinch and Rotation are pretty Similar. instead of calling the calc Center Directly, we call another method to help determine if we are in bounds.
//--------------------------------------------------------------------------------------------------------
// handlePinchGesture
// Description: Called when scrollView got a DoubleFinger DoubleTap Gesture
// We want to Zoom out one ZOOM_STEP.
//
//--------------------------------------------------------------------------------------------------------
- (void)handlePinchGesture:(UIPinchGestureRecognizer *)gestureRecognizer {
if ([gestureRecognizer state] == UIGestureRecognizerStateBegan ) {
[[self superview] bringSubviewToFront:self];
}
if ([gestureRecognizer state] == UIGestureRecognizerStateBegan || [gestureRecognizer state] == UIGestureRecognizerStateChanged) {
BOOL aSelectedElementOffscreen = FALSE;
if ([[gestureRecognizer view] pinchOffScreen:[gestureRecognizer scale]]) {
aSelectedElementOffscreen = TRUE;
}
if (!aSelectedElementOffscreen) {
[gestureRecognizer view].transform = CGAffineTransformScale([[gestureRecognizer view] transform], [gestureRecognizer scale], [gestureRecognizer scale]);
//Update ourself
[self contentSizeChanged];
}
[gestureRecognizer setScale:1];
}
if ([gestureRecognizer state] == UIGestureRecognizerStateEnded) {
if (![self becomeFirstResponder]) {
NSLog(#" %# - %# - couldn't become first responder", INTERFACENAME, NSStringFromSelector(_cmd) );
return;
}
}
}
}
Pinch Off Screen Method
//--------------------------------------------------------------------------------------------------------
// pinchOffScreen
// Description: Called to see if the Pinch Gesture will cause element to go off screen Gesture
//
//--------------------------------------------------------------------------------------------------------
- (BOOL)pinchOffScreen:(CGFloat)scale {
// Save Our Current Transform incase we go off Screen
CGAffineTransform elementOrigTransform = [self transform];
//Apply our Transform
self.transform = CGAffineTransformScale([self transform], scale, scale);
// Get our new Bounds in the SuperView
CGRect newElementBoundsInSuperView = [self convertRect:self.bounds toView:[self superview]];
//Find out where we are in the SuperView
CGFloat xPosition = CGRectGetMidX( newElementBoundsInSuperView);
CGFloat yPosition = CGRectGetMidY( newElementBoundsInSuperView);
//See if we are off the Screen
BOOL offScreen = [self calcOffEditorFromXposition:xPosition yPosition:yPosition fromBoundsInSuperView: newElementBoundsInSuperView];
// We just wanted to Check. Revert to where we were
self.transform = elementOrigTransform;
return offScreen;
}
The Handle Rotation is Similar to Pinch, we have a helper method to see if we rotated off screen.
//--------------------------------------------------------------------------------------------------------
// handleRotationGesture
// Description: Called when we get a rotation gesture
// toggle the scroll/zoom lock
//
//--------------------------------------------------------------------------------------------------------
- (void) handleRotationGesture:(UIRotationGestureRecognizer *)gestureRecognizer{
UIView *element = [gestureRecognizer view];
if ([gestureRecognizer state] == UIGestureRecognizerStateBegan ) {
[[self superview] bringSubviewToFront:self];
}
if ([gestureRecognizer state] == UIGestureRecognizerStateBegan || [gestureRecognizer state] == UIGestureRecognizerStateChanged) {
BOOL aSelectedElementOffscreen = FALSE;
if ([element rotateOffScreen:[gestureRecognizer rotation]]) {
aSelectedElementOffscreen = TRUE;
}
if (!aSelectedElementOffscreen) {
[gestureRecognizer view].transform = CGAffineTransformRotate([element transform], [gestureRecognizer rotation]);
//Update ourself
[self contentSizeChanged];
}
[gestureRecognizer setRotation:0];
}
if ([gestureRecognizer state] == UIGestureRecognizerStateEnded) {
}
}
}
Rotate Off Screen method
//--------------------------------------------------------------------------------------------------------
// rotateOffScreen
// Description: Called to see if the Rotation Gesture will cause element to go off screen Gesture
//
//--------------------------------------------------------------------------------------------------------
- (BOOL)rotateOffScreen:(CGFloat)rotation {
// Save Our Current Transform incase we go off Screen
CGAffineTransform elementOrigTransform = [self transform];
//Apply our Transform
self.transform = CGAffineTransformRotate([self transform], rotation);
// Get our new Bounds in the SuperView
CGRect newElementBoundsInSuperView = [self convertRect:self.bounds toView:[self superview]];
//Find out where we are in the SuperVire
CGFloat xPosition = CGRectGetMidX( newElementBoundsInSuperView);
CGFloat yPosition = CGRectGetMidY( newElementBoundsInSuperView);
//See if we are off the Screen
BOOL offScreen = [self calcOffEditorFromXposition:xPosition yPosition:yPosition fromBoundsInSuperView: newElementBoundsInSuperView];
// We just wanted to Check. Revert to where we were
self.transform = elementOrigTransform;
return offScreen;
}
Calc Screen Positioning Helper Methods
#pragma mark -
#pragma mark === Calc Screen Positioning ===
#pragma mark
//--------------------------------------------------------------------------------------------------------
// calcCenterFromXposition: yPosition: fromBoundsInSuperView:
// Description: calculate the center point in the element's super view from x, y
//
//--------------------------------------------------------------------------------------------------------
-(CGPoint) calcCenterFromXposition: (CGFloat) xPosition yPosition:(CGFloat) yPosition fromBoundsInSuperView:(CGRect) elementBoundsInSuperView{
// Ge the Height/width based on SuperView Bounds
CGFloat elementWidth = CGRectGetWidth(elementBoundsInSuperView);
CGFloat elementHeight = CGRectGetHeight(elementBoundsInSuperView);
//Determine our center.x from the new x
if (xPosition < elementWidth/2) {
xPosition = elementWidth/2;
} else if (xPosition + elementWidth/2 > kscreenEditorSpace) {
xPosition = kscreenEditorSpace - elementWidth/2;
}
//Determine our center.y from the new y
if (yPosition < elementHeight/2) {
yPosition = elementHeight/2;
} else if (yPosition + elementHeight/2 > kscreenEditorSpace) {
yPosition = kscreenEditorSpace - elementHeight/2;
}
return (CGPointMake(xPosition, yPosition));
}
//--------------------------------------------------------------------------------------------------------
// calcOffEditorFromXposition: yPosition: fromBoundsInSuperView:
// Description: Determine if moving the element to x, y will it be off the editor screen
//
//--------------------------------------------------------------------------------------------------------
-(BOOL) calcOffEditorFromXposition: (CGFloat) xPosition yPosition:(CGFloat) yPosition fromBoundsInSuperView:(CGRect) elementBoundsInSuperView{
BOOL offScreen = NO;
// Ge the Height/width based on SuperView Bounds
CGFloat elementWidth = CGRectGetWidth(elementBoundsInSuperView);
CGFloat elementHeight = CGRectGetHeight(elementBoundsInSuperView);
// Off Screen on Left
if (xPosition < elementWidth/2) {
offScreen = YES;
}
//Off Screen Right
if (xPosition + elementWidth/2 > kscreenEditorSpace) {
offScreen = YES;;
}
// Off Screen Top
if (yPosition < elementHeight/2) {
offScreen = YES;
}
//Off Screen Bottom
if (yPosition + elementHeight/2 > kscreenEditorSpace) {
offScreen = YES;
}
return (offScreen);
}