iphone - how to scroll uiscrollview from event touched by uibutton in scrollview - iphone

structure
UIViewController
- UIScrollview
- UIButton
I'm going to make scrollview can receive button event. So whenever user do scrolling(dragging) on the button, the scrollview react as scrolling itself.
I made button down and moved handler with event forwarding like below
- (IBAction)buttonTouchedMove:(id)sender withEvent:(UIEvent *)event {
[[sender nextResponder] touchesMoved:[event allTouches] withEvent:event];
}
- (IBAction)buttonTouchedDown:(id)sender withEvent:(UIEvent *)event {
[[sender nextResponder] touchesBegan:[event allTouches] withEvent:event];
}
and to move scrollview as touches change, I made below codes
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
self.oldPoint = [touch locationInView:self.view];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
CGPoint offset = self.scrollView1.contentOffset;
UITouch *touch = [touches anyObject];
self.newPoint = [touch locationInView:self.view];
int diffX = newPoint.x - oldPoint.x;
offset.x = offset.x - diffX;
[scrollView1 setContentOffset:offset animated:YES];
self.oldPoint = self.newPoint;
}
but, scrollview react strange.. not move enough as I touch moved.

// get the button width
CGFloat buttonWidth = self.theButton.frame.size.width;
// replace your button name here
// now get the view width
CGFloat viewWidth = self.view.frame.size.width;
// replace your view name here
// now get the multiplier which i said u as 10
CGFloat mult = viewWidth / buttonWidth;
// and now use it in your code
.
.
.
diffX = mult*(newPoint.x - oldPoint.x);
.
.
.

