I am doing an app which has a feature to rotate and re size a view. i have implemented this feature but i do face an issue.
My problem
The View wil be resized when dragging its four corners, after resizing it i can rotate the view in both directions.
Once the rotation is done, if i try again to resize the view by dragging its corner, the view's size gone to unpredictable value and its moving all around the screen.
I googled lot finally i got the following solution
The frame property is undefined when transform != CGAffineTransformIdentity, as per the docs on UIView
I saw one app which has implemented the feature exactly what i wish to implement.
How can i resize the UIView after rotation of UIView
My code for resize the view
Touches Began
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
UITouch *touch = [[event allTouches] anyObject];
NSLog(#"[touch view]:::%#",[touch view]);
touchStart = [[touches anyObject] locationInView:testVw];
isResizingLR = (testVw.bounds.size.width - touchStart.x < kResizeThumbSize && testVw.bounds.size.height - touchStart.y < kResizeThumbSize);
isResizingUL = (touchStart.x <kResizeThumbSize && touchStart.y <kResizeThumbSize);
isResizingUR = (testVw.bounds.size.width-touchStart.x < kResizeThumbSize && touchStart.y<kResizeThumbSize);
isResizingLL = (touchStart.x <kResizeThumbSize && testVw.bounds.size.height -touchStart.y <kResizeThumbSize);
}
Touches Moved
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
CGPoint touchPoint = [[touches anyObject] locationInView:testVw];
CGPoint previous=[[touches anyObject]previousLocationInView:testVw];
float deltaWidth = touchPoint.x-previous.x;
float deltaHeight = touchPoint.y-previous.y;
NSLog(#"CVTM:%#",NSStringFromCGRect(testVw.frame));
if (isResizingLR) {
testVw.frame = CGRectMake(testVw.frame.origin.x, testVw.frame.origin.y,touchPoint.x + deltaWidth, touchPoint.y + deltaWidth);
}
if (isResizingUL) {
testVw.frame = CGRectMake(testVw.frame.origin.x + deltaWidth, testVw.frame.origin.y + deltaHeight, testVw.frame.size.width - deltaWidth, testVw.frame.size.height - deltaHeight);
}
if (isResizingUR) {
testVw.frame = CGRectMake(testVw.frame.origin.x ,testVw.frame.origin.y + deltaHeight, testVw.frame.size.width + deltaWidth, testVw.frame.size.height - deltaHeight);
}
if (isResizingLL) {
testVw.frame = CGRectMake(testVw.frame.origin.x + deltaWidth ,testVw.frame.origin.y , testVw.frame.size.width - deltaWidth, testVw.frame.size.height + deltaHeight);
}
if (!isResizingUL && !isResizingLR && !isResizingUR && !isResizingLL) {
testVw.center = CGPointMake(testVw.center.x + touchPoint.x - touchStart.x,testVw.center.y + touchPoint.y - touchStart.y);
}
}
Since you don't use UIView animations you can save current view's transform, set view's transform to identity, resize the view and reapply saved transform:
UIView *v;
CGAffineTransform t = v.transform;
v.transform = CGAffineTransformIdentity;
CGFloat scale = 2.0f;
v.frame = CGRectMake(v.frame.origin.x, v.frame.origin.y, v.frame.size.width*scale , v.frame.size.height*scale);
v.transform = t;
(EDIT)About your resizing:
If you define your rectangle with 4 vectors (points) A,B,C,D where (A+C)*.5 = (B+D)*.5 = rectangle_center Then for moving point C to position C' would also move B and D to B' and D':
A' = A
C' = C' //touch input
B' = A + (normalized(B-A) * dotProduct((C'-A), normalized(B-A)))
D' = A + (normalized(D-A) * dotProduct((C'-A), normalized(D-A)))
After that:
transform your view to identity
set frame to (.0f, .0f, length(A-D), length(A-C))
set center to (A+D)*.5f
get rotation to create view's transform through atanf(height/width) and few "if's" OR fill/create transform with base vectors that are normalized(D-A) and normalized(B-A)
You can do that for any point in a rectangle.
Related
I have an UIButton that I've creates programmatically. Actually it should'n be UIButton, I just need to have possibility to mark some area above the image.
So the features I need it - move object and resize it. For this i have 2 methods:
- (void) objMove:(id) sender withEvent:(UIEvent *) event
{
UIControl *control = sender;
UITouch *t = [[event allTouches] anyObject];
CGPoint pPrev = [t previousLocationInView:control];
CGPoint p = [t locationInView:control];
CGPoint center = control.center;
center.x += p.x - pPrev.x;
center.y += p.y - pPrev.y;
control.center = center;
}
- (void)objScale:(UIPinchGestureRecognizer *)recognizer
{
UIView *pinchView = recognizer.view;
CGRect bounds = pinchView.bounds;
CGPoint pinchCenter = [recognizer locationInView:pinchView];
pinchCenter.x -= CGRectGetMidX(bounds);
pinchCenter.y -= CGRectGetMidY(bounds);
CGAffineTransform transform = pinchView.transform;
transform = CGAffineTransformTranslate(transform, pinchCenter.x, pinchCenter.y);
CGFloat scale = recognizer.scale;
transform = CGAffineTransformScale(transform, scale, scale);
transform = CGAffineTransformTranslate(transform, -pinchCenter.x, -pinchCenter.y);
pinchView.transform = transform;
recognizer.scale = 1.0;
}
Scale works ok. Moving looks ok until I change the size of object - when i increase object it become moves slower than finger, and vice versa - if object smaller than original it moves faster than finger. why it works like this?
I think you should get startPoint and startCenter in
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
// get startPoint and startCenter here
}
- (void) objMove:(id) sender withEvent:(UIEvent *) event
{
UIControl *control = sender;
UITouch *t = [[event allTouches] anyObject];
CGPoint p = [t locationInView:control];
startCenter.x += p.x - startPoint.x;
startCenter.y += p.y - startPoint.y;
control.center = startCenter;
}
Change your code like this, maybe it works.
Your center is current center, p is current point, pPrev is previous point.
current center adds previous point moved size is wrong.
You should get relative distance, not dynamic distance.
I am creating a custom UIControl object as detailed here. It is all working well except for the touch area.
I want to find a way to limit the touch area to only part of the control, in the example above I want it to be restricted to the black circumference only rather than the whole control area.
Any idea?
Cheers
You can override UIView's pointInside:withEvent: to reject unwanted touches.
Here's a method that checks if the touch occurred in a ring around the center of the view:
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
UITouch *touch = [[event touchesForView:self] anyObject];
if (touch == nil)
return NO;
CGPoint touchPoint = [touch locationInView:self];
CGRect bounds = self.bounds;
CGPoint center = { CGRectGetMidX(bounds), CGRectGetMidY(bounds) };
CGVector delta = { touchPoint.x - center.x, touchPoint.y - center.y };
CGFloat squareDistance = delta.dx * delta.dx + delta.dy * delta.dy;
CGFloat outerRadius = bounds.size.width * 0.5;
if (squareDistance > outerRadius * outerRadius)
return NO;
CGFloat innerRadius = outerRadius * 0.5;
if (squareDistance < innerRadius * innerRadius)
return NO;
return YES;
}
To detect other hits on more complex shapes you can use a CGPath to describe the shape and test using CGPathContainsPoint. Another way is to use an image of the control and test the pixel's alpha value.
All that depends on how you build your control.
some applications have this "drag resistance" when you drag an image. The further away you drag the image from the origin point, the less it becomes drag-able.
How can I implement this feature?
How about subclassing UIImageView and doing something like below: (Not perfect , but something along this lines maybe?)
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
CGPoint currentPosition = [[touches anyObject] locationInView:self.superview];
CGFloat dx = currentPosition.x - initialCentre.x;//delta x with sign
CGFloat dy = currentPosition.y - initialCentre.y;//delta y with sign
CGFloat distance = sqrt(dx*dx + dy*dy);//distance
CGFloat weightedX = initialCentre.x + dx * (1/log(distance)); //new x as inverse function of distance
CGFloat weightedY = initialCentre.y + dy * (1/log(distance)); //new y as inverse function of distance
self.center = CGPointMake(weightedX, weightedY);//set the center
}
I have one base view A with size of (0,0,320,280), it contain another view B with size of
(100,100,50,50) and View B has one button and one image view(C) as sub view. image view frame is same as view B, button has added in B's top left corner.
My requirement is when we drag the B's bottom right corner its size has to increased or decreased.
if we drag the B from any other place except bottom right corner it has to move. view size should not be modified.
My problem is view B does not receive the touch action.
i added the code below. please guide me.
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [[event allTouches] anyObject];
//baseVw-------> view B//
if ([touch view]==baseVw)
{
touchStart = [[touches anyObject] locationInView:baseVw];
isResizingLR = (baseVw.bounds.size.width - touchStart.x < kResizeThumbSize && baseVw.bounds.size.height - touchStart.y < kResizeThumbSize);
isResizingUL = (touchStart.x <kResizeThumbSize && touchStart.y <kResizeThumbSize);
isResizingUR = (baseVw.bounds.size.width-touchStart.x < kResizeThumbSize && touchStart.y<kResizeThumbSize);
isResizingLL = (touchStart.x <kResizeThumbSize && baseVw.bounds.size.height -touchStart.y <kResizeThumbSize);
}
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [[event allTouches] anyObject];
CGPoint touchPoint = [[touches anyObject] locationInView:baseVw];
CGPoint previous=[[touches anyObject]previousLocationInView:baseVw];
float deltaWidth = touchPoint.x-previous.x;
float deltaHeight = touchPoint.y-previous.y;
if ([touch view]==baseVw)
{
if (isResizingLR)
{
baseVw.frame = CGRectMake(baseVw.frame.origin.x, baseVw.frame.origin.y,touchPoint.x + deltaWidth, touchPoint.y + deltaWidth);
}
else
{
CGPoint activePoint = [[touches anyObject] locationInView:baseVw];
// Determine new point based on where the touch is now located
CGPoint newPoint = CGPointMake(baseVw.center.x + (activePoint.x - touchStart.x),
baseVw.center.y + (activePoint.y - touchStart.y));
//--------------------------------------------------------
// Make sure we stay within the bounds of the parent view
//--------------------------------------------------------
float midPointX = CGRectGetMidX(baseVw.bounds);
// If too far right...
if (newPoint.x > baseVw.superview.bounds.size.width - midPointX)
newPoint.x = baseVw.superview.bounds.size.width - midPointX;
else if (newPoint.x < midPointX) // If too far left...
newPoint.x = midPointX;
float midPointY = CGRectGetMidY(baseVw.bounds);
// If too far down...
if (newPoint.y > baseVw.superview.bounds.size.height - midPointY)
newPoint.y = baseVw.superview.bounds.size.height - midPointY;
else if (newPoint.y < midPointY) // If too far up...
newPoint.y = midPointY;
// Set new center location
baseVw.center = newPoint;
}
}
}
View B probably does not receive any touch events because these are absorbed by the subview C. Assuming you implemented the touch methods in B you should also make sure that you set B as the firstResponder. Implement this:
-(BOOL)canBecomeFirstResponder
{
return YES;
}
And then call [self becomeFirstResponder]; after you add C as the subview. The code inside the touchesBegan/Moved doesn't matter if they are not being called (touch events not received).
I have one UIImageView having an image of an arrow. When user taps on the UIView this arrow should point to the direction of the tap maintaing its position it should just change the transform. I have implemented following code. But it not working as expected. I have added a screenshot. In this screenshot when i touch the point upper left the arrow direction should be as shown.But it is not happening so.
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
UITouch *touch=[[event allTouches]anyObject];
touchedPoint= [touch locationInView:touch.view];
imageViews.transform = CGAffineTransformMakeRotation(DEGREES_TO_RADIANS(rangle11));
previousTouchedPoint = touchedPoint ;
}
- (CGFloat) pointPairToBearingDegrees:(CGPoint)startingPoint secondPoint:(CGPoint) endingPoint
{
CGPoint originPoint = CGPointMake(endingPoint.x - startingPoint.x, endingPoint.y - startingPoint.y); // get origin point to origin by subtracting end from start
float bearingRadians = atan2f(originPoint.y, originPoint.x); // get bearing in radians
float bearingDegrees = bearingRadians * (180.0 / M_PI); // convert to degrees
bearingDegrees = (bearingDegrees > 0.0 ? bearingDegrees : (360.0 + bearingDegrees)); // correct discontinuity
return bearingDegrees;
}
I assume you wanted an arrow image to point to where ever you touch, I tried and this is what i could come up with. I put an image view with an arrow pointing upwards (haven't tried starting from any other position, log gives correct angles) and on touching on different locations it rotates and points to touched location. Hope it helps ( tried some old math :-) )
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
UITouch *touch=[[event allTouches]anyObject];
touchedPoint= [touch locationInView:touch.view];
CGFloat angle = [self getAngle:touchedPoint];
imageView.transform = CGAffineTransformMakeRotation(angle);
}
-(CGFloat) getAngle: (CGPoint) touchedPoints
{
CGFloat x1 = imageView.center.x;
CGFloat y1 = imageView.center.y;
CGFloat x2 = touchedPoints.x;
CGFloat y2 = touchedPoints.y;
CGFloat x3 = x1;
CGFloat y3 = y2;
CGFloat oppSide = sqrtf(((x2-x3)*(x2-x3)) + ((y2-y3)*(y2-y3)));
CGFloat adjSide = sqrtf(((x1-x3)*(x1-x3)) + ((y1-y3)*(y1-y3)));
CGFloat angle = atanf(oppSide/adjSide);
// Quadrant Identifiaction
if(x2 < imageView.center.x)
{
angle = 0-angle;
}
if(y2 > imageView.center.y)
{
angle = M_PI/2 + (M_PI/2 -angle);
}
NSLog(#"Angle is %2f",angle*180/M_PI);
return angle;
}
-anoop4real
Given what you told me, I think the problem is that you are not resetting your transform in touchesBegan. Try changing it to something like this and see if it works better:
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
UITouch *touch=[[event allTouches]anyObject];
touchedPoint= [touch locationInView:touch.view];
imageViews.transform = CGAffineTransformIdentity;
imageViews.transform = CGAffineTransformMakeRotation(DEGREES_TO_RADIANS(rangle11));
previousTouchedPoint = touchedPoint ;
}
Do you need the line to "remove the discontinuity"? Seems atan2f() returns values between +π to -π. Won't those work directly with CATransform3DMakeRotation()?
What you need is that the arrow points to the last tapped point. To simplify and test, I have used a tap gesture (but it's similar to a touchBegan:withEvent:).
In the viewDidLoad method, I register the gesture :
UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(tapped:)];
[self.view addGestureRecognizer:tapGesture];
[tapGesture release];
The method called on each tap :
- (void)tapped:(UITapGestureRecognizer *)gesture
{
CGPoint imageCenter = mFlecheImageView.center;
CGPoint tapPoint = [gesture locationInView:self.view];
double deltaY = tapPoint.y - imageCenter.y;
double deltaX = tapPoint.x - imageCenter.x;
double angleInRadians = atan2(deltaY, deltaX) + M_PI_2;
mFlecheImageView.transform = CGAffineTransformMakeRotation(angleInRadians);
}
One key is the + M_PI_2 because UIKit coordinates have the origin at the top left corner (while in trigonometric, we use a bottom left corner).