UIButton does not detect touch events when clicked - objective-c

I've created a tableview that has custom cells (I've subclassed UITableViewCell).
I'm creating several UIButtons in it. Declaring this in the .h file:
#property (nonatomic, strong) UIButton *myButton;
and this in the .m file:
myButton = [[UIButton alloc] initWithFrame:CGRectMake(-100.0, 285.0, 60.0, 30.0)];
myButton.titleLabel.font = [UIFont boldSystemFontOfSize:16.0];
myButton.backgroundColor = [UIColor clearColor];
[myButton setTitleColor:[UIColor redColor] forState:UIControlStateNormal];
[myButton setTitle:#"text" forState:UIControlStateNormal];
[myButton addTarget:self action:#selector(myButtonClicked:) forControlEvents:UIControlEventTouchUpInside];
[self.contentView addSubview:myButton];
I'm moving the cell to the right using UIPanGestureRecognizer and then the UIButton is revealed (notice that it is in a negative X index).I can see it but it is not clickable (I guess it is not in the view any more).
Any suggestion about how to make it clickable?
More details:
In the init of the cell, I'm doing:
UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(handlePanGesture:)];
panGesture.delegate = self;
[self.contentView addGestureRecognizer:panGesture];
Then I implemented the gesture:
-(void)handlePanGesture:(UIPanGestureRecognizer *)sender {
CGPoint translate = [sender translationInView:self.contentView];
CGRect currentFrame = self.contentView.frame;
switch ([sender state]) {
case UIGestureRecognizerStateChanged:
currentFrame.origin.x = translate.x;
self.contentView.frame = currentFrame;
break;
default:
break;
}
}
When the frame moves to the right - the button is revealed. But not clickable...

If a button's frame is outside that of its parent's frame, it will not register touches. In this case, you mention that your button stops working when its frame's x location is set to -100. This makes sense because it is outside the touch area.
Your pan gesture you implemented moves the cell's view, so while the button may appear on screen, it is still OUTSIDE of the cell's view, hence it cannot receive touches.
One possible suggestion for fixing this would be to make your cells bigger from the start. So for example, if a cell's frame size is currently 0,0,320,44, you could instead make it -100,0,420,44 and place the contents of your cell accordingly. Now, your button's frame would change to 0,0,x,y and it would be inside of the cell's view and able to receive touches.

Related

Touch UITableView background while scrolling (Kind of an easter egg)

I need to do a hidden button at the bottom of the table view, not in a cell, in the background of the table view itself.
I saw something like this in a game "Where is my water", if you scroll out of bounds you can find a hidden button.
first, I created a simple button, and placed in the bottom of the table view
self.tableView.backgroundColor = [UIColor purpleColor]; //just to see better
UIButton *venom = [[UIButton alloc] initWithFrame:CGRectMake(50.0, self.tableView.contentSize.height+80.0, 100.0, 40.0)];
venom.backgroundColor = [UIColor redColor];
[venom addTarget:self action:#selector(venomAction) forControlEvents:UIControlEventTouchDown];
[self.tableView addSubview:venom];
Because of the self.tableView.contentSize.height+80.0, I need to scroll out of the bounds of the table view to see the button, having something like this:
The result is correct, I want this to be sort of hidden, but the problem is, I cannot click on the button, to see the button, I need to be scrolling and I cannot multitouch.
Can anyone assist me with that? or point me to the right direction
I'd make the button not the tableView's subview, but rather a sibling view above the tableView.
For example, your code could look like this
- (void)viewDidLoad {
[super viewDidLoad];
// Just creating a simple UITableView
UITableView *tableView = [[UITableView alloc] initWithFrame:self.view.bounds];
tableView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
tableView.dataSource = self;
tableView.delegate = self;
self.tableView = tableView;
[self.view addSubview:tableView];
// Creating the Easter Egg Button
UIButton *easterEggButton = [UIButton buttonWithType:UIButtonTypeSystem];
[easterEggButton setTitle:#"Easter Egg Button" forState:UIControlStateNormal];
[easterEggButton addTarget:self
action:#selector(easterEggButtonPressed)
forControlEvents:UIControlEventTouchUpInside];
self.easterEggButton = easterEggButton;
// NOTE that we add the Easter Egg Button to self.view over the Table View, not into Table View
[self.view addSubview:easterEggButton];
}
So we have the button placed over the tableView. This detaches it from interfering with tableView's gestureRecognizer.
Now you need some code to adjust the button's frame to be placed somewhere under the tableView's last cell. It may look something like this:
- (void)updateButtonFrame {
UIButton *button = self.easterEggButton;
// Just the easiest way calculate the width and height of the button depending on it's content
[button sizeToFit];
CGRect frame = button.bounds;
// Now, we want to place the button under all the cells
frame.origin.y = self.tableView.contentSize.height;
frame.origin.y += 60.0f; // add some padding to place it even lower
/* We have calculated button's frame in tableView's coordinate space, so we need to convert it to
* button superview's coordinate space. */
frame = [self.tableView convertRect:frame toView:button.superview];
// And finally apply the frame to the button
button.frame = frame;
}
And of course you need to call this layout method in appropriate moments, that is, whenever tableView reloads it's data (because contentSize may change), and whenever tableView scrolls.
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
/* Button resides in self.view and is unaware of Table View scrolling, so we need to update
* it's frame whenever Table View's content offset changes */
[self updateButtonFrame];
}
You may also call it viewDidLayoutSubviews to be safe if tableView's cell height depends on screen orientation and such.
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
// It's safe to recalculate button's frame whenever self.view layout updates
[self updateButtonFrame];
}
You may have a look at this example implementation
https://gist.github.com/bartekchlebek/429906e05fcbd976291d

UIInputView background is invisible while keyboard is animating

I have a text field which has a UIInputView input accessory view.
When I tap my text field, and the keyboard comes flying into view and I can see the accessory view's subviews, but it has no visible background.
Once the keyboard animation is complete, the UIInputView's background pops into view.
What can I do to force the UIInputView's background to be visible while the keyboard animation is still going?
Here's my code:
UIInputView *inputView = [[UIInputView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, CGRectGetWidth(self.bounds), 44.0f) inputViewStyle:UIInputViewStyleKeyboard];
UIButton *button = [UIButton buttonWithType:UIButtonTypeSystem];
button.frame = CGRectInset(inputView.bounds, 15.0f, 2.0f);
[button setTitle:#"Button" forState:UIControlStateNormal];
[inputView addSubview:button];
UITextField *textField = [[UITextField alloc] initWithFrame:CGRectMake(0.0f, 0.0f, CGRectGetWidth(self.bounds), 44.0f)];
textField.inputAccessoryView = inputView;
[self addSubview:textField];
Not a complete solution, but this impact can be minimized if you set background of your UIInputView manually inside -viewDidLoad: method. Fortunately, no need to remove it after. Right after appearing UIInputView will have a truly keyboard background and a blur effect as well.
UIColor *tmpBackground; //Keyboard have color base on the current device.
if ([UIDevice isPad] || (UIDevice isPod)]) { //These are my helpers, you have to implement it
tmpBackground = [UIColor colorWithRed:207/255.0f green:210/255.0f blue:214/255.0f alpha:1];
} else {
tmpBackground = [UIColor colorWithRed:216/255.0f green:219/255.0f blue:223/255.0f alpha:1];
}
[textField.inputAccessoryView setBackgroundColor:tmpBackground];
P.S. And I know, it is not a perfect solution to define colors in such a stupid way.

Recognize swipe direction together with UIButton object

I need to recognize in which direction performed swipe and to know which object on my view has sent it. I've found workaround for now but it looks complicated and monstrous so I doubt is there any simplest solution?
My workaround in few words:
I attached recognizers to every UIButton objects on my view.
There is indicator that shows which button was tapped during performing swipe
I have to check this indicator and swipe direction for making right decision, which object and in which direction it should be moved.
Thank you in advise.
#synthesize swipeDirection; //label which shows swipe direction
#synthesize buttonNumber; //label which shows number of button which being tapped
- (void)viewDidLoad
{
UIButton* button = [UIButton buttonWithType:UIButtonTypeRoundedRect]; //creating button
[button setTitle:#"test1" forState:UIControlStateNormal];
button.frame = CGRectMake(100, 100, 100, 100);
[button addTarget:self //adding selector which will update indicator which button been tapped, there isn't way to make swipe without tap button
action:#selector(updateButtonNumber:)
forControlEvents:UIControlEventTouchDown];
[button setTag:1];
[self.view addSubview:button]; //add button to view
[self beginListenToSwipes:button]; //send button to method which will add UISwipeGestureRecognizer for 4 directions
/*same task for second button*/
UIButton* button1 = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[button1 setTitle:#"test2" forState:UIControlStateNormal];
button1.frame = CGRectMake(200, 200, 100, 100);
[button1 addTarget:self
action:#selector(updateButtonNumber:)
forControlEvents:UIControlEventTouchDown];
[button1 setTag:2];
[self.view addSubview:button1];
[self beginListenToSwipes:button1];
}
/*every time when we tap button (begin swipe this method is called and buttonNumber always points to current button*/
-(void)updateButtonNumber:(UIButton*)sender
{
self.buttonNumber.text = [NSString stringWithFormat:#"%d",sender.tag];
}
/*method receives UIButton and add to it recognizers*/
- (void) beginListenToSwipes:(UIButton*)sender
{
UISwipeGestureRecognizer *recognizer;
recognizer = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(handleSwipe:)];
[recognizer setDirection:(UISwipeGestureRecognizerDirectionRight)];
[sender addGestureRecognizer:recognizer];
recognizer = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(handleSwipe:)];
[recognizer setDirection:(UISwipeGestureRecognizerDirectionLeft)];
[sender addGestureRecognizer:recognizer];
recognizer = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(handleSwipe:)];
[recognizer setDirection:(UISwipeGestureRecognizerDirectionUp)];
[sender addGestureRecognizer:recognizer];
recognizer = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(handleSwipe:)];
[recognizer setDirection:(UISwipeGestureRecognizerDirectionDown)];
[sender addGestureRecognizer:recognizer];
}
/*method which called when swipe performed and shows in which direction it was performed*/
-(void)handleSwipe:(UISwipeGestureRecognizer *)recognizer
{
int i = recognizer.direction;
self.swipeDirection.text = [NSString stringWithFormat:#"%d",i];
}
My idea would be,
Register for touchUpoutside event for UIButton.
When you move your finger out of the button it will be fired.
Find the touch point from touchesEnded method and store in a class level variable.
Compare the button frame and touched point in the touchupoutside method to find the direction of movement.
I think you could also put your gesture recognizer in self.view, and then see if the gesture took place over the control in question (obviously, you'd want to make your buttons ivars or properties of your viewcontroller so you can keep track of which is which), e.g.
CGPoint point = [sender locationInView:self.view];
if (CGRectContainsPoint(button1.frame, point))
// do whatever you want
It's up to you as to whether you do this logic at UIGestureRecognizerStateBegan or UIGestureRecognizerStateEnded or both.
Update:
By the way, if you're detecting which direction you're moving something, you can also contemplate using UIPanGestureRecognizer, which handles all four directions in a single gesture recognizer (and because it's a continuous, you can offer the user realtime feedback in the UI animating the move).

Detecting background tap

I'm busy with an app you need to touch a button, but when u touch outside the button (on the background of the app screen) i want to display an alert.
Does anyone know how to detect an tap on the background
Button:
MyButton = [UIButton buttonWithType:UIButtonTypeCustom];
MyButton.frame = CGRectMake(0, 0, 100, 100);
[MyButton setImage:[UIImage imageNamed:#"tap.png"] forState:nil];
[self.view addSubview:MyButton];
[MyButton addTarget:self action:#selector(buttonPressed) forControlEvents:UIControlEventTouchUpInside];
You could add a gesture recogniser to the view.
E.g.
// in viewDidLoad:
UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(backgroundTapped:)];
tapRecognizer.numberOfTapsRequired = 1;
[self.view addGestureRecognizer:tapRecognizer];
- (void)backgroundTapped:(UITapGestureRecognizer*)recognizer {
// display alert
}
What you could also try is having a full size UIView behind the button that has a gesture recogniser on it:
// in viewDidLoad:
UIView *backgroundView = [[UIView alloc] initWithFrame:self.view.bounds];
backgroundView.backgroundColor = [UIColor clearColor];
backgroundView.opaque = NO;
[self.view addSubview:backgroundView];
UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(backgroundTapped:)];
tapRecognizer.numberOfTapsRequired = 1;
[backgroundView addGestureRecognizer:tapRecognizer];
- (void)backgroundTapped:(UITapGestureRecognizer*)recognizer {
// display alert
}
This might work better than adding the gesture recogniser to self.view.
You can create a custom subclass of a UIView, make it transparent but with touch handlers, and place a full size instance of that new view subclass underneath your button. You can then have that subview send a message to your main view controller (via delegation) whenever it see a touch down event. Since the button is on top of this subview, the subview won't do anything if the button is tapped.
I looked at the solution in the first answer and tested it. It did not work for me. The gesture recognizer captured my button & other UI element touches (I'm loading from nib)
A window delivers touch events to a gesture recognizer before it delivers
them to the hit-tested view attached to the gesture recognizer. Generally,
if a gesture recognizer analyzes the stream of touches in a multi-touch
sequence and does not recognize its gesture, the view receives the full
complement of touches
I used a slightly different solution:
UIButton *invisibleBtn = [[UIButton alloc] initWithFrame:self.view.bounds];
invisibleBtn.titleLabel.text = #"";
invisibleBtn.backgroundColor = [UIColor clearColor]; // no tap events if this is not set, bizarre
[invisibleBtn addTarget:self action:#selector(backgroundTapped:) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:invisibleBtn];
[self.view sendSubviewToBack:invisibleBtn];

Capture dynamic button tap ios

I have button created programmatically in the UIImageView. This view was also created programmatically. When image in this view is tapped the button is created, now I want to capture when this button is tapped but I'm unable to here is my relevant code :
- (void)imageTapped:(UIGestureRecognizer *)sender{
MyImageView *myView = (MyImageView *)sender.view;
NSLog(#"Image tapped is => %#", myView.currentImageName);
//add button to view
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
[button addTarget:self
action:#selector(pressedPlay:)
forControlEvents:UIControlEventTouchDown];
//button image
UIImage *backgroundImage = [UIImage imageNamed:[NSString stringWithFormat:#"playbutton.png"]];
button.frame = CGRectMake(85.0, 175.0, backgroundImage.size.width, backgroundImage.size.height);
myView.userInteractionEnabled = YES;
button.userInteractionEnabled = YES;
[button setImage:backgroundImage forState:(UIControlStateNormal)];
[myView addSubview:button];
}
Now this code should handle the tap but for some reason it doesn't :
- (void)pressedPlay:(UIGestureRecognizer *)sender{
//MyImageView *senderView = (MyImageView *) sender.view;
NSLog(#"%#", #"I Presed button");
}
Any reason why this is not working?
Your code suggests that you are using a UIGestureRecognizer (most likely a UITapGestureRecognizer) to detect the initial tap. The problem is that UIGestureRecognizer is being greedy and stopping the UIButton from receiving the touch.
You will need to do some shifting to disable the gesture recognizer after the initial detection and then reenable it after the play has been tapped.
UIGestureRecognizer has the property
#property(nonatomic, getter=isEnabled) BOOL enabled
or you can choose to implement UIGestureRecognizerDelegate and provide an implementation of
gestureRecognizer:shouldReceiveTouch:
a very simple attempt could look like:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch;
{
return ![touch.view isKindOfClass:[UIButton class]];
}
and don't forget to set the delegate on the gesture recognizer
myGesture.delegate = self;
set the button frame from:
button.frame = CGRectMake(85.0, 175.0, backgroundImage.size.width, backgroundImage.size.height);
to:
button.frame = CGRectMake(0,0, backgroundImage.size.width, backgroundImage.size.height);
it looks like you are setting button frame outside the imageview's frame and hence the button is not actually getting any interaction.
The Button frame is likely setting the button outside the bounds of the UIImageView, frames are always relative to the superviews coordinate in this case the UIImageView.
[button setExclusiveTouch:YES];