Basic Drag and Drop in iOS - iphone

I want to have a view in which there are vehicles driving around that the user can also drag and drop. What do you think is the best large-scale strategy for doing this? Is it best to get touch events from the views representing the vehicles, or from the larger view? Is there a simple paradigm you've used for drag and drop that you're satisfied with? What are the drawbacks of different strategies?

Assume you have a UIView scene with a background image and many vehicles, you may define each new vehicle as a UIButton (UIImageView will probably work too):
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
[button addTarget:self action:#selector(imageTouch:withEvent:) forControlEvents:UIControlEventTouchDown];
[button addTarget:self action:#selector(imageMoved:withEvent:) forControlEvents:UIControlEventTouchDragInside];
[button setImage:[UIImage imageNamed:#"vehicle.png"] forState:UIControlStateNormal];
[self.view addSubview:button];
Then you may move the vehicle wherever you want, by responding to the UIControlEventTouchDragInside event, e.g.:
- (IBAction) imageMoved:(id) sender withEvent:(UIEvent *) event
{
CGPoint point = [[[event allTouches] anyObject] locationInView:self.view];
UIControl *control = sender;
control.center = point;
}
It's a lot easier for individual vehicle to handle its own drags, comparing to manage the scene as a whole.

In addition to ohho's answer I've tried similar implementation, but without problem of "fast" drag and centering to drag.
The button initialization is...
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
[button addTarget:self action:#selector(imageMoved:withEvent:) forControlEvents:UIControlEventTouchDragInside];
[button addTarget:self action:#selector(imageMoved:withEvent:) forControlEvents:UIControlEventTouchDragOutside];
[button setImage:[UIImage imageNamed:#"vehicle.png"] forState:UIControlStateNormal];
[self.view addSubview:button];
and method implementation:
- (IBAction) imageMoved:(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;
}
I don't say, that this is the perfect solution for the answer. Nevertheless I found this the easiest solution for dragging.

I had a case where I would like to drag/drop uiviews between multiple other uiviews. In that case I found the best solution to trace the pan events in a super view containing all the drop zones and all the drag gable objects. The primary reason for NOT adding the event listeners to the actual dragged views was that I lost the ongoing pan events as soon as I changed superview for the dragged view.
Instead I implemented a rather generic drag drop solution that could be used on single or multiple drop zones.
I have created a simple example that can be seen here: Drag an drop uiviews between multiple other uiviews
If you decide to go the other way, try using uicontrol instead of uibutton. They declare the addTarget methods and are much easier to extend - hence give custom state and behavior.

I would actually track dragging on the vehicle view itself, rather than the large view - unless there is a particular reason no to.
In one case where I allow the user to place items by dragging them on the screen.
In that case I experimented with both having the top view and the child views draggable. I found it's cleaner code if you add a few "draggable" views to the UIView and handle how to they could be dragged. I used a simple callback to the parent UIView to check if the new location was suitable or not - so I could indicate with animations.
Having the top view track dragging I guess is as good, but that makes it slightly more messy if you would like to add non-draggable views that still interact with the user, such as a button.

-(void)funcAddGesture
{
// DRAG BUTTON
UIPanGestureRecognizer *panGestureRecognizer;
panGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(dragButton:)];
panGestureRecognizer.cancelsTouchesInView = YES;
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
button.frame = CGRectMake(50, 100, 60, 60);
[button addTarget:self action:#selector(buttontapEvent) forControlEvents:UIControlEventPrimaryActionTriggered];
[button setImage:[UIImage imageNamed:#"button_normal.png"] forState:UIControlStateNormal];
[button addGestureRecognizer:panGestureRecognizer];
[self.view addSubview:button];
}
- (void)dragButton:(UIPanGestureRecognizer *)recognizer {
UIButton *button = (UIButton *)recognizer.view;
CGPoint translation = [recognizer translationInView:button];
float xPosition = button.center.x;
float yPosition = button.center.y;
float buttonCenter = button.frame.size.height/2;
if (xPosition < buttonCenter)
xPosition = buttonCenter;
else if (xPosition > self.view.frame.size.width - buttonCenter)
xPosition = self.view.frame.size.width - buttonCenter;
if (yPosition < buttonCenter)
yPosition = buttonCenter;
else if (yPosition > self.view.frame.size.height - buttonCenter)
yPosition = self.view.frame.size.height - buttonCenter;
button.center = CGPointMake(xPosition + translation.x, yPosition + translation.y);
[recognizer setTranslation:CGPointZero inView:button];
}
- (void)buttontapEvent {
NSLog(#"buttontapEvent");
}

ohho's answer worked well for me. In case you need the swift 2.x version:
override func viewDidLoad() {
let myButton = UIButton(type: .Custom)
myButton.setImage(UIImage(named: "vehicle"), forState: .Normal)
// drag support
myButton.addTarget(self, action:#selector(imageMoved(_:event:)), forControlEvents:.TouchDragInside)
myButton.addTarget(self, action:#selector(imageMoved(_:event:)), forControlEvents:.TouchDragOutside)
}
private func imageMoved(sender: AnyObject, event: UIEvent) {
guard let control = sender as? UIControl else { return }
guard let touches = event.allTouches() else { return }
guard let touch = touches.first else { return }
let prev = touch.previousLocationInView(control)
let p = touch.locationInView(control)
var center = control.center
center.x += p.x - prev.x
center.y += p.y - prev.y
control.center = center
}

Here's the best way to do drag and drop as of iOS 11:
https://developer.apple.com/documentation/uikit/drag_and_drop/adopting_drag_and_drop_in_a_custom_view

For Swift 4 Easy and easiest way. 😛
Step 1:
Connect Your Button From Storyboard To View Controller. And Set The all Basic AutoLayout Constraints
#IBOutlet weak var cameraButton: UIButton!
Step 2:
Add Pan Gesture for your button In viewDidLoad()
self.cameraButton.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(self.panGestureHandler(panGesture:))))
Step 3:
Add panGestureHandler
#objc func panGestureHandler(panGesture recognizer: UIPanGestureRecognizer) {
let location = recognizer.location(in: view)
cameraButton.center = location
}
And Now Your Button Action
#IBAction func ButtonAction(_ sender: Any) {
print("This is button Action")
}
Add See the Result ☝️

Related

Stretch image on top of a button in order to push view controller

I have this image which is UIButton:
I want to add some kind of gesture (pan gesture) to slide it to the right and I want the image to stretch to the right also like this:
It's very similar to the iPhone's "Slide to unlock" methods but without the same image that moves around from left to right.
Is it possible to do that?
EDIT:
I've added a UIPanGestureRecognizer to my button like this:
UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(buttonDidDragged:)];
[self.buttonAbout addGestureRecognizer:panGesture];
self.buttonFrame = self.buttonAbout.frame;
Also I saved the button frame into a CGRect.
Now this is the 'buttonDidDragged' methods, but I also have a problem, the button seems to go to the right but the image remains the same image. it will not stretch.
- (IBAction)buttonDidDragged:(UIPanGestureRecognizer*)gesture
{
CGPoint translation = [gesture translationInView:self.view];
// gesture.view.center = CGPointMake(gesture.view.center.x + translation.x, gesture.view.center.y);
NSLog(#"%f",gesture.view.frame.origin.x + gesture.view.frame.size.width);
gesture.view.frame = CGRectMake(gesture.view.frame.origin.x,
gesture.view.frame.origin.y,
gesture.view.frame.size.width + translation.x,
gesture.view.frame.size.height);
UIButton *button = (UIButton*)gesture.view;
UIImage *buttonImage = [UIImage imageNamed:#"about_us_button.png"];
buttonImage = [buttonImage resizableImageWithCapInsets:UIEdgeInsetsMake(0, 80, 0, 0)];
[button setImage:buttonImage forState:UIControlStateNormal];
[button setImage:buttonImage forState:UIControlStateHighlighted];
if (gesture.state == UIGestureRecognizerStateEnded)
{
[UIView animateWithDuration:0.5
delay:0
options:UIViewAnimationOptionCurveEaseOut
animations:^{
CGPoint finalPoint = CGPointMake(self.buttonFrame.origin.x + (self.buttonFrame.size.width / 2),
self.buttonFrame.origin.y + (self.buttonFrame.size.height / 2));
// gesture.view.center = finalPoint;
button.frame = newFrame; // This is where you change the frame of the button to make it stretch.
} completion:nil];
}
[gesture setTranslation:CGPointMake(0, 0) inView:self.view];
}
Nice idea! Yes, this is possible. You can have three images, left, right and center, the center one being just one pixel wide.
You can adjust the image in touchesMoved. There you can calculate the necessary stretch width based on the location of the touch.
The stretching is pretty easy with methods like
[UIImage stretchableImageWithLeftCapWidth:topCapHeight]
(UIImage *)resizableImageWithCapInsets:(UIEdgeInsets)capInsets
resizingMode:(UIImageResizingMode)resizingMode
Do like this,
In .h
CGSize dragViewSize;
then,
-(IBAction)buttonDidDragged:(UIPanGestureRecognizer*)gesture
{
CGPoint translation = [gesture translationInView:self.view];
if (gesture.state == UIGestureRecognizerStateBegan) {
dragViewSize = gesture.view.frame.size;
} else if (gesture.state == UIGestureRecognizerStateChanged) {
CGRect _rect = gesture.view.frame;
_rect.size.width=dragViewSize.width+translation.x;
gesture.view.frame = _rect;
}
else if (gesture.state == UIGestureRecognizerStateEnded) {
}
}
Its working fine... hope it will helps you...
As I wrote in the comment to Mundi the method - (UIImage *)stretchableImageWithLeftCapWidth:(NSInteger)leftCapWidth topCapHeight:(NSInteger)topCapHeight is deprecated and it is therefore not wise to use it in any new development.
You should therefore as per #msgambel suggestion move on to - (UIImage *)resizableImageWithCapInsets:(UIEdgeInsets)capInsets for iOS 5+ compatibility or even better - (UIImage *)resizableImageWithCapInsets:(UIEdgeInsets)capInsets resizingMode:(UIImageResizingMode)resizingMode for iOS 6+ compatibility.
Never use a deprecated method :)
Finally, this is what I did and it worked just great. Thank you very much everyone!
CGPoint translation = [gesture translationInView:self.view];
UIButton *button = (UIButton*)gesture.view;
if (translation.x < kTranslationMaximum && translation.x > kTranslationMinimum)
{
self.aboutCenterImage.frame = CGRectMake(self.aboutCenterImage.frame.origin.x,
self.aboutCenterImage.frame.origin.y,
translation.x,
self.aboutCenterImage.frame.size.height);
gesture.view.frame = CGRectMake(self.aboutCenterImage.frame.origin.x + self.aboutCenterImage.frame.size.width,
self.aboutCenterImage.frame.origin.y,
self.buttonAbout.frame.size.width,
self.aboutCenterImage.frame.size.height);
}
else if (translation.x > kTranslationMaximum)
{
// Push ViewController or do whatever you like to do :)
}

Modify Height of Single Tab in UITabBar

Is there a way so that I can increase the height of a single tab (UITabbarItem) keeping the rest of the tabs with same height in UITabBar.
I am just taking a guess that you are trying to have an instagram (previous instagram) or keek like interface where one of the tabBarItems were much larger than the rest.
You have two options.
Create a customTabBar with one of the items much larger. (I will not recommend this)
Just keep the existing tabBarItems as they are. There is no need for a custom one.
You will just have to create a UIButton with the required height and shape and add that on top of your tabBarItem.
Our work then comes down to creating a subclass of UITabBarController and add a custom UIButton on top of the UITabBar.
UIButton* button = [UIButton buttonWithType:UIButtonTypeCustom];
button.frame = CGRectMake(0.0, 0.0, buttonImage.size.width, buttonImage.size.height);
[button setBackgroundImage:buttonImage forState:UIControlStateNormal];
[button setBackgroundImage:highlightImage forState:UIControlStateHighlighted];
CGFloat heightDifference = buttonImage.size.height - self.tabBar.frame.size.height;
if (heightDifference < 0)
button.center = self.tabBar.center;
else
{
CGPoint center = self.tabBar.center;
center.y = center.y - heightDifference/2.0;
button.center = center;
}
[self.view addSubview:button];
From my experience, you must extend the UITabBar class and when you initialize your UITabBarItems, set their heights accordingly. Also, create a background image for your UITabBar.
You can use customTabBar, here you can get sample of it, its nice classes and you can set tabBar item according to your size.
Link
In Swift 2.0, you can have a subclass of UITabBarController and add the UIButton using the following code:
let button: UIButton = UIButton(type: UIButtonType.Custom)
button.backgroundColor = UIColor.clearColor()
let buttonImage: UIImage = UIImage(named: "logo")!
let buttonImageHighlighted: UIImage = UIImage(named: "logoHighlighted")!
button.frame = CGRectMake(0.0, 0.0, buttonImage.size.width, buttonImage.size.height);
button.setBackgroundImage(buttonImage, forState: UIControlState.Normal)
button.setBackgroundImage(buttonImageHighlighted, forState: UIControlState.Highlighted)
let heightDifference:CGFloat = buttonImage.size.height - self.tabBar.frame.size.height;
if (heightDifference < 0) {
button.center = self.tabBar.center;
} else {
var center: CGPoint = self.tabBar.center;
center.y = center.y - heightDifference/2.0;
button.center = center;
}
self.view.addSubview(button)

Drag and drop objects and return them to their original position

I'm trying to drag an image and let it return to it's original position after releasing it. So far, I can drag an image by creating it as a button using the following code: (as seen in the answer to this question: Basic Drag and Drop in iOS )
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
[button addTarget:self action:#selector(imageTouch:withEvent:) forControlEvents:UIControlEventTouchDown];
[button addTarget:self action:#selector(imageMoved:withEvent:) forControlEvents:UIControlEventTouchDragInside];
[button setImage:[UIImage imageNamed:#"vehicle.png"] forState:UIControlStateNormal];
[self.view addSubview:button];
And later I define:
- (IBAction) imageMoved:(id) sender withEvent:(UIEvent *) event
{
CGPoint point = [[[event allTouches] anyObject] locationInView:self.view];
UIControl *control = sender;
control.center = point;
}
How can I make it return to it's original position after releasing it ?, for starters I will save the position at which each image is supposed to return, but then, there is anyway to indicate an UIImage to go from it's current position and move to another one ?, or any other alterative solution ?
UIGestureRecognizer is recommended by Apple for recent iOSes. You can use it not only for UIButton, but also for UIView (especially UIImageView in your case), etc. So I would like to recommend it.
In the interface:
#interface TestDragViewController : UIViewController {
IBOutlet UIImageView *dragImage;
CGPoint originalCenter;
}
In viewDidLoad, plz remember to enable userInteraction:
- (void)viewDidLoad {
[super viewDidLoad];
UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self
action:#selector(dragGesture:)];
[dragImage addGestureRecognizer:panGesture];
[dragImage setUserInteractionEnabled:YES];
}
And in its selector, I just set animation to visualize the returning effect:
#pragma mark -
#pragma mark UIPanGestureRecognizer selector
- (void) dragGesture:(UIPanGestureRecognizer *) panGesture{
CGPoint translation = [panGesture translationInView:self.view];
switch (panGesture.state) {
case UIGestureRecognizerStateBegan:{
originalCenter = dragImage.center;
}
break;
case UIGestureRecognizerStateChanged:{
dragImage.center = CGPointMake(dragImage.center.x + translation.x,
dragImage.center.y + translation.y);
}
break;
case UIGestureRecognizerStateEnded:{
[UIView animateWithDuration:kImageReturnTime
animations:^{
dragImage.center = originalCenter;
}
completion:^(BOOL finished){
NSLog(#"Returned");
}];
}
break;
default:
break;
}
[panGesture setTranslation:CGPointZero inView:self.view];
}
Cheers,
Tommy
Define CGPoint Globally, or in .h file.
CGPoint point;
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
[button addTarget:self action:#selector(imageTouch:withEvent:)forControlEvents:UIControlEventTouchDown];
[button addTarget:self action:#selector(imageMoved:withEvent:) forControlEvents:UIControlEventTouchDragInside];
[button setImage:[UIImage imageNamed:#"vehicle.png"] forState:UIControlStateNormal];
[self.view addSubview:button];
And later I define:
- (IBAction) imageMoved:(UIButton*) sender withEvent:(UIEvent *) event
{
point = [[[event allTouches] anyObject] locationInView:self.view];
UIControl *control = sender;
control.center = point;
[sender addTarget:self action:#selector(touches:withEvent:) forControlEvents:UIControlEventTouchUpInside];
}
-(void)touches:(UIButton *)button withEvent:(UIEvent *)event {
[UIView animateWithDuration:0.1 delay:0 options:UIViewAnimationOptionCurveEaseOut | UIViewAnimationOptionAllowUserInteraction animations:^{
button.center = point;
} completion:^(BOOL finished){
if (finished) {
// do anything yu want
}
it works awesome cause i am using it in my project!
How about calling a selector on UIControlEventTouchUpInside and then in the selector, just set the frame of your button to a new x and y coordinate (the ones that you have saved).
In the solution I linked to in the thread you are referring I have created a generic drag drop manager that takes care of the return of a dragged object to the original position (if released outside a known dropzone). Currently there is no animation in the solution, but you could combine some of the ideas presented above.
See the some how generic drag and drop solution in iOS
You know what, I was just working on this yesterday, so here you go with a little bonus animation:
- (void)previewImageTouchUpInside:(UIButton*)aButton {
if (!previewImageDragged) {
[self selectPreviewImage:aButton];
return;
}
previewImageDragged = NO;
// If user drag photo a little and then release, we'll still treat it as a tap
// instead of drag
if ((originalPositionOfButton.x - aButton.center.x) *
(originalPositionOfButton.x - aButton.center.x) +
(originalPositionOfButton.y - aButton.center.y) *
(originalPositionOfButton.y - aButton.center.y) < 10) {
aButton.center = originalPositionOfButton;
[self selectPreviewImage:aButton];
return;
}
[UIView animateWithDuration:0.5
delay:0
options:UIViewAnimationOptionCurveEaseOut |
UIViewAnimationOptionAllowUserInteraction animations:^{
aButton.center = originalPositionOfButton;
} completion:nil];
}
- (void)previewImageDraggedInside:(UIButton*)aButton event:(UIEvent*)event {
if (!previewImageDragged) {
originalPositionOfButton = aButton.center;
[self.view bringSubviewToFront:aButton];
[self.view bringSubviewToFront:[self.view viewWithTag:CHOOSE_PHOTO_BUTTON_TAG]];
}
UITouch* touch = [[event allTouches] anyObject];
previewImageDragged = YES;
aButton.center = CGPointMake(aButton.center.x+[touch locationInView:self.view].x-
[touch previousLocationInView:self.view].x,
aButton.center.y+[touch locationInView:self.view].y-
[touch previousLocationInView:self.view].y);
}
It still has a few magic numbers and I haven't renamed the functions and variables to suit your example, but it should be clear. I also did the indentation here since I don't manually break long lines in my code.

Dynamic Button Layout without hard coding positions

I would like to implement a view (it will actually be going into the footer of a UITableView) that implements 1-3 buttons. I would like the layout of these buttons (and the size of the buttons themselves) to change depending on which ones are showing. An example of almost the exact behavior I would want is at the bottom of the Info screen in the Contacts application when you are looking at a specific contact ("Add to Favorites" disappears when it is clicked, and the other buttons expand to take up the available space in a nice animation).
As I'm running through the different layout scenarios, I'm hard coding all these values and I just think... "this is NOT the way I'm supposed to be doing this."
It even APPEARS that Interface Builder has some features that may do this for me in the ruler tab, but I'll be darned if I can figure out how they work (if they work) on the phone.
Does anyone have any suggestions, or a tutorial online that I could look at to point me in the right direction?
Thanks much.
You could take a look at
Stanford cs193p classes
I think there is a demo in one of the lessons/example where they programaticaly set the autoresize of the buttons and some other where they animate the view.
you can also make the view set dynamically the width of each of its subview to (ownWidth/numberofSubView),the same for the positon of each elements.
[edit] like that ? it's not "dynamic" yet, but you've got the idea
you can increase/decrease numberOfbuttons when "adding/removing" a button then reset the new frame of all the subviews
- (void)loadView
{
UIView *view = [[UIView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame]];
view.backgroundColor = [UIColor lightGrayColor];
view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
int numberOfbuttons = 2;
CGFloat buttonsLegth = (-20+view.bounds.size.width)/numberOfbuttons-20;
for(int i=0;i<numberOfbuttons;i++){
CGRect buttonFrame = CGRectMake(0, 0, buttonsLegth, 37);
buttonFrame.origin.x = (buttonFrame.size.width+20)*i + 20.0;
buttonFrame.origin.y = 20.0;
UIButton *button = [UIButton buttonWithType:UIButtonTypeRoundedRect]; // Autoreleased
button.frame = buttonFrame;
[button setTitle:#"Done" forState:UIControlStateNormal];
[view addSubview:button];
}
self.view = view;
[view release];
}
hi remove all buttons in view and add again using this method...
you have to pass number of buttons and a UIView in which you want to add these buttons
-(void)addButtons:(int)numberOfButtons ToView:(UIView *)containerView;
{
CGRect containerRect = containerView.frame;
CGFloat padding = 5.0;
CGFloat width = (containerRect.size.width - (padding*numberOfButtons+1))/numberOfButtons;
for (int i = 1; i<=numberOfButtons; i++)
{
UIButton * btn = [[[UIButton alloc] initWithFrame:CGRectMake(i*padding+width, 0, width, containerRect.size.height)]autorelease];
//set Button Properties like Target,backColor,Title,,,,etc MUST SET TAG TO ACCESS THEM IN CLICK METHOD IF YOU ARE USING SAME SELECTOR FOR ALL BUTTONS.
[containerView addSubview:btn];
}
}
Try this and have fun......

Adding multiple UIButtons to an UIView

I've added some buttons to an UIView (via addSubview) programmatically. However, they appear as overlays (so that I always see the last button only). How do I add new buttons below existing buttons?
Regards
you can offset the button like this
int newX = previousButton.frame.origin.x + previousButton.frame.size.width ;
int newY = previousButton.frame.origin.y ;
and either set the frame for new button when you create it:
[[UIButton alloc] initWithFrame:CGRectMake(newX,newY,100,100)];
or set the frame later
newButton.frame = CGRectMake(newX,newY,100,100);
Set the UIView's frame origin to layout the UIButtons in the locations you wish:
CGRect buttonFrame = button.frame;
buttonFrame.origin = CGPointMake(100.0f, 100.0f);
button.frame = buttonFrame;
view.addSubview(button);
You can either use the insertSubview:atIndex method or insertSubview:belowSubview of your view.
UIButton *myButton = [[UIButton alloc] initWithFrame:CGRectMake(0,0,100,100)];
[myView insertSubview:myButton belowSubview:previousButton];
OR
[myView insertSubview:myButton atIndex:0];
Thanks for your answers guys.
I did the (horizontal) align with this code:
if([myContainer.subviews lastObject] == nil){
NSLog(#"NIL");
[myContainer insertSubview:roundedButton atIndex:0];
}else{
[myContainer insertSubview:roundedButton belowSubview:[tagsContainer.subviews lastObject]];
}
It works technically, but still overlays the buttons. I have to find a way, how to not overlay them...