How to Rotate UIButton by tap and hold (hold - rotate by finger) - objective-c

I'm noob in ios development. I need some help. I have custom UIButton with picture "arrow", so I need to rotate this button by pressing and moving finger in +360 gr. and -360 gr., like compass arrow.

Here is code that makes rotation.
-(void)LongPress:(UILongPressGestureRecognizer *)gesture {
CGPoint p = [gesture locationInView:self.view];
CGPoint zero;
zero.x = self.view.bounds.size.width / 2.0;
zero.y = self.view.bounds.size.height / 2.0;
CGPoint newPoint;
newPoint.x = p.x - zero.x;
newPoint.y = zero.y - p.y;
CGFloat angle;
angle = atan2(newPoint.x, newPoint.y);
self.myButton.transform = CGAffineTransformRotate(CGAffineTransformIdentity, angle);
}

In detail, you can custom a rotateView,then:
1: In the delegate method of "touchesBegan", get initialPoint of finger and initialAngle.
2: During "touchesMoved",get the newPoint of finger:
CGPoint newPoint = [[touches anyObject] locationInView:self];
[self pushTouchPoint:thePoint date:[NSDate date]];
double angleDif = [self angleForPoint:newPoint] - [self angleForPoint:initialPoint];
self.angle = initialAngle + angleDif;
[[imageView layer] setTransform:CATransform3DMakeRotation(angle, 0, 0, 1)];
3: At last, in "touchesEnded" you can calculate final AngularVelocity.
If anything being confused, for more detail, you can write back.

Related

How to drag a CALayer that is rotated and scaled?

