How to change border of UIButton if touched outside of UIButton? - ios

Currently I am trying to create a stereotypical "selected" action of an object. That is, when I click on the object, its border changes to a different color, and when I click outside the object, the border changes back to its normal color. I can figure out how to change the border of the object when I touch the inside of the object(in this case, a UIButton) however, I cannot figure out how to change the border of the UIButton back to its original state when I touch outside of the UIButton. Here is my code so far:
- (void)longPress:(UILongPressGestureRecognizer*)gesture {
if ( gesture.state == UIGestureRecognizerStateBegan ) {
gesture.view.layer.borderColor = [UIColor lightGrayColor].CGColor;
UIAlertController * alert= [UIAlertController
alertControllerWithTitle:#"Would you like to delete this rep?"
message:nil
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction* deleteButton = [UIAlertAction
actionWithTitle:#"Delete"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action)
{
[gesture.view removeFromSuperview];
[alert dismissViewControllerAnimated:YES completion:nil];
}];
UIAlertAction* cancelButton = [UIAlertAction
actionWithTitle:#"Cancel"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action)
{
gesture.view.layer.borderColor = [UIColor blackColor].CGColor;
[alert dismissViewControllerAnimated:YES completion:nil];
}];
[alert addAction:deleteButton];
[alert addAction:cancelButton];
[self presentViewController:alert animated:YES completion:nil];
}
}
- (void)panWasRecognized:(UIPanGestureRecognizer *)panner {
{
panner.view.layer.borderColor = [UIColor lightGrayColor].CGColor;
_draggedView = panner.view;
CGPoint offset = [panner translationInView:_draggedView.superview];
CGPoint center = _draggedView.center;
_draggedView.center = CGPointMake(center.x + offset.x, center.y + offset.y);
_buttonField.layer.borderWidth = 4.0f;
// Reset translation to zero so on the next `panWasRecognized:` message, the
// translation will just be the additional movement of the touch since now.
[panner setTranslation:CGPointZero inView:_draggedView.superview];
}
}
-(void)buttonTouched:(UIButton*)sender forEvent:(id)tap {
NSSet *touches = [tap allTouches];
UITouch *touch = [touches anyObject];
UITouchPhase *phase = touch.phase;
touch.view.layer.borderColor = [UIColor lightGrayColor].CGColor;
}
-(void)doubleTapped:(UIButton*)sender forEvent:(id)twoTaps {
NSSet *touches = [twoTaps allTouches];
UITouch *touch = [touches anyObject];
UITouchPhase *phase = touch.phase;
touch.view.layer.borderColor = [UIColor blackColor].CGColor;
}
- (IBAction)addRepButton:(UIBarButtonItem *)newRep {
self.labelCounter++;
buttonCount ++;
if (buttonCount > 0 )
{
_buttonField = [[UIButton alloc]initWithFrame:CGRectMake(300, 300, 28, 28)];
[_buttonField setTitle:[NSString stringWithFormat:#"%i", self.labelCounter]forState:UIControlStateNormal];
[_buttonField setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
_buttonField.contentHorizontalAlignment = UIControlContentHorizontalAlignmentCenter;
_buttonField.userInteractionEnabled = YES;
_buttonField.layer.cornerRadius = 14;
_buttonField.layer.borderColor = [UIColor blackColor].CGColor;
_buttonField.layer.borderWidth = 4.0f;
_buttonField.titleLabel.font = [UIFont systemFontOfSize: 18];
[_buttonField setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
_buttonField.layer.backgroundColor = [UIColor blackColor].CGColor;
//Pan gesture declared in button
UIPanGestureRecognizer *panner = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(panWasRecognized:)];
[_buttonField addGestureRecognizer:panner];
//Long Press gesture declared in button
UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(longPress:)];
[self.buttonField addGestureRecognizer:longPress];
//Touch down inside declared in button
[self.buttonField addTarget:self action:#selector(buttonTouched:forEvent:) forControlEvents:UIControlEventTouchDown];
//Double Tap inside declared in button
[self.buttonField addTarget:self action:#selector(doubleTapped:forEvent:) forControlEvents:UIControlEventTouchDownRepeat];
[self.view addSubview:_buttonField];
}
}
#end
I need to find out how to change the border of the UIButton back to normal when touching outside of the UIButton in order to get the true "select/deselect" feel.

It seems like the problem isn't that you can't change the border back as much as it is you can't trigger the action when the user touches outside of the button.
I would suggest approaching this problem from the perspective of Setting the button as unselected anytime the user does not touch that button. There are a couple ways to do that.
One option, if all of your other buttons and touch areas disable that button, you could add the action to deselect that button when any other button is touched.
UISegmentedController is designed to only allow one item to be selected a time so this could be an option as well if your design allows for it.
You could look at all touch events on the screen and for any touch with the state UIGestureRecognizerStateBegan that is outside of your button you could set your button to be unselected.
This would look like this:
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event {
UITouch *aTouch = [touches anyObject];
CGPoint point = [aTouch locationInView:self.myButton.superView];
if (!CGRectContainsPoint(self.myButton, point)) {
// deselect button
}
}
There are definitely more ways to try to solve the problem, but I find these three generally cover most any situation.

Related

How to disable/enable a UIPanGestureRecognizer with two separate UIBarButtonItems?

I am trying to turn off a UIPanGestureRecognizer with the click of a "Save" button. Then, I am trying to turn back on this UIPanGestureRecognizer with the click of an "Edit" button. I can figure out how to turn them off with the following code like this:
- (IBAction)saveButton:(id)sender {
buttonCount ++;
if (buttonCount > 0) {
for (_buttonField in self.view.subviews) {
_buttonField.gestureRecognizers = nil;
_buttonField.layer.borderColor = [UIColor blackColor].CGColor;
}
}
}
Yet, I have two problems. First of all, this is turning off all of the gesture recognizers, which I do not want to do. Secondly, I cannot figure out how to turn them back on. For this I have tried the following:
- (IBAction)editButton:(id)sender {
buttonCount ++;
if (buttonCount > 0) {
if ([[UIColor colorWithCGColor:_buttonField.layer.borderColor] isEqual:[UIColor whiteColor]]) {
for (_buttonField in self.view.subviews) {
_buttonField.gestureRecognizers = YES;
}
}
}
}
However, I receive an error on the line of code that is set to YES.
Here is my complete set of code for reference:
#implementation FieldGoalChartViewController
{
}
-(void)viewDidLoad{
[super viewDidLoad];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event {
UITouch *aTouch = [touches anyObject];
CGRect buttonRect = self.buttonField.frame;
CGPoint point = [aTouch locationInView:self.buttonField.superview];
if (!CGRectContainsPoint(buttonRect, point)) {
_buttonField.layer.borderColor = [UIColor blackColor].CGColor;
_draggedView.layer.borderColor = [UIColor blackColor].CGColor;
for (_buttonField in self.view.subviews) {
_buttonField.layer.borderColor = [UIColor blackColor].CGColor;
}
}
}
- (void)longPress:(UILongPressGestureRecognizer*)gesture {
if ( gesture.state == UIGestureRecognizerStateBegan ) {
gesture.view.layer.borderColor = [UIColor whiteColor].CGColor;
UIAlertController * alert= [UIAlertController
alertControllerWithTitle:#"Would you like to delete the selected rep(s)?"
message:nil
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction* deleteButton = [UIAlertAction
actionWithTitle:#"Delete"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action)
{
for (_buttonField in self.view.subviews) {
if ([[UIColor colorWithCGColor:_buttonField.layer.borderColor] isEqual:[UIColor whiteColor]]) {
[_buttonField removeFromSuperview];
}
}
[alert dismissViewControllerAnimated:YES completion:nil];
}];
UIAlertAction* cancelButton = [UIAlertAction
actionWithTitle:#"Cancel"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action)
{
[alert dismissViewControllerAnimated:YES completion:nil];
}];
[alert addAction:deleteButton];
[alert addAction:cancelButton];
[self presentViewController:alert animated:YES completion:nil];
}
}
- (void)panWasRecognized:(UIPanGestureRecognizer *)panner {
{
panner.view.layer.borderColor = [UIColor whiteColor].CGColor;
_draggedView = panner.view;
CGPoint offset = [panner translationInView:_draggedView.superview];
CGPoint center = _draggedView.center;
_draggedView.center = CGPointMake(center.x + offset.x, center.y + offset.y);
_draggedView.layer.masksToBounds =YES;
_buttonField.layer.borderWidth = 3.0f;
// Reset translation to zero so on the next `panWasRecognized:` message, the
// translation will just be the additional movement of the touch since now.
[panner setTranslation:CGPointZero inView:_draggedView.superview];
}
}
-(void)buttonTouched:(UIButton*)sender forEvent:(id)tap {
NSSet *touches = [tap allTouches];
UITouch *touch = [touches anyObject];
UITouchPhase *phase = touch.phase;
touch.view.layer.borderColor = [UIColor whiteColor
].CGColor;
}
-(void)doubleTapped:(UIButton*)sender forEvent:(id)twoTaps {
NSSet *touches = [twoTaps allTouches];
UITouch *touch = [touches anyObject];
UITouchPhase *phase = touch.phase;
touch.view.layer.borderColor = [UIColor blackColor].CGColor;
}
- (IBAction)saveButton:(id)sender {
buttonCount ++;
if (buttonCount > 0) {
for (_buttonField in self.view.subviews) {
_buttonField.gestureRecognizers = nil;
_buttonField.layer.borderColor = [UIColor blackColor].CGColor;
}
}
}
- (IBAction)editButton:(id)sender {
buttonCount ++;
if (buttonCount > 0) {
if ([[UIColor colorWithCGColor:_buttonField.layer.borderColor] isEqual:[UIColor whiteColor]]) {
for (_buttonField in self.view.subviews) {
_buttonField.gestureRecognizers = YES;
}
}
}
}
- (IBAction)addRepButton:(UIBarButtonItem *)newRep {
self.labelCounter++;
buttonCount ++;
if (buttonCount > 0 )
{
_buttonField = [[UIButton alloc]initWithFrame:CGRectMake(300, 300, 28, 28)];
[_buttonField setTitle:[NSString stringWithFormat:#"%i", self.labelCounter]forState:UIControlStateNormal];
[_buttonField setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
_buttonField.contentHorizontalAlignment = UIControlContentHorizontalAlignmentCenter;
_buttonField.userInteractionEnabled = YES;
_buttonField.layer.cornerRadius = 14;
_buttonField.layer.borderColor = [UIColor blackColor].CGColor;
_buttonField.layer.borderWidth = 3.0f;
_buttonField.titleLabel.font = [UIFont boldSystemFontOfSize:13];
[_buttonField setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
_buttonField.layer.backgroundColor = [UIColor blackColor].CGColor;
_buttonField.layer.masksToBounds = YES;
//Pan gesture declared in button
UIPanGestureRecognizer *panner = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(panWasRecognized:)];
[_buttonField addGestureRecognizer:panner];
//Long Press gesture declared in button
UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(longPress:)];
[self.buttonField addGestureRecognizer:longPress];
//Touch down inside declared in button
[self.buttonField addTarget:self action:#selector(buttonTouched:forEvent:) forControlEvents:UIControlEventTouchDown];
//Double Tap inside declared in button
[self.buttonField addTarget:self action:#selector(doubleTapped:forEvent:) forControlEvents:UIControlEventTouchDownRepeat];
[self.view addSubview:(_buttonField)];
}
}
#end
I want to know how to specifically turn off the UIPanGestureRecognizer with the click of my "Save" UIBarButton, and then turn the UIPanGestureRecognizer with the click of my "Edit" UIBarButton.
If you added Pan gesture in IB then you can create outlet and use the enabled property on the instance of the gesture to enable and disable the gesture, simple.
panGestureRecognizer.enabled = NO; (or)
panGestureRecognizer.enabled = YES;

Saving Placement of a Draggable Button within the UIView into Core Data

I would like to be able to save the placement of the draggable button within the UIView created by a user on the click of the add button (in the navigation bar )into core data so that when the user saves the data and calls upon it from the tableview cell created by the core data that it is in the same place where the user saved it. I already have my core data set up i just want to know what si the best way to go about this whether I should use NSKeyedArchiver or something else. Here is my code and here are some pictures for better understanding.
Chart .m
#interface ChartViewController ()
#end
#implementation ChartViewController
-(void)viewDidLoad{
[super viewDidLoad];
self.title = #"Chart";
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event {
UITouch *aTouch = [touches anyObject];
CGRect buttonRect = self.buttonField.frame;
CGPoint point = [aTouch locationInView:self.buttonField.superview];
if (!CGRectContainsPoint(buttonRect, point)) {
_buttonField.layer.borderColor = [UIColor blackColor].CGColor;
_draggedView.layer.borderColor = [UIColor blackColor].CGColor;
for (_buttonField in self.view.subviews) {
_buttonField.layer.borderColor = [UIColor blackColor].CGColor;
}
}
}
- (void)longPress:(UILongPressGestureRecognizer*)gesture {
if ( gesture.state == UIGestureRecognizerStateBegan ) {
gesture.view.layer.borderColor = [UIColor whiteColor].CGColor;
UIAlertController * alert= [UIAlertController
alertControllerWithTitle:#"Would you like to delete the selected rep(s)?"
message:nil
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction* deleteButton = [UIAlertAction
actionWithTitle:#"Delete"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action)
{
for (_buttonField in self.view.subviews) {
if ([[UIColor colorWithCGColor:_buttonField.layer.borderColor] isEqual:[UIColor whiteColor]]) {
[_buttonField removeFromSuperview];
}
}
[alert dismissViewControllerAnimated:YES completion:nil];
}];
UIAlertAction* cancelButton = [UIAlertAction
actionWithTitle:#"Cancel"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action)
{
[alert dismissViewControllerAnimated:YES completion:nil];
}];
[alert addAction:deleteButton];
[alert addAction:cancelButton];
[self presentViewController:alert animated:YES completion:nil];
}
}
- (void)panWasRecognized:(UIPanGestureRecognizer *)panner {
{
panner.view.layer.borderColor = [UIColor whiteColor].CGColor;
_draggedView = panner.view;
CGPoint offset = [panner translationInView:_draggedView.superview];
CGPoint center = _draggedView.center;
_draggedView.center = CGPointMake(center.x + offset.x, center.y + offset.y);
_draggedView.layer.masksToBounds =YES;
_buttonField.layer.borderWidth = 3.0f;
// Reset translation to zero so on the next `panWasRecognized:` message, the
// translation will just be the additional movement of the touch since now.
[panner setTranslation:CGPointZero inView:_draggedView.superview];
}
}
-(void)buttonTouched:(UIButton*)sender forEvent:(id)tap {
NSSet *touches = [tap allTouches];
UITouch *touch = [touches anyObject];
touch.view.layer.borderColor = [UIColor whiteColor
].CGColor;
}
-(void)doubleTapped:(UIButton*)sender forEvent:(id)twoTaps {
NSSet *touches = [twoTaps allTouches];
UITouch *touch = [touches anyObject];
touch.view.layer.borderColor = [UIColor blackColor].CGColor;
}
- (IBAction)saveButton:(UIBarButtonItem*)saveRep {
saveCount ++;
if (saveCount == 1) {
self.title = #"Chart";
for (_buttonField in self.view.subviews) {
_buttonField.userInteractionEnabled = NO;
_buttonField.layer.borderColor = [UIColor blackColor].CGColor;
saveCount = 0;
}
}
}
- (IBAction)editButton:(UIBarButtonItem*)editRep {
editCount ++;
if (editCount == 1) {
self.title = #"Edit Mode";
for (_buttonField in self.view.subviews) {
_buttonField.userInteractionEnabled = YES;
editCount = 0;
}
}
}
- (IBAction)addRepButton:(UIBarButtonItem *)newRep {
self.labelCounter++;
buttonCount ++;
if (buttonCount > 0 )
{
_buttonField = [[UIButton alloc]initWithFrame:CGRectMake(300, 300, 28, 28)];
[_buttonField setTitle:[NSString stringWithFormat:#"%i", self.labelCounter]forState:UIControlStateNormal];
_buttonField.contentHorizontalAlignment = UIControlContentHorizontalAlignmentCenter;
_buttonField.userInteractionEnabled = YES;
_buttonField.layer.cornerRadius = 14;
_buttonField.layer.borderColor = [UIColor blackColor].CGColor;
_buttonField.layer.borderWidth = 3.0f;
_buttonField.titleLabel.font = [UIFont boldSystemFontOfSize:13];
[_buttonField setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
_buttonField.layer.backgroundColor = [UIColor blackColor].CGColor;
_buttonField.layer.masksToBounds = YES;
//Pan gesture declared in button
UIPanGestureRecognizer *panner = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(panWasRecognized:)];
[_buttonField addGestureRecognizer:panner];
//Long Press gesture declared in button
UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(longPress:)];
[self.buttonField addGestureRecognizer:longPress];
//Touch down inside declared in button
[self.buttonField addTarget:self action:#selector(buttonTouched:forEvent:) forControlEvents:UIControlEventTouchDown];
//Double Tap inside declared in button
[self.buttonField addTarget:self action:#selector(doubleTapped:forEvent:) forControlEvents:UIControlEventTouchDownRepeat];
[self.view addSubview:(_buttonField)];
}
}
- (void) saveData {
NSMutableDictionary *dataDict = [[NSMutableDictionary alloc] initWithCapacity:3];
if (_buttonField != nil) {
[dataDict setObject:_buttonField forKey:#"placement"]; // save the placement array
}
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectoryPath = [paths objectAtIndex:0];
NSString *filePath = [documentsDirectoryPath stringByAppendingPathComponent:#"Rep"];
[NSKeyedArchiver archiveRootObject:dataDict toFile:filePath];
}
Picture of ViewController
When the user presses done I want to save it from there into the core data. Thank you!!
For CGRect there is also an easy way out by using a string which is already a standard data type in Core Data.
// Swift and Objective-C
object.rect = NSStringFromCGRect(frame)
view.frame = CGRectFromString(object.rect)
While NSValue is also a good option, this is also eminently readable.
In your data model add a transformable called something like boundingRect. Then when your user finishes dragging the button say something like:
managedObject.boundingRect = [NSValue valueWithCGRect:button.frame];
Then when you want to read it out of the database say:
UIButton *button = [[UIButton alloc]initWithFrame: [managedObject.boundingRect CGRectValue]];

Programmatically made UIButton is not deleting when action fired

Currently I have a button that makes a draggable UIView with a subview of a UIButton. When I long press that UIButton, an alert view comes up and I have two buttons, a delete button and a cancel button. The delete button is supposed to delete the last long pressed UIButton, however it deletes the most recently made UIButton.
I would like for the delete button on the alert view to delete the last long pressed UIButton.(not the most recently created) I have tried different if statements, but this is what I have so far. Here is my code for my .m file:
- (void)longPress:(UILongPressGestureRecognizer*)gesture {
if ( gesture.state == UIGestureRecognizerStateBegan ) {
UIAlertController * alert= [UIAlertController
alertControllerWithTitle:#"Would you like to delete this rep?"
message:nil
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction* deleteButton = [UIAlertAction
actionWithTitle:#"Delete"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action)
{
[_buttonField removeFromSuperview];
[alert dismissViewControllerAnimated:YES completion:nil];
}];
UIAlertAction* cancelButton = [UIAlertAction
actionWithTitle:#"Cancel"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action)
{
[alert dismissViewControllerAnimated:YES completion:nil];
}];
[alert addAction:deleteButton];
[alert addAction:cancelButton];
[self presentViewController:alert animated:YES completion:nil];
}
}
- (void)panWasRecognized:(UIPanGestureRecognizer *)panner {
{
UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(longPress:)];
[self.buttonField addGestureRecognizer:longPress];
_draggedView = panner.view;
CGPoint offset = [panner translationInView:_draggedView.superview];
CGPoint center = _draggedView.center;
_draggedView.center = CGPointMake(center.x + offset.x, center.y + offset.y);
_draggedView.layer.borderWidth = 2.0f;
_buttonField.layer.borderColor = [UIColor blackColor].CGColor;
[_buttonField setTintColor:[UIColor magentaColor]];
// Reset translation to zero so on the next `panWasRecognized:` message, the
// translation will just be the additional movement of the touch since now.
[panner setTranslation:CGPointZero inView:_draggedView.superview];
}
}
- (IBAction)addRepButton:(UIBarButtonItem *)newRep {
self.labelCounter++;
buttonCount ++;
if (buttonCount >= 0 )
{
_buttonField = [[UIButton alloc]initWithFrame:CGRectMake(100, 100, 28, 28)];
[_buttonField setTitle:[NSString stringWithFormat:#"%i", self.labelCounter]forState:UIControlStateNormal];
[_buttonField setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
_buttonField.contentHorizontalAlignment = UIControlContentHorizontalAlignmentCenter;
_buttonField.userInteractionEnabled = YES;
_buttonField.layer.cornerRadius = 14;
_buttonField.layer.borderColor = [UIColor blackColor].CGColor;
_buttonField.layer.borderWidth = 2.0f;
_buttonField.titleLabel.font = [UIFont systemFontOfSize: 18];
UIPanGestureRecognizer *panner = [[UIPanGestureRecognizer alloc]
initWithTarget:self action:#selector(panWasRecognized:)];
[_buttonField addGestureRecognizer:panner];
[self.view addSubview:_buttonField];
}
}
How do I go about making the delete button remove the most recently long pressed _buttonField?
You are saying:
[_buttonField removeFromSuperview];
Well, as your loop shows (inside addRepButton), _buttonField is the most recently added button, because every time you add a button, you set it to that button. So what is happening is exactly what you are saying to happen.
I presume, although it is a little hard to tell from your code, that the button you want to delete is the one whose long press gesture recognizer this is — that is, gesture.view.
- (void)longPress:(UILongPressGestureRecognizer*)gesture {
if ( gesture.state == UIGestureRecognizerStateBegan ) {
//Update
UIButton *buttonPressedLatest;
UIView *ifBtnPressed = gesture.view;
if([ifBtnPressed isKindOfClass:[UIButton class]]){
buttonPressedLatest = (UIButton *)ifBtnPressed;
}
UIAlertController * alert= [UIAlertController
alertControllerWithTitle:#"Would you like to delete this rep?"
message:nil
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction* deleteButton = [UIAlertAction
actionWithTitle:#"Delete"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action)
{
[buttonPressedLatest removeFromSuperview];
[alert dismissViewControllerAnimated:YES completion:nil];
}];
UIAlertAction* cancelButton = [UIAlertAction
actionWithTitle:#"Cancel"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action)
{
[alert dismissViewControllerAnimated:YES completion:nil];
}];
[alert addAction:deleteButton];
[alert addAction:cancelButton];
[self presentViewController:alert animated:YES completion:nil];
}
}
Try this once and tell me if this works.

iOS: Handling long press and drag to select another button. (Like the keyboard)

I'm having a hard time finding the right documentation for how to handle touch events in order to support similar behavior to the keyboard.
What I want is a button that when I long press it, it shows a custom view controller above the button, but I want the user to be able to drag their finger to one of the other buttons (without taking their finger off the screen).
I have the button with a long press and it's custom view controller all setup and working. What I can't figure is how to support dragging from the first button over to the other button in the view controller to be able to select it.
I've tried using a subclassed UIButton where I tried this:
[self addTarget:self action:#selector(onDragOver:) forControlEvents:UIControlEventTouchDragEnter];
But that doesn't work.
I also found this question How to track button selection after long press? which is precisely the functionality I'm trying to duplicate. But there are no answers.
Here's my solution. The trick is you have to use hitTest:.
First you add a gesture recognizer to the button that is a normal button - the button that you want to open a context menu / custom view controller.
Then in your gesture recognizer callback, you use hitTest: to figure out if the user is over a custom button of yours and update it's state manually.
- (id) init {
//add a long press gesture recognizer
UILongPressureGestureRecognizer * gesture = [[UILongPressureGestureRecognizer alloc] initWithTarget:self action:#selector(onLongTap:)];
[self.myButton addGestureRecognizer:gesture];
}
- (void) onLongTap:(UIGestureRecognizer *) gesture {
if(gesture.state == UIGestureRecognizerStateBegan) {
//display your view controller / context menu over the button
}
if(gesture.state == UIGestureRecognizerStateEnded) {
//gesture stopped, use hitTest to find if their finger was over a context button
CGPoint location = [gesture locationInView:self.view];
CGPoint superviewLocation = [self.view.superview convertPoint:location fromView:self.view];
UIView * view = [self.view.superview hitTest:superviewLocation withEvent:nil];
if([view isKindOfClass:[MMContextMenuButton class]]) {
//their finger was over my custom button, tell the button to send actions
MMContextMenuButton * button = (MMContextMenuButton *) view;
[self hideAndSendControlEvents:UIControlEventTouchUpInside];
if(self.draggedContextMenuButton == button) {
self.draggedContextMenuButton = nil;
}
}
if(self.draggedContextMenuButton) {
[self sendActionsForControlEvents:UIControlEventTouchUpInside];
}
self.draggedContextMenuButton = nil;
}
if(gesture.state == UIGestureRecognizerStateChanged) {
//gesture changed, use hitTest to see if their finger
//is over a button. Manually have to tell the button
//that it should update it's state.
CGPoint location = [gesture locationInView:self.view];
CGPoint superviewLocation = [self.view.superview convertPoint:location fromView:self.view];
UIView * view = [self.view.superview hitTest:superviewLocation withEvent:nil];
if([view isKindOfClass[MMContextMenuButton class]]) {
MMContextMenuButton * button = (MMContextMenuButton *) view;
if(self.draggedContextMenuButton != button) {
[self.draggedContextMenuButton dragOut];
}
self.draggedContextMenuButton = button;
[button dragOver];
}
}
}
//////////////
#import "MMContextMenuButton.h"
#import "MMContextMenus.h"
#implementation MMContextMenuButton
- (id) initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
self.layer.cornerRadius = 4;
self.adjustsImageWhenHighlighted = FALSE;
self.adjustsImageWhenDisabled = FALSE;
self.backgroundColor = [UIColor clearColor];
[self setTitleColor:[UIColor whiteColor] forState:UIControlStateHighlighted];
[self setTitleColor:[UIColor colorWithRed:0.435 green:0.745 blue:0.867 alpha:1] forState:UIControlStateNormal];
[self addTarget:self action:#selector(onHighlight:) forControlEvents:UIControlEventTouchDown];
[self addTarget:self action:#selector(onRelease:) forControlEvents:UIControlEventTouchUpOutside&UIControlEventTouchUpOutside];
return self;
}
- (void) onHighlight:(id) sender {
self.backgroundColor = [UIColor colorWithRed:0.435 green:0.745 blue:0.867 alpha:1];
}
- (void) onRelease:(id) sender {
self.backgroundColor = [UIColor clearColor];
}
- (void) hideAndSendControlEvents:(UIControlEvents) events {
[self dragOut];
[self sendActionsForControlEvents:events];
[[MMContextMenus instance] hideContextMenus];
}
- (void) dragOver {
self.highlighted = TRUE;
self.backgroundColor = [UIColor colorWithRed:0.435 green:0.745 blue:0.867 alpha:1];
}
- (void) dragOut {
self.highlighted = FALSE;
self.backgroundColor = [UIColor clearColor];
}
#end

Touch and drag a UIButton around, but don't trigger it when releasing the finger

I'm trying to allow some UIButton instances on one of my views to be touched and dragged around the screen (eventually with momentum, but that's for later!). I have this working in a very simple form, shown below, but the problem is that by touching the button to begin dragging it, it attaches to the finger, and by lifting the finger off, the "Touch Up Inside" event is triggered, which is the code I want to execute when actually tapping the button.
In a nutshell: how do I differentiate between a tap, and a drag/release? Do I need to change the tap to a short-tap gesture recognizer, or similar, perhaps? Code:
In viewDidLoad:
[firstButton addTarget: self action: #selector(wasDragged: withEvent:) forControlEvents: UIControlEventTouchDragInside];
And my wasDragged method:
- (void)wasDragged:(UIButton *)button withEvent:(UIEvent *)event
{
if (button == letter1Button) {
UITouch *touch = [[event touchesForView:button] anyObject];
CGPoint previousLocation = [touch previousLocationInView:button];
CGPoint location = [touch locationInView:button];
CGFloat delta_x = location.x - previousLocation.x;
CGFloat delta_y = location.y - previousLocation.y;
button.center = CGPointMake(button.center.x + delta_x, button.center.y + delta_y);
}
}
You could use a UIPanGestureRecognizer and tell it to cancel touches in view...
- (void)viewDidLoad
{
[super viewDidLoad];
UIPanGestureRecognizer *panRecognizer;
panRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self
action:#selector(wasDragged:)];
// cancel touches so that touchUpInside touches are ignored
panRecognizer.cancelsTouchesInView = YES;
[[self draggableButton] addGestureRecognizer:panRecognizer];
}
- (void)wasDragged:(UIPanGestureRecognizer *)recognizer {
UIButton *button = (UIButton *)recognizer.view;
CGPoint translation = [recognizer translationInView:button];
button.center = CGPointMake(button.center.x + translation.x, button.center.y + translation.y);
[recognizer setTranslation:CGPointZero inView:button];
}
- (IBAction)buttonWasTapped:(id)sender {
NSLog(#"%s - button tapped",__FUNCTION__);
}
For beginners like me, I tried UIPanGestureRecognizer as suggested above, but it did not work. So, here is my simple solution:
First, add event listeners as suggested by Baig:
// add drag listener
[button addTarget:self action:#selector(wasDragged:withEvent:) forControlEvents:UIControlEventTouchDragInside];
// add tap listener
[button addTarget:self action:#selector(wasTapped:) forControlEvents:UIControlEventTouchUpInside];
Both drag and tap will both trigger UIControlEventTouchUpInside, so add a flag in wasDragged:withEvent: like this:
-(IBAction)wasDragged: (id)sender withEvent: (UIEvent *) event {
was_dragged = YES;
UIButton *selected = (UIButton *)sender;
selected.center = [[[event allTouches] anyObject] locationInView:self.view];
}
- (IBAction)buttonWasTapped:(id)sender {
if(!was_dragged)
NSLog(#"button tapped");
else{
was_dragged = NO;
NSLog(#"button dragged");
}
}
Voila. Done.
Use UIControlEventTouchDragInside and UIControlEventTouchUpInside events of UIButton like
// add drag listener
[button addTarget:self action:#selector(wasDragged:withEvent:) forControlEvents:UIControlEventTouchDragInside];
// add tap listener
[button addTarget:self action:#selector(wasTapped:) forControlEvents:UIControlEventTouchUpInside];
You can use HBDraggableButton control..

Resources