Scaling image anchored at cetain point - iphone

Is it possible to scale an image by using a specified point as the anchor, i.e. the image "grows" out from this point?

Scaling isn't based on a point. What you want to do is move it so that the the corresponding point on the new and original images is at the same point. To do this, just adjust the (x,y) position of the image. Use the proportional distance to the edge multiplied by the difference in size.

You could do something like this (based on solution using UIPinchGesureRecognize, but you can get the idea...).
This is the selector called for the gestureRecognizer:
CGPoint newDistanceFromCenter;
CGPoint distanceFromCenter;
- (void) scale:(id)sender
{
UIPinchGestureRecognizer *recognizer = (UIPinchGestureRecognizer*)sender;
if(recognizer.state == UIGestureRecognizerStateBegan)
{
CGPoint pinchPoint = [recognizer locationInView:self];
distanceFromCenter.x = self.center.x - pinchPoint.x;
distanceFromCenter.y = self.center.y - pinchPoint.y;
}
else if(recognizer.state == UIGestureRecognizerStateChanged)
{
CGAffineTransform currentTransform = self.transform;
CGFloat scale = recognizer.scale;
newDistanceFromCenter.x = (distanceFromCenter.x * scale);
newDistanceFromCenter.y = (distanceFromCenter.y * scale);
CGPoint center = scalingImage_.center;
center.x -= (distanceFromCenter.x - newDistanceFromCenter.x);
center.y -= (distanceFromCenter.y - newDistanceFromCenter.y);
self.center = center;
distanceFromCenter = newDistanceFromCenter;
self.transform = CGAffineTransformScale(currentTransform, scale, scale);
recognizer.scale = 1;
}
}

Related

Make UIPanGestureRecognizer stretch when pulling past certain threshold

I'm trying to implement something similar to how Mac/iOS web pages are implemented, where if you pull past a certain threshold, the main view becomes "stretchy" and moves based on the velocity of the pull.
The difference, though, is I'm trying to do this horizontally for UITableViewCells with a UIPanGestureRecognizer.
I've got a handlePan method:
- (void)handlePan:(UIPanGestureRecognizer *)recognizer
{
[recognizer setMaximumNumberOfTouches:1];
CGPoint velocity = [recognizer velocityInView:self];
int panDelta = ([recognizer locationInView:self].x - [recognizer locationInView:self.superview].x);
if (recognizer.state == UIGestureRecognizerStateBegan) {
_originalCenter = self.center;
}
if (recognizer.state == UIGestureRecognizerStateChanged) {
CGPoint translation = [recognizer translationInView:self];
float xVal = 0;
if (panDelta <= 38) {
xVal = _originalCenter.x + translation.x;
} /*else {
// this is where I struggle.
xVal = _originalCenter.x;
}*/
self.center = CGPointMake(xVal, _originalCenter.y);
}
if (recognizer.state == UIGestureRecognizerStateEnded) {
CGRect newFrame;
int xOffset = 0;
newFrame = CGRectMake(xOffset, self.frame.origin.y,
self.bounds.size.width, self.bounds.size.height);
[UIView animateWithDuration:0.2 animations:^{
self.frame = newFrame;
}];
}
}
I don't have any specific algorithm to create the "stretchiness"; all I'm trying to do is make it so the sliding doesn't go as wide as the user's interaction is, and is fluid.
Ideas?
I think you are looking for a simple function with a horizontal asymptote indicating a maximum offset:
// this is where I struggle.
CGFloat maxOffset = 50.0; // change as you like
CGFloat elementWidth = ?; // fill in the width of your view animated, eg: self.frame.size.width
CGFloat absPannedOffset = fabsf(translation.x);
CGFloat rico = powf(elementWidth, 2) / maxOffset;
CGFloat absOffset = (((- 1 / rico) * powf(absPannedOffset - elementWidth, 2)) + maxOffset);
if (pannedOffset < 0) {
xVal = -absOffset;
} else {
xVal = absOffset;
}

How can I zoom a UIScrollview to a fixed point?