I have an image in a CALayer (using the CALayers contents property). A CALayer has a transform property that can be use to scale and rotate. I can use that transform without difficulty when the CALayer is still.
But when I try to drag the CALayer (using mouseDragged:(NSEvent *)theEvent), then it all fells apart (the image gets flattened out as I drag).
-(void)mouseDragged:(NSEvent *)theEvent {
CGPoint loc = [self convertPoint:theEvent.locationInWindow fromView:nil];
CGPoint deltaLoc = ccpSub(loc, downLoc); // subtract two points
CGPoint newPos = ccpAdd(startPos, deltaLoc); // adds two points
[layer changePosition:newPos];
...
}
In the layer
-(void)changePosition:(CGPoint)newPos {
//prevent unintended animation actions
self.actions = #{#"position": [NSNull null]};
CGSize size = self.frame.size;
CGRect newRect = CGRectMake(newPos.x, newPos.y, size.width, size.height);
self.frame = newRect;
}
The problem is that I use CALayer's frame to dynamically change the location of the CALayer as I drag, but when the rotation angle is not zero, then the frame is not longer parallel to x and y axis. What is the best way to fix this problem?
One way to do it is to regenerate transforms for each incremental movement.
-(void)mouseDragged:(CGPoint)deltaLoc start:(CGPoint)startPos {
CGPoint newPos = ccpAdd(startPos, deltaLoc);
[self changePosition:newPos];
}
-(void)changePosition:(CGPoint)newPos {
//prevent unintended animation actions
self.actions = #{#"position": [NSNull null]};
double angle = [[self valueForKeyPath: #"transform.rotation"] doubleValue];
double scaleX = [[self valueForKeyPath: #"transform.scale.x"] doubleValue];
double scaleY = [[self valueForKeyPath: #"transform.scale.y"] doubleValue];
self.transform = CATransform3DIdentity;
CGSize size = self.frame.size;
CGRect newRect = CGRectMake(newPos.x, newPos.y, size.width, size.height);
self.frame = newRect;
[self applyTransformRotation:angle scale:ccp(scaleX, scaleY)];
}
-(void)applyTransformRotation:(double)angle scale:(CGPoint)scale {
self.transform = CATransform3DMakeRotation(angle, 0, 0, 1);
self.transform = CATransform3DScale(self.transform, scale.x, scale.y, 1);
}
I do not know if this is the most efficient way to do it, but it definitely works.

Slightly confused with coordinates

Hi I have such code:
- (void)viewDidLoad
{
[super viewDidLoad];
// Get view bounds
CGRect bounds = [self.view bounds];
// Get center point
CGPoint center;
center.x = bounds.origin.x + bounds.size.width/2.0;
center.y = bounds.origin.y + bounds.size.height/2.0;
NSLog(#"center y = %f", center.y);
}
The above log initially prints 274.0
Then I have this method:
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)x
duration:(NSTimeInterval)duration
{
CGRect bounds = [[self view] bounds];
// Get center point
CGPoint center;
center.x = bounds.origin.x + bounds.size.width/2.0;
center.y = bounds.origin.y + bounds.size.height/2.0;
// If the orientation is rotating to Portrait mode...
if (UIInterfaceOrientationIsPortrait(x))
{
NSLog(#"In rotation, center y = %f", center.y);
}
}
This center.y in above method is calculated in the same way as the one in viewDidLoad as you might notice. However, when phone is rotated first to landscape then back to portrait as initially, now the center.y in the above method always prints 230 (instead of 274.0 as initially) -- why?
What can be causing this?
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation{
CGRect bounds = [[self view] bounds];
CGPoint center;
center.x = bounds.origin.x + bounds.size.width/2.0;
center.y = bounds.origin.y + bounds.size.height/2.0;
if (UIInterfaceOrientationIsLandscape(fromInterfaceOrientation)){
NSLog(#"In rotation, center y = %f", center.y);
}
}

Why is the touchesMoved method not being called more than once when a view is being dragged--IOS

here is the panGesture's instance.
pangstr = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(handlePan:)];
- (void)handlePan:(UIPanGestureRecognizer *)recognizer{
CGPoint translation = [recognizer translationInView:baseView];
recognizer.view.center = CGPointMake(recognizer.view.center.x + translation.x, recognizer.view.center.y);
[recognizer setTranslation:CGPointMake(0, 0) inView:baseView];
if (recognizer.state == UIGestureRecognizerStateEnded) {
CGPoint velocity = [recognizer velocityInView:baseView];
CGFloat magnitude = sqrtf((velocity.x * velocity.x));
CGFloat slideMult = magnitude / 1000;
NSLog(#"magnitude: %f, slideMult: %f", magnitude, slideMult);
float slideFactor = 0.15 * slideMult;
CGPoint finalPoint = CGPointMake(recognizer.view.center.x + (velocity.x * slideFactor),
recognizer.view.center.y + (velocity.y * slideFactor));
}
Now my question is how can i use the (panGestureRecogniser *)recogniser parameter in
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event function.
I want this specifically because i wanna get the finalPoint's value in my TouchesMoved function and CGPoint finalPoint uses the *recogniser parameter.
It would be really great if anyone could give me a solution
Thanks
Why not just used the recognisers state?
if (recognizer.state == UIGestureRecognizerStateChanged)
{
//Save whatever information you need here
}
With regard to shared variables, use a different one for StateChanged and sync them up in StateEnded?

Limit UIPanGestureRecognizer to 1 object at 1 time in array

How do i set the panning off when the other object is currently using it?
Eg, when i'm panning on 1 object, the other object should not move. Currently i'm able to pan 2 object.
Another way is to set the panning min touch to 2, which i don't want.
My main code:
UIPanGestureRecognizer *imagePanGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(moveImage:)];
imagePanGesture.delegate = self;
[tempImageView addGestureRecognizer:imagePanGesture];
[currentImageArray addObject:tempImageView];
- (void)moveImage:(UIPanGestureRecognizer *)recognizer
{
CGPoint translation = [recognizer translationInView:self.view];
recognizer.view.center = CGPointMake(recognizer.view.center.x + translation.x,
recognizer.view.center.y + translation.y);
[recognizer setTranslation:CGPointMake(0, 0) inView:self.view];
if (recognizer.state == UIGestureRecognizerStateEnded)
{
CGPoint velocity = [recognizer velocityInView:self.view];
CGFloat magnitude = sqrtf((velocity.x * velocity.x) + (velocity.y * velocity.y));
CGFloat slideMult = magnitude / 200;
float slideFactor = 0.1 * slideMult; // Increase for more of a slide
CGPoint finalPoint = CGPointMake(recognizer.view.center.x + (velocity.x * slideFactor),
recognizer.view.center.y + (velocity.y * slideFactor));
finalPoint.x = MIN(MAX(finalPoint.x, 0), self.view.bounds.size.width);
finalPoint.y = MIN(MAX(finalPoint.y, 0), self.view.bounds.size.height);
[UIView animateWithDuration:slideFactor*2 delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{
recognizer.view.center = finalPoint;
} completion:nil];
}
}
i figured out how to do it. Before panning, compare the view.
if(recognizer.view == [currentImageArray objectAtIndex:iCurrentImageTag])
i figured out how to do it. Before panning, compare the view.
if(recognizer.view == [currentImageArray objectAtIndex:iCurrentImageTag])
The correct way to do this would be in the GestureRecognizer Delegate:
https://developer.apple.com/library/prerelease/ios/documentation/UIKit/Reference/UIGestureRecognizerDelegate_Protocol/index.html
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer

Making a circular NSSlide look and behave like in GarageBand

I am new to drawing with Cocoa, and I am making some software which will have sliders similar to these found in GarageBand:
GB Sliders http://img33.imageshack.us/img33/2668/schermafbeelding2010061r.png
These look beautiful and can be controld by moving the mouse up and down.
Can you help me with customizing NSSliders by subclassing them, so I can make them look and behave exactly as in GarageBand? Thanks.
I have one image for the knob which should be rotated as they do not need to be in 3D .
The simplest way is to create a NSView subclass that handles both the mouse management and the drawing.
There is a sample code that can help you to start named "TLayer". It is part of the Examples of the XCode 3.1.4. It contains a circular custom view that controls the offset and the radius of the shadow drawn for layers. It is easy to understand and easy to extend.
Note: as it does not seems to be available on the Apple website, so I have pasted the sources below.
ShadowOffsetView.h
#import <AppKit/AppKit.h>
extern NSString *ShadowOffsetChanged;
#interface ShadowOffsetView : NSView
{
CGSize _offset;
float _scale;
}
- (float)scale;
- (void)setScale:(float)scale;
- (CGSize)offset;
- (void)setOffset:(CGSize)offset;
#end
ShadowOffsetView.m
#import "ShadowOffsetView.h"
NSString *ShadowOffsetChanged = #"ShadowOffsetChanged";
#interface ShadowOffsetView (Internal)
- (NSCell *)cell;
#end
#implementation ShadowOffsetView
- (id)initWithFrame:(NSRect)frame
{
self = [super initWithFrame:frame];
if (self == nil)
return nil;
_offset = CGSizeZero;
return self;
}
- (void)dealloc
{
[super dealloc];
}
- (float)scale
{
return _scale;
}
- (void)setScale:(float)scale
{
_scale = scale;
}
- (CGSize)offset
{
return CGSizeMake(_offset.width * _scale, _offset.height * _scale);
}
- (void)setOffset:(CGSize)offset
{
offset = CGSizeMake(offset.width / _scale, offset.height / _scale);
if (!CGSizeEqualToSize(_offset, offset)) {
_offset = offset;
[self setNeedsDisplay:YES];
}
}
- (BOOL)isOpaque
{
return NO;
}
- (void)setOffsetFromPoint:(NSPoint)point
{
float radius;
CGSize offset;
NSRect bounds;
bounds = [self bounds];
offset.width = (point.x - NSMidX(bounds)) / (NSWidth(bounds) / 2);
offset.height = (point.y - NSMidY(bounds)) / (NSHeight(bounds) / 2);
radius = sqrt(offset.width * offset.width + offset.height * offset.height);
if (radius > 1) {
offset.width /= radius;
offset.height /= radius;
}
if (!CGSizeEqualToSize(_offset, offset)) {
_offset = offset;
[self setNeedsDisplay:YES];
[(NSNotificationCenter *)[NSNotificationCenter defaultCenter]
postNotificationName:ShadowOffsetChanged object:self];
}
}
- (void)mouseDown:(NSEvent *)event
{
NSPoint point;
point = [self convertPoint:[event locationInWindow] fromView:nil];
[self setOffsetFromPoint:point];
}
- (void)mouseDragged:(NSEvent *)event
{
NSPoint point;
point = [self convertPoint:[event locationInWindow] fromView:nil];
[self setOffsetFromPoint:point];
}
- (void)drawRect:(NSRect)rect
{
NSRect bounds;
CGContextRef context;
float x, y, w, h, r;
bounds = [self bounds];
x = NSMinX(bounds);
y = NSMinY(bounds);
w = NSWidth(bounds);
h = NSHeight(bounds);
r = MIN(w / 2, h / 2);
context = [[NSGraphicsContext currentContext] graphicsPort];
CGContextTranslateCTM(context, x + w/2, y + h/2);
CGContextAddArc(context, 0, 0, r, 0, 2*M_PI, true);
CGContextClip(context);
CGContextSetGrayFillColor(context, 0.910, 1);
CGContextFillRect(context, CGRectMake(-w/2, -h/2, w, h));
CGContextAddArc(context, 0, 0, r, 0, 2*M_PI, true);
CGContextSetGrayStrokeColor(context, 0.616, 1);
CGContextStrokePath(context);
CGContextAddArc(context, 0, -2, r, 0, 2*M_PI, true);
CGContextSetGrayStrokeColor(context, 0.784, 1);
CGContextStrokePath(context);
CGContextMoveToPoint(context, 0, 0);
CGContextAddLineToPoint(context, r * _offset.width, r * _offset.height);
CGContextSetLineWidth(context, 2);
CGContextSetGrayStrokeColor(context, 0.33, 1);
CGContextStrokePath(context);
}
#end
Well, for the actual drawing you'd either have to have images for each rotation angle of the knob (easier to implement) and then just draw the proper one.
(While for a real realistic 3d look—even if possible—programmatic drawing wouldn't be worth its time, I guess.)
Or draw the knob by code. This article should give you an idea I think:
http://katidev.com/blog/2008/03/07/how-to-create-a-custom-control-with-nsview/
(For both, the mouse event handling and basic NSBezerPath drawing of circular and rotating knob-like elements)