Final answer, Using UIView animation, I got what i want.
- (IBAction)buttonTouchedDown:(id)sender withEvent:(UIEvent *)event {
UITouch *touch = [[event allTouches] anyObject];
self.oldPoint = [touch locationInView:self.view];
self.velocity = 0;
isButtonTouchedDown = YES;
}
- (IBAction)buttonTouchedMove:(id)sender withEvent:(UIEvent *)event {
CGPoint offset = self.scrollView1.contentOffset;
UITouch *touch = [[event allTouches] anyObject];
self.newPoint = [touch locationInView:self.view];
int diffX = self.newPoint.x - self.oldPoint.x;
velocity = diffX;
offset.x = offset.x - diffX;
[self.scrollView1 setContentOffset:offset animated:NO];
self.oldPoint = self.newPoint;
}
There is a trick..I calculated velocity by differentiating moved distance in TouchedMove function.
In Button TouchUp event handler, I controlled Scrollview with velocity value and CurveEaseout animation to scroll more. By offering very short duration of animation and repeating it if there is no event(button touched down). It becomes very similar with scrollview's animation.
- (IBAction)buttonTouchedUp:(id)sender withEvent:(UIEvent *)event {
CGPoint offset = self.scrollView1.contentOffset;
amountX = (self.velocity)*(abs(self.velocity));
dX = amountX;
isButtonTouchedDown = NO;
if (offset.x - amountX < 0) {
offset.x = 0;
[scrollView1 setContentOffset:offset animated:YES];
}
else if (abs(dX) < 70) { // 70: content item size
offset.x = roundf(offset.x/70)*70;
[scrollView1 setContentOffset:offset animated:YES];
}
else {
[self endScrollAnimation];
}
- (void)startAnimation:(float)x moveAmount:(float)moveAmount
{
CGPoint offset = self.scrollView1.contentOffset;
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:duration];
offset.x = x < 0 ? offset.x + moveAmount : offset.x -moveAmount;
[UIView setAnimationCurve:UIViewAnimationCurveLinear];
[UIView setAnimationDelegate:self];
[UIView setAnimationDidStopSelector:#selector(endScrollAnimation)];
[scrollView1 setContentOffset:offset animated:NO];
[UIView commitAnimations];
}
- (void)endScrollAnimation
{
CGPoint offset = self.scrollView1.contentOffset;
float slowMoveAmount = abs(amountX) * 0.05;
float fastMoveAmount = abs(amountX) * 0.1;
if (isButtonTouchedDown) {
return;
}
else if (abs(dX) > abs(amountX)*0.35 && offset.x > 0) {
[self startAnimation:dX moveAmount:fastMoveAmount];
dX = dX < 0 ? dX + fastMoveAmount : dX -fastMoveAmount;
}
else if (abs(dX) > slowMoveAmount && offset.x > 0) {
[self startAnimation:dX moveAmount:slowMoveAmount];
dX = dX < 0 ? dX + slowMoveAmount : dX - slowMoveAmount;
}
else {
offset.x = roundf(offset.x/70)*70;
[scrollView1 setContentOffset:offset animated:YES];
}
}

Related

How to restrict uilabel to be dragged within a particular frame only?

I have added one label in imageview and added Pangesture for dragging label.With this code label is dragged within the whole.but i want to restrict the label drag within the frame of uiimageview only.
How can i achieve this? The code for adding lable and gesture is as below.
lblQuote=[[UILabel alloc]init];
lblQuote.frame=CGRectMake(130,380, 400,100);
lblQuote.userInteractionEnabled=TRUE;
lblQuote.backgroundColor=[UIColor clearColor];
lblQuote.userInteractionEnabled=TRUE;
lblQuote.lineBreakMode=UILineBreakModeWordWrap;
lblQuote.font=[UIFont systemFontOfSize:40.0];
lblQuote.tag=500;
lblQuote.text=#"";
CGSize maximumLabelSize = CGSizeMake(296,9999);
CGSize expectedLabelSize = [strQuote sizeWithFont:lblQuote.font
constrainedToSize:maximumLabelSize
lineBreakMode:lblQuote.lineBreakMode];
//adjust the label the the new height.
int nooflines=expectedLabelSize.height/16.0;
lblQuote.numberOfLines=nooflines;
// CGRect newFrame = lblQuote.frame;
//newFrame.size.height = expectedLabelSize.height;
// yourLabel.frame = newFrame;
lblQuote.textAlignment=UITextAlignmentCenter;
UIPanGestureRecognizer *gesture = [[[UIPanGestureRecognizer alloc]
initWithTarget:self
action:#selector(labelDragged:)] autorelease];
[lblQuote addGestureRecognizer:gesture];
- (void)labelDragged:(UIPanGestureRecognizer *)gesture
{
UILabel *label = (UILabel *)gesture.view;
CGPoint translation = [gesture translationInView:label];
// move label
label.center = CGPointMake(label.center.x + translation.x,
label.center.y + translation.y);
// reset translation
[gesture setTranslation:CGPointZero inView:label];
}
I tried moving label on touches event.code is as below:
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint pointMoved = [touch locationInView:imgView2];
lblQuote.frame=CGRectMake(pointMoved.x, pointMoved.y, lblQuote.frame.size.width, lblQuote.frame.size.height);
}
But movement is not as smooth as pan gesture recognizer.sometimes touch is in another direction and label movement in some other direction and sometimes more or less movement than touch.
Well this is totally depends on your logics although i wrote a code for you, hope this will help you!!
Make sure your UILabel UserIntraction is set to be YES; and use touch began and touch moved methods instead of pangesture.... see this now
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [[event allTouches]anyObject];
if([touch view] == lblName)
{
NSLog(#"touch on label");
CGPoint pt = [[touches anyObject] locationInView:lblName];
startLocation = pt;
// startLocation is a CGPoint declare globaly in .h..and lblName is your UILabel
}
}
- (void) touchesMoved:(NSSet *)touches withEvent: (UIEvent *)event
{
UITouch *touch = [[event allTouches]anyObject];
if([touch view] == lblName)
{
CGPoint pt = [[touches anyObject] previousLocationInView:lblName];
CGFloat dx = pt.x - startLocation.x;
CGFloat dy = pt.y - startLocation.y;
CGPoint newCenter = CGPointMake(lblName.center.x + dx, lblName.center.y + dy);
//now put if condition for dragging in specific area
CGFloat min_X = lblName.center.x + dx - lblName.frame.size.width/2.0;
CGFloat max_X = lblName.center.x + dx + lblName.frame.size.width/2.0;
CGFloat min_Y = lblName.center.y + dy - lblName.frame.size.height/2.0;
CGFloat max_Y = lblName.center.y + dy + lblName.frame.size.height/2.0;
if((min_X >= imageView.frame.origin.x && max_X <= imageView.frame.origin.x + imageView.frame.size.width) && (min_Y >= imageView.frame.origin.y && max_Y <= imageView.frame.origin.y + imageView.frame.size.height))
{
lblName.center = newCenter;
}
}
}

need UIButton inside custom UIView respond to touches

I have a custom UIView with some buttons as subviews. I am using touchesBegan/Moved/Ended to rotate the view with the user's finger. The problem is, when I try to drag/rotate the view from a button, the touches methods aren't called. I need the buttons to respond to the UIControlEventTouchUpInside event, but if it is dragged with the view, then to make my view rotate. I have looked at this question and this question, but couldn't get either solution to work for me.
Here is my custom view code:
- (id)initWithFrame:(CGRect)frame andRadius:(float)Radius andViews:
(NSMutableArray *)AllViews
{
self = [super initWithFrame:frame];
if (self) {
radius = Radius;
allViews = AllViews;
for(int i = 0; i < [allViews count]; i++){
//the "currentView" is a UIButton
UIView *currentView = (UIView *)[allViews objectAtIndex:i];
[currentView setFrame:CGRectMake(frame.size.width / 2 - 25 + radius * cos((2 * M_PI / [allViews count]) * i), frame.size.height / 2 - 25 + radius * sin((2 * M_PI / [allViews count]) * i), 50, 50)];
[self addSubview:currentView];
}
[self setBackgroundColor:[UIColor redColor]];
[self setAlpha:.5];
}
return self;
}
//- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
//{
// for (UIView * view in [self subviews]) {
// if ([view pointInside:[self convertPoint:point toView:view] withEvent:event]) {
// return YES;
// }
// }
// return NO;
//}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint point = [touch locationInView:self];
x = point.x;
y = point.y;
NSLog(#"touchesBegan");
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint point = [touch locationInView:self];
float X = point.x;
float Y = point.y;
float a = sqrt(X * X + Y * Y);
float b = sqrt(x * x + y * y);
float c = sqrt((x - X) * (x - X) + (y - Y) * (y - Y));
float alpha = acos((a * a + b * b - (c * c)) / (2 * a * b));
if(alpha == NAN)
alpha = 0;
alpha = -alpha;
[UIView beginAnimations:#"rotate" context:nil];
[UIView setAnimationDuration:0];
[UIView setAnimationCurve:UIViewAnimationCurveEaseOut];
self.transform = CGAffineTransformRotate(self.transform, alpha);
[UIView commitAnimations];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint point = [touch locationInView:self];
float X = point.x;
float Y = point.y;
float a = sqrt(X * X + Y * Y);
float b = sqrt(x * x + y * y);
float c = sqrt((x - X) * (x - X) + (y - Y) * (y - Y));
float alpha = acos((a * a + b * b - (c * c)) / (2 * a * b));
alpha = -alpha;
[UIView beginAnimations:#"rotate" context:nil];
[UIView setAnimationDuration:0.5];
[UIView setAnimationCurve:UIViewAnimationCurveEaseOut];
self.transform = CGAffineTransformRotate(self.transform, alpha);
[UIView commitAnimations];
NSLog(#"touchesEnded");
}
- (void)dealloc
{
[allViews release];
[super dealloc];
}
Any suggestions?
Maybe not the answer you're looking for, but:
I'm not sure why you'd want "finger down on button then drag finger outside of button" activity to be picked up by the view behind the button as a dragging/rotating motion. As far as I'm concerned, if you put your finger down inside a button, you are interacting with the button, not the view behind; and if you move your finger off the button while still touching the screen, you're doing that to abort the button press (i.e. you've "armed" it but not "triggered" it), not to interact with the view behind.
So what I'm saying is: why would you want to do this? Maybe you need to rethink your UI design.
If you absolutely want to do this, you may want to look at using UIGestureRecognizers instead of touchesBegan etc. - you can set up more complicated scenarios more easily when using them. But the gesture recognizers are not available on all versions of iOS.
Alternatively, implement something that looks like a button or whatever it is you want, and handle the touching of it (and the view dragging etc.) using touchesBegan etc. - i.e. don't use an actual button.
Just incase someone comes across a similar issue, I'll post the solution I used. I overrode another UIView class that I used as a button. In each of the touchedBegan/Moved/Ended, I implemented the code I need for the button click, then passed the touch up the chain of command to the next custom UIView, like so:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[self.nextResponder touchesBegan:touches withEvent:event];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
[self.nextResponder touchesMoved:touches withEvent:event];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint point = [touch locationInView:self];
if (point.x < self.frame.size.width && point.y < self.frame.size.height) {
NSDictionary *userInfo = [[NSDictionary alloc] initWithObjectsAndKeys:[NSString stringWithFormat:#"%d", self.tag], #"tag", nil];
[[NSNotificationCenter defaultCenter] postNotificationName:#"ChangeFilter" object:self userInfo:userInfo];
}
[self.nextResponder touchesEnded:touches withEvent:event];
}
Notice that I use NSNotifications to send the action to my ViewController that contains the overridden view.

Dragging an Image View

I am trying to drag an image view. I have got little bit of success in doing so, but it is not behaving as i want. I wish that it should move only if touch inside the image and drag it.
But it is moving even if I am touching and dragging from anywhere on the screen.
I have written code like this:
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
//retrieve touch point
CGPoint pt= [[ touches anyObject] locationInView:[self.view.subviews objectAtIndex:0]];
startLocation = pt;
}
- (void) touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event
{
CGPoint pt = [[touches anyObject] locationInView: [self.view.subviews objectAtIndex:0]];
CGRect frame = [[self.view.subviews objectAtIndex:0]frame];
frame.origin.x += pt.x - startLocation.x;
frame.origin.y += pt.y - startLocation.y;
[[self.view.subviews objectAtIndex:0] setFrame: frame];
}
The return value of locationInView method is point relative to the view frame. check it is or not in the view frame first.
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
CGRect targetFrame = [self.view.subviews objectAtIndex:0].frame;
//retrieve touch point
CGPoint pt= [[ touches anyObject] locationInView:[self.view.subviews objectAtIndex:0]];
//check if the point in the view frame
if (pt.x < 0 || pt.x > targetFrame.size.width || pt.y < 0 || pt.y > targetFrame.size.height)
{
isInTargetFrame = NO;
}
else
{
isInTargetFrame = YES;
startLocation = pt;
}
}
- (void) touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event
{
if(!isInTargetFrame)
{
return;
}
//move your view here...
}
Try something like this:
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
//retrieve touch point
startLocation = [[ touches anyObject] locationInView:self.view];
// Now here check to make sure that start location is within the frame of
// your subview [self.view.subviews objectAtIndex:0]
// if it is you need to have a property like dragging = YES
// Then in touches ended you set dragging = NO
}
- (void) touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event
{
CGPoint pt = [[touches anyObject] locationInView: [self.view.subviews objectAtIndex:0]];
CGRect frame = [[self.view.subviews objectAtIndex:0]frame];
frame.origin.x += pt.x - startLocation.x;
frame.origin.y += pt.y - startLocation.y;
[[self.view.subviews objectAtIndex:0] setFrame: frame];

iPhone - Inertial dragging

I have an object that the user has to drag around the screen. This is a regular UIImageView. Currently, the image can be dragged around but when you release it, it stops where your finger lifted the image. What I want is to create momentum, so I can give an impulse to the image and it scrolls until it bounces the screen edge.
I don't want nothing very complex like adding physics libraries and stuff like that. I want to keep it as minimum as possible.
How do I do that? Can you guys point me to some tutorial or in the right direction?
thanks.
Well one way of doing this without using any physics library is this:
First in your class create 4 instance variables like this:
CFTimeInterval startTime;
CGPoint startPoint;
CGPoint oldPoint;
BOOL imageViewTouched;
Than in your - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event method have this code:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [[event allTouched] anyObject];
// Test to see if the touched view is your image view. If not just return.
if (touch.view != <#yourImageView#>) {
return;
}
startPoint = [touch locationInView:self.view];
oldPoint = startPoint;
startTime = CACurrentMediaTime();
imageViewTouched = YES;
}
For - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event do this:
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
// Fingers were moved on the screen but your image view was not touched in the beginning
if (!imageViewTouched) {
return;
}
UITouch *touch = [[event allTouches] anyObject];
CGPoint newPoint = [touch locationInView:self.view];
<#yourImageView#>.frame = CGRectOffset(<#yourImageView#>.frame, newPoint.x - oldPoint.x, newPoint.y - oldPoint.y);
oldPoint = newPoint;
}
Now for - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event try this:
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
// Fingers were removed from the screen but your image view was not touched in the beginning
if (!imageViewTouched) {
return;
}
imageViewTouched = NO;
UITouch *touch = [[event allTouches] anyObject];
CGPoint endPoint = [touch locationInView:self.view];
CFTimeInterval endTime = CACurrentMediaTime();
CFTimeInterval timeDifference = endTime - startTime;
// You may play with this value until you get your desired effect
CGFloat maxSpeed = 80;
CGFloat deltaX = 0;
CGFloat deltaY = 0;
if (timeDifference < 0.35) {
deltaX = (endPoint.x - startPoint.x) / (timeDifference * 10);
deltaY = (endPoint.y - startPoint.y) / (timeDifference * 10);
}
if (deltaX > maxSpeed) { deltaX = maxSpeed; }
else if (deltaX < -maxSpeed) { deltaX = -maxSpeed; }
else if (deltaX > -5 && deltaX < 5) { deltaX = 0; }
if (deltaY > maxSpeed) { deltaY = maxSpeed; }
else if (deltaY < -maxSpeed) { deltaY = -maxSpeed; }
else if (deltaY > -5 && deltaY < 5) { deltaY = 0; }
[UIView begginAnimations:nil context:nil];
[UIView setAnimationDuration:0.5f];
[UIView setAnimationCurve:UIViewAnimationCurveEaseOut];
<#yourImageView#>.frame = CGRectOffset(<#yourImageView#>.frame, deltaX, deltaY);
[UIView commitAnimations];
}
Please let me know if this makes sense and/or works for you. Cheers!

iPhone/Cocoa Animation having effect I don't understand

I have created a basic unlock slider like you use to unlock the iPhone.
In my touchesEnded function, I use an animation to move the slider back to home.
The first time the user moves the slider (and doesn't actually hit the end to unlock), the animation works fine and the slider moves back to home.
However, the next time the user tries to move the slider (and touchesMoved gets called), the slider gets pushed offscreen (to the left) by the amount they had slid the slider before.
So, what's happening?
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
self.dragging = NO;
self.startX = 0;
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:0.5];
self.unlock.frame = CGRectMake(0, 0, 75, 35);
[UIView commitAnimations];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
if (!self.dragging ) { return; }
UITouch *touch = [[event allTouches] anyObject];
CGPoint point = [touch locationInView:self];
int offset = point.x - self.startX;
NSLog(#"%i", offset);
if (offset > (LENGTH-SIZE)) {
offset = (LENGTH-SIZE);
}
CGAffineTransform myTransform = CGAffineTransformMakeTranslation(offset, 0.0);
[self.unlock setTransform:myTransform];
if (point.x >= (LENGTH-(SIZE-self.startX))) {
NSLog(#"Slider reached end");
[self.sliderView removeFromSuperview];
}
}
EDIT
Here's the working function, cleaned up a bit, though it could be better (thanks to a couple answered questions on SO from one hacker). I'm going to blog the complete module since there doesn't seem to be a standard widget:
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
if (!self.dragging ) { return; }
UITouch *touch = [[event allTouches] anyObject];
CGPoint point = [touch locationInView:self];
if (point.x >= (LENGTH-(SIZE-self.startX))) {
NSLog(#"Slider reached end");
[self.sliderView removeFromSuperview];
}
// offset based on where the user clicked the slider
int offset = point.x - self.startX;
if (offset > (LENGTH-SIZE)) {
offset = (LENGTH-SIZE);
}
//move the slider
CGRect oldFrame = self.unlock.frame;
CGRect newFrame = CGRectMake(self.frame.origin.x+offset, self.frame.origin.y-5, oldFrame.size.width, oldFrame.size.height);
self.unlock.frame = newFrame;
}
IN your previous question I didn't realize you were moving the slider by changing its transform. Mixing up motion by changing its frame and transform is going to be moderately complicated.
Maybe you could try doing something more like this:
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
if (!self.dragging ) { return; }
UITouch *touch = [[event allTouches] anyObject];
CGPoint point = [touch locationInView:self];
int offset = point.x - self.startX;
NSLog(#"%i", offset);
if (offset > (LENGTH-SIZE)) {
offset = (LENGTH-SIZE);
}
CGRect oldFrame = self.frame;
CGRect newFrame = CGRectMake(oldFrame.origin.x+offset, oldFrame.origin.y, oldFrame.size.width, oldFrame.size.height);
self.frame = newFrame;
if (point.x >= (LENGTH-(SIZE-self.startX))) {
NSLog(#"Slider reached end");
[self.sliderView removeFromSuperview];
}
}
That moves the slider by modifying its frame, not its translation, which should cause it to interact better with the snap back animation. Alternatively, rather than resetting the frame in the implicit animator you could reset the translation there.
You need to reset the view transform in your touchesEnded handler:
[self.unlock setTransform:CGAffineTransformIdentity];
Try that instead of animating the frame property.