I'm thinking about a Google Map street view pin drag and drop system on iOS. The user selects a pin from another view (let's say UINavigationBar) and drag it to a position on map and then drop it.
I'm a bit lost with it. Do you have a workflow for this type of interaction ?
It is quite a complex task but it can be divided into a couple of simple steps.
Make a custom UIView, which after touching will follow the touch movement.
Example
-(void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
_originalPosition = self.view.center;
_touchOffset = CGPointMake(self.view.center.x-position.x,self.view.center.y-position.y);
}
-(void) touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint position = [touch locationInView: self.view.superview];
[UIView animateWithDuration:.001
delay:0.0
options:UIViewAnimationOptionCurveEaseInOut
animations:^ {
self.view.center = CGPointMake(position.x+_touchOffset.x, position.y+_touchOffset.y);
}
completion:^(BOOL finished) {}];
}
-(void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
CGPoint positionInView = [touch locationInView:self.view];
CGPoint newPosition;
if (CGRectContainsPoint(_desiredView.frame, positionInView)) {
newPosition = positionInView;
// _desiredView is view where the user can drag the view
} else {
newPosition = _originalPosition;
// its outside the desired view so lets move the view back to start position
}
[UIView animateWithDuration:0.4
delay:0.0
options:UIViewAnimationOptionCurveEaseInOut
animations:^ {
self.view.center = newPosition
// to
}
completion:^(BOOL finished) {}];
}
When user releases the finger, you will have to get the location of the touch.
Calculate the touch location in map view coordinates and place the pin.
Hope it points you to the right direction.
Related
Is there a way to find the endpoint of the screen? Let's say a screen of size (100x100) have the midpoint at (50,) and starting point (0,) and the endpoint as (100,). I can hard code to get the end point based on the device screen size, but that would be a too much work based on assumptions.
I am making a custom UIView animation based on UIBezierPath.
Here is what I am trying to do:
As you can see the animation is stopped after it reaches some point.And I want this point to be the end of the screen,so that UIView is covered whole screen. To be more clear, I drag the UIView to point A, and it animates to point B (end point,so B>A)
I am not sure how to calculate B, so that when I drag UIView to A, it animates to the end of the screen (endpoint of the screen).
Here is the code to get better understanding:
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [[event allTouches] anyObject];
CGPoint touchLocation = [touch locationInView:self.dragView];
if (touchLocation.x<250) {
if (!startBackSlide) {
_circleDragView.touchdragPoint=touchLocation.y;
_circleDragView.dragToPoint=CGPointMake(touchLocation.x+40, touchLocation.y);
[_circleDragView setNeedsDisplay];
_savedPoint=touchLocation;
}else{
//move the top and bot points backwards along with slide
[self animateCOmpleteBezier:touchLocation];
_circleDragView.touchdragPoint=touchLocation.y;
_circleDragView.dragToPoint=CGPointMake(touchLocation.x+54, touchLocation.y);
[_circleDragView setNeedsDisplay];
_savedPoint=touchLocation;
if (touchLocation.x<20) {
startBackSlide=NO;
snapped=YES;
}
}
}
if (touchLocation.x>=250) {
if (snapped) {
snapped=NO;
//move the top and bottom points to the specified point(In this case the end point)
}
}
}
-(void)animateCOmpleteBezier:(CGPoint) value{
[UIView animateWithDuration:0.01 delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{
_circleDragView.topWidthPoint=value.x-10;
_circleDragView.botWidthPoint=value.x-10;
[_circleDragView setNeedsDisplay];
} completion:^(BOOL finished) {
}];
}
The endpoint of screen is equal to it's width and height:
CGSize screenSize = [UIScreen mainScreen].bounds.size;
CGPoint endPoint = CGPointMake(screenSize.width, screenSize.height);
https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIScreen_Class/#//apple_ref/occ/instp/UIScreen/bounds
I'm displaying real time locations of city's buses on MKMapView. My app polls locations with certain interval and updates them on map. I'm trying to animate movement of the map annotations.
I have successfully animated movement with the following code found from this stackoverflow answer:
- (void)moveBusAnnotation:(TKLBus*)bus coordinate:(CLLocationCoordinate2D)coordinate {
[UIView animateWithDuration:0.5f
animations:^(void){
bus.annotation.coordinate = coordinate;
}];
}
Problem is that when user pans or zooms the map while animation is playing, the motion path of the annotation looks weird and buggy. Here's a demonstration:
Notice how the map annotation follows a strange curve instead of straight line. Bus movement is simulated so ignore its strange position on map.
How can I make the animation look more natural or stop animations while map is panned/zoomed?
Edit: The animation seems to do the right thing. It looks weird just because the map annotation's next coordinate is moving while the animation plays. I think the solution would be to prevent animations while user is touching the screen.
Try this:
Set up a bool that will indicate wether the map is being used by the user:
#property (nonatomic, assign) BOOL userIsInteracting;
Then check the users touches in the map:
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesBegan:touches withEvent:event];
for (UITouch * touch in touches)
{
CGPoint loc = [touch locationInView:_mapView];
if ([_mapView pointInside:loc withEvent:event])
{
_userIsInteracting = YES;
break;
}
}
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesEnded:touches withEvent:event];
for (UITouch * touch in touches)
{
CGPoint loc = [touch locationInView:_mapView];
if ([_mapView pointInside:loc withEvent:event])
{
_userIsInteracting = NO;
break;
}
}
}
Now you know when to animate the location in the map:
- (void)moveBusAnnotation:(TKLBus*)bus coordinate:(CLLocationCoordinate2D)coordinate
{
if (_userIsInteracting) return;
[UIView animateWithDuration:0.5f
animations:^(void){
bus.annotation.coordinate = coordinate;
}];
}
I want to make a UIView go off the screen upon a touch and come back after a small delay after touch up. So far:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [[event allTouches] anyObject];
if ([touch view] == myMainView)
{
[disappearingView.layer removeAllAnimations];
[UIView animateWithDuration:0.5
delay:0
usingSpringWithDamping:0.7
initialSpringVelocity:0.2
options:UIViewAnimationOptionBeginFromCurrentState
animations:^
{
disappearingView.transform = CGAffineTransformMakeScale(0, 0);
}
completion:^(BOOL finished)
{
}];
}
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [[event allTouches] anyObject];
if ([touch view] == myMainView)
{
[UIView animateWithDuration:0.5
delay:1
usingSpringWithDamping:0.7
initialSpringVelocity:0.2
options:UIViewAnimationOptionBeginFromCurrentState
animations:^
{
disappearingView.transform = CGAffineTransformMakeScale(1, 1);
}
completion:^(BOOL finished)
{
}];
}
}
Above code works fine so far. However, if the user lift the finger up and touch down again before the 1 second delay expires, disappearing view still comes back, even with the touch down. If you touch down and up randomly, animation is very inconsistent.
I want view to come back only after 1 second and there are no touches at all.
Set a flag in touchesBegan and clear it again in touchesEnded. In your animation block for touchesEnded, check the flag and do not rescale to (1,1) if the flag is set. That way if you get a second touch after the first has ended but before the animation completes, it will not come back.
I've read all the stuff about drag'n'drop and gestureRecognizer but haven't found the solution.
I got "gamefield" 10x10 and I want to implement drag'n'drop with snap to grid and check if element placed on "gamefield".
I got my 8 "TempButtons" added to UIView buttonsSuperView with action:#selector(imageMoved:withEvent:).
- (IBAction) imageMoved:(id) sender withEvent:(UIEvent *) event
{
UIControl *control = sender;
originalPosition = [self getButton:sender];
UITouch *touch = [[event allTouches] anyObject];
point = [touch locationInView:control];
float step = 31.0; // Grid step size.
CGPoint center = control.center;
center.x += step * floor((point.x / step));
center.y += step * floor((point.y / step));
control.center = center;
}
It works, I can move any button with grid step 31px. I can place it everywhere, but I have to put it on UIView fieldView. If it placed right there it's OK and I can replace it on another tile. But if I place it outside fieldView the button should return on its place. Something like this:
if ([self.fieldView pointInside:point withEvent:nil])
{
control.center = center;
} else {
[UIView animateWithDuration:0.4
delay:0.0
options:UIViewAnimationOptionCurveEaseInOut
animations:^ {
control.center = originalPosition;
// to
}
completion:^(BOOL finished) {}];
}
If this code placed on - (IBAction) imageMoved:(id) sender withEvent:(UIEvent *) event method it makes button get back to its "originalposition" all the time its moving. But I need to check if its placed properly only on 'touchesEnded'. But that's another method and I don't understand how to realize which button pressed and its original coords to get back if placed improperly.
Maybe there's a better way to use 'UIView' instead of 'UIButton' but I still don't understand how to do that.
Thanks in advance.
Kind Regards,
Nick
Since no one answered my question I've spent some time and found working solution.
- (IBAction) imageMoved:(id) sender withEvent:(UIEvent *) event
{
dragObject = sender;
originalPosition = [self getButton:sender];
UITouch *touch = [[event allTouches] anyObject];
point = [touch locationInView:dragObject];
float step = 31.0; // Grid step size.
CGPoint center = dragObject.center;
center.x += step * floor((point.x / step));
center.y += step * floor((point.y / step));
dragObject.center = center;
[sender addTarget:self action:#selector(touchesEnded:withEvent:) forControlEvents:UIControlEventTouchUpInside];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
if (![fieldView pointInside:dragObject.center withEvent:nil])
{
[UIView animateWithDuration:0.4
delay:0.0
options:UIViewAnimationOptionCurveEaseInOut
animations:^ {
dragObject.center = CGPointMake(originalPosition.x + 15.5, originalPosition.y + 15.5);
}
completion:^(BOOL finished) {}];
}
}
Now it works like charm and I can move along. My buttons moving freely snapped to grid and when pleced outside game field they move back to their original position.
Regards,
Nick
I am trying to create an iOS app with blocks floating down the screen(UIViews). I have them floating down the screen but I also want the user to be able to move them on the x axis as they are falling. I tried to do it with the code below but they just fall and don't move left to right. My problem is I am trying to move it with my finger left to right as it is already moving town the screen. How can I adapt the code below to work?
Note: I was able to move the views left to right without them moving down the screen and I was able to move them down the screen without moving them left to right. The problem arises when I combine both.
ANIMATION FOR Y AXIS
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:letView.speed];
[UIView setAnimationDelay:0.0];
[UIView setAnimationCurve:UIViewAnimationCurveEaseOut];
letView.layer.frame = CGRectMake(letView.layer.frame.origin.x, [[UIScreen mainScreen] bounds].size.height, letView.layer.frame.size.width, letView.layer.frame.size.height);
[UIView commitAnimations];
ANIMATION FOR TOUCH
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [[event allTouches] anyObject];
CGPoint touchLocation = [touch locationInView:self.view];
//Goes through an array of views to see which one to move
for (LetterView * view in _viewArray) {
if (CGRectContainsPoint(view.frame, touchLocation)) {
dragging = YES;
currentDragView = view;
[currentDragView.layer removeAllAnimations];
}
}
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [[event allTouches] anyObject];
CGPoint touchLocation = [touch locationInView:self.view];
if (dragging) {
CGPoint location = touchLocation;
currentDragView.center = CGPointMake(location.x, currentDragView.center.y);
}
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
dragging = NO;
[self checkForCorrectWord];
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:currentDragView.speed];
[UIView setAnimationDelay:0.0];
[UIView setAnimationCurve:UIViewAnimationCurveEaseOut];
currentDragView
.layer.frame = CGRectMake(currentDragView.layer.frame.origin.x, [[UIScreen mainScreen] bounds].size.height, currentDragView.layer.frame.size.width, currentDragView.layer.frame.size.height);
[UIView commitAnimations];
}
I've got a demo project that shows you how to make a "boat" animate down the screen in an s-curve, like this:
The solution I use is to make a keyframe animation (actually it's a keyframe animation that forms part of a grouped animation, but you might not need the rest of the grouped animation: it is the keyframe animation that makes the curved path shape). Perhaps you could adapt what I'm doing, modifying it to your own purposes?
The code is available for download here:
https://github.com/mattneub/Programming-iOS-Book-Examples/tree/master/ch17p501groupedAnimation
It is discussed in detail in my book. Keyframe animations generally:
http://www.apeth.com/iOSBook/ch17.html#_keyframe_animation
This particular example:
http://www.apeth.com/iOSBook/ch17.html#_grouped_animations