I have a circular image which I am trying to rotate, so that the yellow and black striped circle stays under the user's finger and rotates in both directions. I have this so far:
- (void)handleJogShuttle:(UIPanGestureRecognizer *)recognizer {
UIView *shuttle = [recognizer view];
if ([recognizer state] == UIGestureRecognizerStateBegan ||
[recognizer state] == UIGestureRecognizerStateChanged) {
[recognizer view].transform = CGAffineTransformRotate([[recognizer view] transform],M_PI / 20.0f);
[recognizer setTranslation:CGPointZero inView:[shuttle superview]];
}
}
Also, currently, the slightest movement can cause the view to rotate in a full circle, which is obviously not desired.
#import "RotationTestViewController.h"
#interface RotationTestViewController()
-(void)transformSpinnerwithTouches:(UITouch *)touchLocation;
-(CGPoint)vectorFromPoint:(CGPoint)firstPoint toPoint:(CGPoint)secondPoint;
-(void)animateView:(UIView *)theView toPosition:(CGAffineTransform) newTransform;
#end
#implementation RotationTestViewController
- (void)viewDidLoad {
[super viewDidLoad];}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
for (UITouch *touch in touches)
{
// Send to the dispatch method, which will make sure the appropriate subview is acted upon
// currentTouch=touch;
}
}
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
for (UITouch *touch in touches){
// Sends to the dispatch method, which will make sure the appropriate subview is acted upon
}
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch=[[event allTouches]anyObject];
if (touch.view==Spinner)
{
[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=Spinner.center.x;
origin.y=Spinner.center.y;
CGPoint previousDifference = [self vectorFromPoint:origin toPoint:PrevioustouchLocationpoint];
CGAffineTransform newTransform =CGAffineTransformScale(Spinner.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(#"Angle:%F\n",(temp1*180)/M_PI);
newTransform = CGAffineTransformRotate(newTransform, newAngle);
[self animateView:Spinner toPosition:newTransform];
}
-(void)animateView:(UIView *)theView toPosition:(CGAffineTransform) newTransform
{
[UIView setAnimationsEnabled:YES];
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationCurve:UIViewAnimationCurveLinear];
[UIView setAnimationBeginsFromCurrentState:YES];
[UIView setAnimationDuration:0.0750];
Spinner.transform = newTransform;
[UIView commitAnimations];
}
//-(CGFloat)distanceFromPoint:(CGPoint)fromPoint toPoint:(CGPoint)toPoint
//
//{
//float x = toPoint.x - fromPoint.x;
//float y = toPoint.y - fromPoint.y;
//return hypot(x, y);
//}
-(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;
}
-(void) dealloc
{
[Spinner release];
[super dealloc];
}
I suspect this method gets called A LOT. Try using NSLog and check how many time it does.
Anyway, you are not calculating the angle properly, you could try using
-(CGPoint)translationInView:(UIView *)view
from the recognizer to calculate the correct angle to rotate.
Related
I want to draw a straight line with my finger that automatically sizes based upon how far away I am from the point of origin.
So if I touch the screen in the middle and slide my finger out a line appears to 'stretch' and pivot around the point of orgin as my finger moves on the screen. WHhen I lift my finger. The Destination Point should finalize and create a line. I can drag my finger across the screen and 'Draw' on the screen but that's not what I am wanting to do.
I thought UIBeizerPath moveToPoint would help but it just messes things up.
What am I doing wrong?
- (id)initWithFrame:(CGRect)frame
{
//default line properties
myPath=[[UIBezierPath alloc]init];
myPath.lineCapStyle=kCGLineCapRound;
myPath.miterLimit=0;
myPath.lineWidth=lineWidth;
brushPattern=[UIColor blackColor];
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
CGPoint curPoint = [[touches anyObject] locationInView:self];
lastPoint = curPoint;
[myPath moveToPoint:lastPoint];
[pathArray addObject:myPath];
[self setNeedsDisplay];
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
CGPoint curPoint = [[touches anyObject] locationInView:self];
myPath.lineWidth=lineWidth;
brushPattern=[UIColor redColor]; //red to show it hasn't been added yet.
[myPath moveToPoint:tempPoint];
[self setNeedsDisplay];
}
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
CGPoint curPoint = [[touches anyObject] locationInView:self];
myPath.lineWidth=lineWidth;
brushPattern=[UIColor blackColor]; //finalize the line with black color
[myPath addLineToPoint:curPoint];
[self setNeedsDisplay];
}
Here's one concept. Draws a line from where you start dragging your finger until where you let go, animating it as you drag your finger around. It does this by making a CAShapeLayer, resetting the path as you move your finger around.
This should demonstrate the basic idea:
- (void)viewDidLoad {
[super viewDidLoad];
UIPanGestureRecognizer *gesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(handlePanGesture:)];
[self.view addGestureRecognizer:gesture];
}
- (CAShapeLayer *)createShapeLayer:(UIView *)view {
CAShapeLayer *shapeLayer = [[CAShapeLayer alloc] init];
shapeLayer.fillColor = [UIColor clearColor].CGColor;
shapeLayer.strokeColor = [UIColor redColor].CGColor;
shapeLayer.lineWidth = 3.0;
[view.layer addSublayer:shapeLayer];
return shapeLayer;
}
- (void)handlePanGesture:(UIPanGestureRecognizer *)gesture {
static CAShapeLayer *shapeLayer;
static CGPoint origin;
if (gesture.state == UIGestureRecognizerStateBegan) {
shapeLayer = [self createShapeLayer:gesture.view];
origin = [gesture locationInView:gesture.view];
} else if (gesture.state == UIGestureRecognizerStateChanged) {
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:origin];
CGPoint location = [gesture locationInView:gesture.view];
[path addLineToPoint:location];
shapeLayer.path = path.CGPath;
} else if (gesture.state == UIGestureRecognizerStateEnded ||
gesture.state == UIGestureRecognizerStateFailed ||
gesture.state == UIGestureRecognizerStateCancelled) {
shapeLayer = nil;
}
}
Or, in Swift 3:
override func viewDidLoad() {
super.viewDidLoad()
let pan = UIPanGestureRecognizer(target: self, action: #selector(handlePan(_:)))
view.addGestureRecognizer(pan)
}
private func createShapeLayer(for view: UIView) -> CAShapeLayer {
let shapeLayer = CAShapeLayer()
shapeLayer.fillColor = UIColor.clear.cgColor
shapeLayer.strokeColor = UIColor.red.cgColor
shapeLayer.lineWidth = 3.0
view.layer.addSublayer(shapeLayer)
return shapeLayer
}
private var shapeLayer: CAShapeLayer!
private var origin: CGPoint!
func handlePan(_ gesture: UIPanGestureRecognizer) {
if gesture.state == .began {
shapeLayer = createShapeLayer(for: gesture.view!)
origin = gesture.location(in: gesture.view)
} else if gesture.state == .changed {
let path = UIBezierPath()
path.move(to: origin)
path.addLine(to: gesture.location(in: gesture.view))
shapeLayer.path = path.cgPath
} else if gesture.state == .ended || gesture.state == .failed || gesture.state == .cancelled {
shapeLayer = nil
}
}
If you don't use CAShapeLayer, but you want to keep track of previous paths, you'll have to maintain an array for those old paths, and build a path that consists of all of the old paths, perhaps something like:
#interface CustomView ()
#property (nonatomic) CGPoint originPoint;
#property (nonatomic) CGPoint currentPoint;
#property (nonatomic) NSMutableArray *previousPaths;
#end
#implementation CustomView
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
[self configure];
}
return self;
}
- (id)init {
return [self initWithFrame:CGRectZero];
}
- (id)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
if (self) {
[self configure];
}
return self;
}
- (void)configure {
_previousPaths = [[NSMutableArray alloc] init];
}
- (void)drawRect:(CGRect)rect {
[[UIColor redColor] setStroke];
UIBezierPath *drawPath = [UIBezierPath bezierPath];
drawPath.lineCapStyle = kCGLineCapRound;
drawPath.miterLimit = 0;
drawPath.lineWidth = 3.0;
for (UIBezierPath *path in self.previousPaths)
[drawPath appendPath:path];
UIBezierPath *path = [self pathForCurrentLine];
if (path)
[drawPath appendPath:path];
[drawPath stroke];
}
- (UIBezierPath *)pathForCurrentLine {
if (CGPointEqualToPoint(self.originPoint, CGPointZero) && CGPointEqualToPoint(self.currentPoint, CGPointZero))
return nil;
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:self.originPoint];
[path addLineToPoint:self.currentPoint];
return path;
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
self.originPoint = [[touches anyObject] locationInView:self];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
if ([event respondsToSelector:#selector(predictedTouchesForTouch:)]) {
touch = [[event predictedTouchesForTouch:touch] lastObject] ?: touch;
}
self.currentPoint = [touch locationInView:self];
[self setNeedsDisplay];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
self.currentPoint = [[touches anyObject] locationInView:self];
[self.previousPaths addObject:[self pathForCurrentLine]];
self.originPoint = self.currentPoint = CGPointZero;
[self setNeedsDisplay];
}
#end
UIBezierPath is building a path from your instructions. Imagine a pen. When you say, "moveToPoint:" it moves the pen to that point. When you say "lineToPoint:" it puts the pen down and moves it from the current location to the new point. And so on.
To get the effect you desire, you will need to create a new path whenever the touches move, drawing a line from the original point to the current touch position.
//To Drag the image View on the iphone screen
- (void) touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [[event allTouches] anyObject];
point0 = [touch locationInView:self.view];
if ([touch tapCount] == 2) {
dropDownBool = NO;
pushUpBool = NO;
self.imageView.alpha = 0.4;
doublePressed = YES;
}
if(doublePressed)
{
point0 = [touch locationInView:self.view];
if (CGRectContainsPoint([self.imageView frame], [touch locationInView:[touch view]]))
{
self.imageView.center = [touch locationInView:nil];
}
}
}
- (void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [[event allTouches] anyObject];
point0 = [touch locationInView:self.imageView];
if(doublePressed)
{
UITouch *touch = [[event allTouches] anyObject];
point0 = [touch locationInView:self.imageView];
//animating the view...
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:0.5];
CGAffineTransform transform =CGAffineTransformTranslate(CGAffineTransformIdentity,0.0f, 0.0f);
self.imageView.transform = transform;
[UIView commitAnimations];
doublePressed = NO;
self.imageView.alpha = 1.0;
}
}
// To drop down a view as you swipe on top of the screen
-(void)handleSwipeGesture:(UISwipeGestureRecognizer *) sender
{
//Gesture detect - swipe up/down , can be recognized direction
if(sender.direction == UISwipeGestureRecognizerDirectionUp)
{
pushUpBool = YES;
}
else
if(sender.direction == UISwipeGestureRecognizerDirectionDown)
{
dropDownBool = YES;
}
if(dropDownBool && (point0.y < 80.0f))
{
NSLog(#"%f",point0.y);
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:0.25];
self.dropDownView.center = CGPointMake(originalCenter.x,30.0 );
self.dropDownView.alpha = 0.8;
dropDownBool = NO;
}
if(pushUpBool && (point0.y < 80.0f))
{
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:0.25];
pushUpBool = NO;
self.dropDownView.center = originalCenter;
self.dropDownView.alpha = 0.0;
}
}
// animates from left to right like Marqee...
-(void) scrollLeft
{
CABasicAnimation *scrollText;
scrollText=[CABasicAnimation animationWithKeyPath:#"position.x"];
scrollText.duration = 6.0;
scrollText.repeatCount = 10000;
scrollText.autoreverses = NO;
scrollText.fromValue = [NSNumber numberWithFloat:500];
scrollText.toValue = [NSNumber numberWithFloat:-120.0];
[[self.detailView layer] addAnimation:scrollText forKey:#"scrollTextKey"];
}
// Making the table View scroll horizontally place this code in viewDidLoad.
CGAffineTransform transform = CGAffineTransformMakeRotation(-1.5707963);
self.horizontalTableView.transform = transform;
// now in tableview method transform the cell back.
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
CGAffineTransform transform = CGAffineTransformMakeRotation(1.5707963);
cell.transform = transform;
}
Using the above Methods one can transform the table view look like a horizontal scroll view. and you can drag the image view by double clicking on the image and moving it across the screen.
if you have any question please feel free.
I am creating a zoomable and pan-able map for my game (using CCPanZoomController) . Within this map I would like to have a tappable sprite, and when it is tapped, "do something"…
I can get the two things to work perfectly in separate projects, however when I try to combine them, nothing happens when I tap my sprite.
I have included an image to demonstrate further:
//in my init section
self.isTouchEnabled = YES;
mapBase = [CCSprite spriteWithFile:#"MapBase.png"];
mapBase.anchorPoint = ccp(0, 0);
[self addChild:mapBase z:-10];
gym = [CCSprite spriteWithFile:#"Gym.png"];
gym.scale = 0.3;
gym.position = ccp(1620, 250);
[self addChild:gym z:1];
CGRect boundingRect = CGRectMake(0, 0, 2499, 1753);
_controller = [[CCPanZoomController controllerWithNode:self] retain];
_controller.boundingRect = boundingRect;
_controller.zoomOutLimit = _controller.optimalZoomOutLimit;
_controller.zoomInLimit = 2.0f;
[_controller enableWithTouchPriority:1 swallowsTouches:YES];
//end of init
-(void) registerWithTouchDispatcher
{
[[[CCDirector sharedDirector] touchDispatcher] addTargetedDelegate:self
priority:0 swallowsTouches:NO];
}
-(BOOL) ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event{
CGPoint touchPoint1 = [touch locationInView:[touch view]];
if (CGRectContainsPoint(gym.boundingBox, touchPoint1)) return YES;
return NO;
}
-(void) ccTouchEnded:(UITouch *)touch withEvent:(UIEvent *)event{
CGPoint touchPoint2 = [touch locationInView:[touch view]];
if (CGRectContainsPoint(gym.boundingBox, touchPoint2)){
CCLOG(#"SPRITE HAS BEEN TAPPED");
}
}
I want to beable to zoom in/out and pan the wholemap (including the ‘Gym’ sprite).
And if the sprite 'Gym' is tapped by the user, then i would like to “do something”.
If anyone can figure this out, I would be extremely grateful!
Thanks.
I'm quite new to Objective-C and am looking to create a fairly simple Jigsaw. I'm currently just looking at making a 4-piece puzzle as a way of learning the method.
At the moment I have 4 pieces placed around the screen that when dragged to a specific place on screen, snap to that place.
My problem is trying to animate the touches. I want the image touched to expand a little but i'm having trouble with the syntax. I've looked at the MoveMe example that Apple provide but it's not helping me in this situation. Below is the code. I'd appreciate any help with this. Thanks.
-(void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
//location of current touch
CGPoint location = [touch locationInView:self.view];
if ([touch view] == image1) {
image1.center = location;
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:0.5];
image1.transform = CGAffineTransformMakeScale(1.5, 1.5);
[UIView commitAnimations];
} else if ([touch view] == image2) {
image2.center = location;
animateFirstTouch:image2;
} else if ([touch view] == image3) {
image3.center = location;
image3.transform = CGAffineTransformMakeScale(1.5, 1.5);
} else if ([touch view] == image4) {
image4.center = location;
image4.transform = CGAffineTransformMakeScale(1.5, 1.5);
}
}
-(void) animateFirstTouch:(UIView *)image {
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:0.5];
self.view.transform = CGAffineTransformMakeScale(1.5, 1.5);
[UIView commitAnimations];
}
The code for image1 works, and the image is scaled up over 0.5 seconds.
The code for image2 throws up an error: expression result unused for animateFirstTouch:image2.
The code for image3 and image4 scales up the images without the animation.
Refer to Kuldeep's answer from this link. I think that would work for you:
iPhone/iOS: How to implement Drag and Drop for subviews of a Scrollview?
Also as an alternative refer to iPhone's answer in this link:
iPhone App: implementation of Drag and drop images in UIView
-(void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
//location of current touch
CGPoint location = [touch locationInView:self.view];
if ([touch view] == image1) {
[self animateFirstTouch:image1 withLocation:location];
} else if ([touch view] == image2) {
[self animateFirstTouch:image2 withLocation:location];
} else if ([touch view] == image3) {
[self animateFirstTouch:image3 withLocation:location];
} else if ([touch view] == image4) {
[self animateFirstTouch:image4 withLocation:location];
}
}
-(void) animateFirstTouch:(UIImageView *)image withLocation:(CGPoint)location {
image.center = location;
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:0.5];
image.transform = CGAffineTransformMakeScale(1.5, 1.5);
[UIView commitAnimations];
}
Hope this helps you.
Contact me if you need more help.
I want to create a little app that takes two images and i want to make only the image over draggable.
After research, i found this solution:
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [[ event allTouches] anyObject];
image.alpha = 0.7;
if([touch view] == image){
CGPoint location = [touch locationInView:self.view];
image.center = location;
}
It works but the problem is that the image is draggable from its center and I don't want that.
So I found another solution:
- (void) touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event {
// Retrieve the touch point
CGPoint pt = [[touches anyObject] locationInView:self.view];
startLocation = pt;
[[self view] bringSubviewToFront:self.view];
}
- (void) touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event {
// Move relative to the original touch point
CGPoint pt = [[touches anyObject] locationInView:self.view];
frame = [self.view frame];
frame.origin.x += pt.x - startLocation.x;
frame.origin.y += pt.y - startLocation.y;
[self.view setFrame:frame];
}
It works very well but when I add another image, all the images of the view are draggable at the same time. I'm a beginner with the iPhone development and I have no idea of how I can only make the image over draggable.
You can use if condition in the touchesBegan: method, See this-
-(void)ViewDidLoad
{
//Your First Image View
UIImageView *imageView1 = [UIImageView alloc]initWithFrame:CGRectMake(150.0, 100.0, 30.0, 30.0)];
[imageView1 setImage:[UIImage imageNamed:#"anyImage.png"]];
[imageView1 setUserInteractionEnabled:YES];
[self.view addSubview:imageView1];
//Your Second Image View
UIImageView *imageView2 = [UIImageView alloc]initWithFrame:CGRectMake(150.0, 200.0, 30.0, 30.0)];
[imageView2 setImage:[UIImage imageNamed:#"anyImage.png"]];
[imageView2 setUserInteractionEnabled:YES];
[self.view addSubview:imageView2];
}
//Now Use Touch begin methods with condition like this
- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [[event allTouches]anyObject];
if([touch view] == imageView1)
{
CGPoint pt = [[touches anyObject] locationInView:imageView1];
startLocation = pt;
}
if([touch view] == imageView2)
{
CGPoint pt = [[touches anyObject] locationInView:imageView2];
startLocation = pt;
}
}
//Use same thing with touch move methods...
- (void) touchesMoved:(NSSet *)touches withEvent: (UIEvent *)event
{
UITouch *touch = [[event allTouches]anyObject];
if([touch view] == imageView1)
{
CGPoint pt = [[touches anyObject] previousLocationInView:imageView1];
CGFloat dx = pt.x - startLocation.x;
CGFloat dy = pt.y - startLocation.y;
CGPoint newCenter = CGPointMake(imageView1.center.x + dx, imageView1.center.y + dy);
imageView1.center = newCenter;
}
if([touch view] == imageView2)
{
CGPoint pt = [[touches anyObject] previousLocationInView:imageView2];
CGFloat dx = pt.x - startLocation.x;
CGFloat dy = pt.y - startLocation.y;
CGPoint newCenter = CGPointMake(imageView2.center.x + dx, imageView2.center.y + dy);
imageView2.center = newCenter;
}
}
These two images will move only when you touch on it and move it. I hope my tutorials will help you.
Simply use these two methods
-(void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [[event allTouches] anyObject];
CGPoint location = [touch locationInView:touch.view];
myImage.center = location;
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
[self touchesBegan:touches withEvent:event];
}
Let's break it down. You should have a main containerView (object of UIView) in your view controller. Within that, you should have DragableImageView objects.
DragableImageView inherits UIImageView and within that, you add your touches code. (Right now you are doing self.frame = position which is wrong - and obviously it will move all images at once - because if I understand correctly, you are adding these touches in your view controller).
Create only DragableImageView first, test it. Then add two DragableImageView instances. test them in different configurations.. you might want to hit test in the containerView to see which DragableImageView should be dragged (e.g. if you have jigsaw pieces, you might want to pick the one that is at the back rather than the front one).