I am creating a control for showing the timeline. Initially , it should show the year. When zooming in and the zoomScale reached a specific value, it will show the value of the individual months, and In the next zoom level, it should show the real values for the individual days.
I have creatd a scrollview and added the layers (for showing the month/year values) to this. After overriding the pinch guesture, I am able to do the zooming (Normal UIScrollview zooming). But I need to zoom it to a particular point. ie, Suppose i am zooming January, I need to keep it in the same position(for creating an effect like moving inside of January). Any suggestions to achieve this?
Is my way is currect? else please help me to start this.
Use This bellow method for your requirement,
- (void)zoomToRect:(CGRect)rect animated:(BOOL)animated
just call this above method with your selected frame or point , like if select January then just take the point ( position) of january button or view and just call above method with that frame..
for more information see this link UIScrollView_Class
i hope this helpful to you...
Here’s what it looks like:
#implementation UIScrollView (ZoomToPoint)
- (void)zoomToPoint:(CGPoint)zoomPoint withScale: (CGFloat)scale animated: (BOOL)animated
{
//Normalize current content size back to content scale of 1.0f
CGSize contentSize;
contentSize.width = (self.contentSize.width / self.zoomScale);
contentSize.height = (self.contentSize.height / self.zoomScale);
//translate the zoom point to relative to the content rect
zoomPoint.x = (zoomPoint.x / self.bounds.size.width) * contentSize.width;
zoomPoint.y = (zoomPoint.y / self.bounds.size.height) * contentSize.height;
//derive the size of the region to zoom to
CGSize zoomSize;
zoomSize.width = self.bounds.size.width / scale;
zoomSize.height = self.bounds.size.height / scale;
//offset the zoom rect so the actual zoom point is in the middle of the rectangle
CGRect zoomRect;
zoomRect.origin.x = zoomPoint.x - zoomSize.width / 2.0f;
zoomRect.origin.y = zoomPoint.y - zoomSize.height / 2.0f;
zoomRect.size.width = zoomSize.width;
zoomRect.size.height = zoomSize.height;
//apply the resize
[self zoomToRect: zoomRect animated: animated];
}
#end
Looking at it, it should be pretty straightforward to figure out how it works. If you see anything wrong with it, let me know in the comments.
Thanks :)
source: http://www.tim-oliver.com/2012/01/14/zooming-to-a-point-in-uiscrollview/
I had same problem,
I was having a world map image, & i wanted to zoom the map to certain location, India
This is how i fixed it.
I found out the position of India, in terms of CGRect(X,Y) [CGRect(500,300)]
& following code in DidLoad
[self.scrollViewForMap setZoomScale:2.0 animated:YES]; //for zooming
_scrollViewForMap.contentOffset = CGPointMake(500,300); //for location
This solved my problem. :)
- (void)pinchDetected:(UIPinchGestureRecognizer *)gestureRecognizer {
if([gestureRecognizer state] == UIGestureRecognizerStateBegan) {
// Reset the last scale, necessary if there are multiple objects with different scales
lastScale = [gestureRecognizer scale];
}
if ([gestureRecognizer state] == UIGestureRecognizerStateBegan ||
[gestureRecognizer state] == UIGestureRecognizerStateChanged) {
CGFloat currentScale = [[[gestureRecognizer view].layer valueForKeyPath:#"transform.scale"] floatValue];
// Constants to adjust the max/min values of zoom
const CGFloat kMaxScale = 2.2;
const CGFloat kMinScale = 0.64;
CGFloat newScale = 1 - (lastScale - [gestureRecognizer scale]);
newScale = MIN(newScale, kMaxScale / currentScale);
newScale = MAX(newScale, kMinScale / currentScale);
CGAffineTransform transform = CGAffineTransformScale([[gestureRecognizer view] transform], newScale, newScale);
[gestureRecognizer view].transform = transform;
[gestureRecognizer setScale:1.0];
lastScale = [gestureRecognizer scale]; // Store the previous scale factor for the next pinch gesture call
}
}
What worked for me (Swift 4):
func zoomIn(with gestureRecognizer: UIGestureRecognizer?) {
guard let location = gestureRecognizer?.location(in: gestureRecognizer?.view)
let rect = CGRect(x: location.x, y: location.y, width: 0, height: 0)
scrollView.zoom(to: rect, animated: true)
}
Or in other words creating a CGRect with zero width and height will cause the UIScrollView to zoom to the rects origin at maximumZoomScale

How to set minimum and maximum zoom scale using UIPinchGestureRecognizer

I want to zoom in and zoom out an image view and i dont want to use UIScrollView for that.
so for this i used UIPinchGestureRecognizer and here is my code -
[recognizer view].transform = CGAffineTransformScale([[recognizer view] transform], [recognizer scale], [recognizer scale]);
recognizer.scale = 1;
this is working fine for zoom in and zoom out.
But problem is that i want to zoom in and zoom out in specific scale like in UIScrollView we can set the maxZoom and minZoom. i could not found any solution for that, every tutorial about UIPinchGestureRecognizer just describe the same code.
Declare 2 ivars CGFloat __scale and CGFloat __previousScale in the interface of the class that handles the gesture. Set __scale to 1.0 by overriding one of the init functions (make sure to call the super constructor here).
- (void)zoom:(UIPinchGestureRecognizer *)gesture {
NSLog(#"Scale: %f", [gesture scale]);
if ([gesture state] == UIGestureRecognizerStateBegan) {
__previousScale = __scale;
}
CGFloat currentScale = MAX(MIN([gesture scale] * __scale, MAX_SCALE), MIN_SCALE);
CGFloat scaleStep = currentScale / __previousScale;
[self.view setTransform: CGAffineTransformScale(self.view.transform, scaleStep, scaleStep)];
__previousScale = currentScale;
if ([gesture state] == UIGestureRecognizerStateEnded ||
[gesture state] == UIGestureRecognizerStateCancelled ||
[gesture state] == UIGestureRecognizerStateFailed) {
// Gesture can fail (or cancelled?) when the notification and the object is dragged simultaneously
__scale = currentScale;
NSLog(#"Final scale: %f", __scale);
}
}
– (void)handlePinchGesture:(UIPinchGestureRecognizer *)gestureRecognizer
{
if([gestureRecognizer state] == UIGestureRecognizerStateBegan) {
// Reset the last scale, necessary if there are multiple objects with different scales
lastScale = [gestureRecognizer scale];
}
if ([gestureRecognizer state] == UIGestureRecognizerStateBegan ||
[gestureRecognizer state] == UIGestureRecognizerStateChanged) {
CGFloat currentScale = [[[gestureRecognizer view].layer valueForKeyPath:#”transform.scale”] floatValue];
// Constants to adjust the max/min values of zoom
const CGFloat kMaxScale = 2.0;
const CGFloat kMinScale = 1.0;
CGFloat newScale = 1 – (lastScale – [gestureRecognizer scale]); // new scale is in the range (0-1)
newScale = MIN(newScale, kMaxScale / currentScale);
newScale = MAX(newScale, kMinScale / currentScale);
CGAffineTransform transform = CGAffineTransformScale([[gestureRecognizer view] transform], newScale, newScale);
[gestureRecognizer view].transform = transform;
lastScale = [gestureRecognizer scale]; // Store the previous scale factor for the next pinch gesture call
}
}
I had similar situation. My requirement was imageView will bounce back to its last transformation if imageView is smaller than a minimum size or bigger than a certain maximum size.
if ((self.frame.size.width > IMAGE_MIN_SIZE) && (self.frame.size.height > IMAGE_MIN_SIZE) && (self.frame.size.width < IMAGE_MAX_SIZE) && (self.frame.size.height < IMAGE_MAX_SIZE)) {
lastSizeTransform = self.transform;
}else {
self.transform = lastSizeTransform;
}
Here self is the imageView.
If you logged view.transform while you are pinching, you can see your image coordination which zoomed in and out. So, this solutions doesn't work as i expect. I made my solution like that;
Obj-C Version
- (void)handlePinchGesture:(UIPinchGestureRecognizer *)recognizer {
[recognizer.view setTransform:CGAffineTransformScale(recognizer.view.transform,
recognizer.scale, recognizer.scale)];
if (recognizer.view.transform.a > 1.6) {
CGAffineTransform fooTransform = recognizer.view.transform;
fooTransform.a = 1.6; // this is x coordinate
fooTransform.d = 1.6; // this is y coordinate
recognizer.view.transform = fooTransform;
}
if (recognizer.view.transform.a < 0.95) {
CGAffineTransform fooTransform = recognizer.view.transform;
fooTransform.a = 0.95; // this is x coordinate
fooTransform.d = 0.95; // this is y coordinate
recognizer.view.transform = fooTransform;
}
recognizer.scale = 1.0;
}
Swift Version
func handlePinchGesture(recognizer: UIPinchGestureRecognizer) {
if let view = recognizer.view {
view.transform = CGAffineTransformScale(view.transform,
recognizer.scale, recognizer.scale)
if CGFloat(view.transform.a) > 1.6 {
view.transform.a = 1.6 // this is x coordinate
view.transform.d = 1.6 // this is x coordinate
}
if CGFloat(view.transform.d) < 0.95 {
view.transform.a = 0.95 // this is x coordinate
view.transform.d = 0.95 // this is x coordinate
}
recognizer.scale = 1
}
}

iphone : How to rotate a rectangle image with touch events?

transform a square image or round image is easy to do...
But what if a rectangle image ???
This is the image I want to transform with touch event .
The circle center is (30 , 236) , if I touch any place on the screen
Imageview will transform the arrow to my touch point.
But the circle center still the same place .
I try to transform the image use a test angle
It will become like this ...
How to adjust the image ?
Also the code is here
- (void)viewDidLoad {
CGRect arrowImageRect = CGRectMake(20.0f, 20.0f, 15.0f, 220.0f);
arrow = [[UIImageView alloc] initWithFrame:arrowImageRect];
[arrow setImage:[UIImage imageNamed:#"arrow.png"]];
arrow.opaque = YES;
[self.view addSubview:arrow];
[super viewDidLoad];
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
UITouch *touch = [touches anyObject];
CGPoint touchPoint = [touch locationInView:self.view];
NSLog(#"Position X: %f \n, Y: %f",touchPoint.x,touchPoint.y);
CGAffineTransform transform = CGAffineTransformIdentity;
arrow.transform = CGAffineTransformRotate(transform, ?????? );
}
The ?????? part should be the last solution ...
Or Maybe I need to resize the imageview ???
Thanks for any reply or answer :-)
Webber
Here is the final solution I figure this problem
check this code
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch=[[event allTouches]anyObject];
[self transformSpinnerwithTouches:touch];
}
-(void)transformSpinnerwithTouches:(UITouch *)touchLocation
{
CGPoint touchLocationpoint = [touchLocation locationInView:self.view];
CGPoint PrevioustouchLocationpoint = [touchLocation previousLocationInView:self.view];
//origin is the respective piont from that i gonna measure the angle of the current position with respective to previous position ....
CGPoint origin;
origin.x = arrow.center.x;
origin.y = arrow.center.y;
CGPoint previousDifference = [self vectorFromPoint:origin toPoint:PrevioustouchLocationpoint];
CGAffineTransform newTransform = CGAffineTransformScale(arrow.transform, 1, 1);
CGFloat previousRotation = atan2(previousDifference.y, previousDifference.x);
CGPoint currentDifference = [self vectorFromPoint:origin toPoint:touchLocationpoint];
CGFloat currentRotation = atan2(currentDifference.y, currentDifference.x);
CGFloat newAngle = currentRotation- previousRotation;
temp1 = temp1 + newAngle;
//NSLog(#"temp1 is %f",temp1);
//NSLog(#"Angle:%F\n",(temp1*180)/M_PI);
newTransform = CGAffineTransformRotate(newTransform, newAngle);
[self animateView:arrow toPosition:newTransform];
}
-(void)animateView:(UIView *)theView toPosition:(CGAffineTransform) newTransform
{
arrow.transform = newTransform;
}
-(CGPoint)vectorFromPoint:(CGPoint)firstPoint toPoint:(CGPoint)secondPoint
{
CGFloat x = secondPoint.x - firstPoint.x;
CGFloat y = secondPoint.y - firstPoint.y;
CGPoint result = CGPointMake(x, y);
//NSLog(#"%f %f",x,y);
return result;
}
From the documentation about transform method:
The origin of the transform is the value of the center property, or the layer’s anchorPoint property if it was changed. (Use the layer property to get the underlying Core Animation layer object.) The default value is CGAffineTransformIdentity.
So you should be able to set the center of your rotation this way!
And for determining the rotation angle, use the atan2 or atan2f function:
float angleInRadians = atan2f(touchPoint.y-circleCenter.y , touchPoint.x-circleCenter.x);
You can re set the center after transform.
CGPoint centerBeforeTransform = view.center;
//your transform code
view.center = centerBeforeTransform;

UIPinchGestureRecognizer Scale view in different x and y directions

i don't want to use the scale as classic zoom, instead i want to change the form of quadrates to a rectangle for example.
After a lot of trying i'm that far that my fingers are the corners of the rectangle.
So but if i start a new pinch gesture inside my view gets smaller to my fingers instead of getting bigger like the normal scale does.
if ([gestureRecognizer numberOfTouches] >1) {
//getting width and height between gestureCenter and one of my finger
float x = [gestureRecognizer locationInView:self].x - [gestureRecognizer locationOfTouch:0 inView:self].x;
if (x<0) {
x *= -1;
}
float y = [gestureRecognizer locationInView:self].y - [gestureRecognizer locationOfTouch:0 inView:self].y;
if (y<0) {
y *= -1;
}
//double size cause x and y is just the way from the middle to my finger
float width = x*2;
if (width < 1) {
width = 1;
}
float height = y*2;
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];
}
does anyone know a way to make a X-Y-Scale which don't resize to my fingers position as corners.
Thank you very much
Got the solution
- (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];
}
}
added xDif and yDif with the distance of my touch from the side of the view.
after scale i add them to the size