UIControl not receiving touches inside UITableView Cell - ios

I added UIView in storyboard & make it as UIControl's subview. This view is inside UITableViewCell. Now I want to add touch up inside action for UIControl. I have added below code:
[cell.conditionControl addTarget:self action:#selector(btnConditionClick:) forControlEvents:UIControlEventTouchUpInside];
But this code is not working & not receiving any events.
I also disable userInteractionEnabled flag for UIView's Subview.

Related

Assigning a callback to a UIView's UIButton from the containing UIViewController

Right now I have a UIViewController subclass that is hosting a UIView subclass. This UIView has a UIButton, and I want the touch event to be sent to the UIViewController subclass. Right now I have a getter method in the UIView subclass, so I grab the button and assign it by just using the reference from within the UIViewController subclass.
What's the best approach to achieve this result?
Add the action event to your ViewController
- (IBAction)myButtonPressed:sender;
Now if you're using IB (you should be) just link it up, or if doing this in code then manually create the event link.
[myButton addTarget:self action:#selector(myButtonPressed:) forControlEvents:UIControlEventTouchUpInside];

How do I make a UILabel *not* pass touches to the UITableViewCell it's embedded in?

I have a UITableView with a bunch of cells, and I add a UILabel to each. I want the UILabel, when tapped, to accept the touches, not the cell. Obviously if they tap outside of the label and still on the cell that is fine, but if it's on the label the cell should not cause a segue or action or whatever, only the label's action on tap should fire.
My normal method to cause this is to set the UIView's userInteractionEnabled to YES, but in this case when I set it on the label it doesn't cause anything different to happen. When I watch touchesBegan in the label subclass those methods do fire, but the cell selection does as well.
How do I make the UILabel not pass the touches on to the cell?
I recommend adding a UITapGestureRecognizer to the label and setting cancelsTouchesInView property to YES to prevent the touches from being delivered to the view upon gesture recognition, ex:
UITapGestureRecognizer *labelTapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:nil];
labelTapGesture.cancelsTouchesInView = YES;
[label addGestureRecognizer:labelTapGesture];
label.userInteractionEnabled = YES;
And since you don't want the gesture to perform an action, you can set its action to nil.

UIButtons not clickable in UIScrollView after being added programatically

I have some UIButtons inside a UIScrollView. When the view first loads there are about 8 buttons showing in the UIScrollView, all these buttons are visible and clickable.
However, once I scroll, any button that wasn't there when the view initially loaded isn't clickable.
Given I have a function that creates every button programatically on view load, is it possible that the addTarget function isn't working? I create about 280 buttons at the start and add the UITouchUpInside event programatically.
--Edit--
This is more or less the code, called inside viewDidLoad function
for (int i = 0; i < numberOfButtons; i++){
//Display stuff here
MyButton *aButton = [[MyButton alloc] initWithFrame:CGRectMake(x,y,w,h)];
[aButton.titleLabel setFont:[UIFont fontWithName:#"QuicksandBook-Regular" size: 17.0]];
[aButton setTitle:[currentDisplayArray objectAtIndex:i] forState:UIControlStateNormal];
[aButton addTarget:self
action:#selector(didPressButton:)
forControlEvents:UIControlEventTouchUpInside];
[aButton setUserInteractionEnabled:TRUE];
NSLog(#"width of button = %f height = %f", [aButton frame].size.height, [aButton frame].size.width);
//I printed this to check the height and width were generated correctly.
[btnContainerView addSubview:aButton]; //UIView hooked up to storyboard
[buttonArray addObject:aButton]; //Array to maintain reference to all buttons
}
Remember, they all display correctly, its just the ones that aren't rendered in the initial frame don't trigger the "didPressButton" selector.
-- Edit --
After playing around, I think its some kind of issue with the UIScrollView it is in. Is this some kind of apple bug? I even tried adding the gesture recognizers in the scrollViewDidScroll function.
-- Edit --
Another interesting hint, If I make the UIScrollView bigger, I can click more of the buttons, if I make it smaller I can click less. It definitely has something to do with the first rendered buttons.
Maybe iOS says its initialising the buttons, but doesn't keep the selectors of all 200+ buttons in memory. Or has an inbuilt number of possible selectors/gesture recognisers per class.
Yay, I worked it out.
There was a UIView sitting in front of the UIScrollView that the buttons were being added to.
The views were the same size, but only the buttons that were inside the initial bounds (the UIView size) were clickable.
There was a UIView that I think was called btnContainerView or something. What was happening was the buttons were being added to this UIView instead of the UIScrollView.
I think the UIView was set as the delegate for the buttons because thats were they were rendered. So when the UIScrollView received an input the buck stopped there. Calling addTarget:self on the buttons would mean their target was added to the scrollView, but they were rendered onto the UIView. A confusing bug because you would expect the UIScrollView not to scroll and render properly but it did, its just the delegates for the selectors were getting confused.
Thanks everyone for the help, this was quite tricky because the buttons scrolled correctly (UIScrollView got touch input) but the UIButtons didn't get the touch input.
TL;DR; The solution was removing the intermeditary UIView as it was interfering
I hope that you are using gestures int the view controller, if so you can use the below code
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
if (([touch.view isKindOfClass:[UIButton class]] && touch.view.tag==<Btn_Tag>)) {
return NO;
}
return YES;
}
When using the gestures, gestures will respond first rather than the button action.So, this code will help to move the responder the button's action.

Why a UIButton on UITableViewCell only drawn darker when touch gesture continued for a short time

Pressing the button quickly and not holding for a short time, will not highlight the button.
Different from a UIButton on a common UIView.
Like the head photo in official Twitter client got same issue.
Instagram client seems solved this, all buttons works fine.
Find same question here:
Why doesn't UIButton showsTouchWhenHighlighted work when the button is on a UITableViewCell?
But I still don't know how to fix it.
Well... a UITableView is a subclass of UIScrollView and the UIScrollView class is known to eat touches for it's own purpose.
When it realizes the touch was not meant for it, it passes it to it's immediate subview.
This feature is the delaysContentTouches property (which by default is YES).
Which is why, the UIButton shows it's highlighted state only after a extended touch because the touch event was with the UITableView for a short while until it determined whether the touch was meant for scrolling or swiping the cell and on realizing the touch was for neither, it immediately passes the touch event to the subView directly below it.
In case of a quick-tap, the button's highlighted state is bypassed due to this delay and the target selector method is called directly.
To show the highlighted state of the button in a UITableView (just as it would on a UIView) do:
For iOS7+:
In -viewDidLoad or anywhere appropriate do:
[yourTableViewObject setDelaysContentTouches:NO];
Also... The cell.subviews has a class UITableViewCellScrollView which apparently is another scrollView and we need to disable the delaysContentTouches property of this class as well.
So... in the -cellForRowAtIndexPath: method (just before return cell;) do:
NSArray *test = cell.subviews;
for (UIView *currentView in cell.subviews) {
if ([NSStringFromClass([currentView class]) isEqualToString:#"UITableViewCellScrollView"]) {
UIScrollView *svTemp = (UIScrollView *) currentView;
[svTemp setDelaysContentTouches:NO];
break;
}
}
For iOS 6-:
In iOS6, the cell.subviews has a UITableViewCellContentView class which is not a scrollView subclass and so all it takes is setting one parameter for the tableView alone.
So, in -viewDidLoad or anywhere appropriate, this is all that you need:
[yourTableViewObject setDelaysContentTouches:NO];
PS: By doing this, it will mess up with the scrolling of the tableView so use your better judgement.

Detect touch event on UIScrollView AND on UIView's components [which is placed inside UIScrollView]

I have UIViewController on my simple storyboard IPad project which contains UIScrollView which is placed over entire surface (1024 x 768). I have created 3 XIB files which are UIViews which my application loads on start in viewDidLoad and add them into UIScrollView. Each of these 3 XIB files contains only one UIButton.
This is hierarchy:
~ UIViewController (UIViewControllerClass is class for this
UIViewController)
~~ UIScrollView (contains 3 identical UIViews)
~~~ UIView (UIViewClass is File's Owner for this XIB file)
~~~~ UIButton
I would like that my UIViewControllerClass becomes aware of both: touch anywhere on UIScrollView component AND if UIScrollView is touched, if UIButton inside of UIView in UIScrollView is touched, to have information that exactly that button is touched.
I made IBAction inside UIViewClass for touch on UIButton inside UIView in UIScrollView and when I set User Interaction Enabled = YES on all elements (UIViewController, UIView and UIScrollView) this method is called as it should.
But at this point my UIViewControllerClass isn't aware that touch occurred inside UIScrollView on that UIButton. I made touch recognizer like this:
UITapGestureRecognizer *touch = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleTouch)];
touch.numberOfTouchesRequired = 1;
and added it to UIScrollView component. In this way I am able to detect touch event on UIScrollView component in UIViewControllerClass, but touch event handler for UIButton in UIView which is inside UIScrollView isn't called anymore.
So, I need to have these two informations in UIViewControllerClass:
Touch on UIScrollView component was made
Touch on UIButton in UIView which is inside UIScrollView (if this button was touched) was made
I suppose that attaching touch event recognizer to entire UIScrollView component isn't solution, since it disables all touch event handlers I wrote inside UIViewClass.
I think solution is that somehow touches which are made on components in UIView inside UIScrollView should be sent up to UIViewControllerClass, but I didn't found a way to do this.
If anyone can help me, I'd be very grateful. Thanks in advance.
[edit #1: ANSWER by Zheng]
Tap gesture must have cancelsTouchesInView option set to NO!
For my case above, this line solves everything:
touch.cancelsTouchesInView = NO;
Many thanks to Zheng.
I don't know if this works for you or not, but I've given an answer about touch events for views inside scrollview here:
Dismissing the keyboard in a UIScrollView
The idea is to tell the scrollView not to swallow up all tap gestures within the scroll view area.
I'll paste the code here anyways, hopefully it fixes your problem:
UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(hideKeyboard)];
// prevents the scroll view from swallowing up the touch event of child buttons
tapGesture.cancelsTouchesInView = NO;
[pageScrollView addGestureRecognizer:tapGesture];
[tapGesture release];
...
// method to hide keyboard when user taps on a scrollview
-(void)hideKeyboard
{
[myTextFieldInScrollView resignFirstResponder];
}
You can subclass your UIScrollView and override the method - hitTest:withEvent: which is called by the system to determine which view will handle the event. Whenever it is called, you can assume that a touch event occurred inside the scroll view, and by calling the super implementation, you can get the view which would normally process the event.
you can capture any kind of gestures in the UIscrollView. Make sure you also handle some of the default properties as well like set cancelsTouchesInView property to false, it is true by default. Also give some tag nos to your sub views to distinguish in selectors. & also enable their User interaction to true.
let tap = UITapGestureRecognizer(target: self, action:
selector(didTapByUser(_:)))

Resources