I'm building out a UITableView that has a button in the tableview's section header, so that as the user scrolls down the screen, the button is pinned to the top of the screen.
The problem is that the touch events for the section header's button don't register when the UITableView is moving. The button only gets the touch event if the tableview is not scrolling.
-(UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
if (!_sectionHeaderView) {
_sectionHeaderView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, 200)];
UIButton *backButton = [UIButton buttonWithType:UIButtonTypeCustom];
backButton.frame = CGRectMake(0, 0, 50, 50);
[backButton setImage:[UIImage imageNamed:#"upArrow"] forState:UIControlStateNormal];
[backButton addTarget:self action:#selector(scrollToTop:) forControlEvents:UIControlEventTouchUpInside];
[_sectionHeaderView addSubview:backButton];
}
return _sectionHeaderView;
}
Currently, what happens is:
- If the tableview is not scrolling, the button performs as you'd expect: tap it, and the button gets the TouchUpInside gesture.
- If the tableview is scrolling, the button doesn't get the TouchUpInside gesture - instead, the tableview just stops in place.
I've tried subclassing the UITableView and looking at the touchesBegan: methods, etc and that didn't work. I see the same pattern: the gestures only show up if/when the tableview isn't moving.
Update:
This isn't a usability issue - while I can't show screenshots of the design I'm working on, it's a valid use case to use buttons & controls in the section header.
Here's a quick sketch to explain why:
Figured it out! All you need to do is subclass UITableView, and override hitTest:withEvent:
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
if ([[super hitTest:point withEvent:event] isKindOfClass:[UIButton class]]) {
UIButton *buttonThatWasTapped = (UIButton *)[super hitTest:point withEvent:event];
[buttonThatWasTapped sendActionsForControlEvents:UIControlEventTouchUpInside];
}
return [super hitTest:point withEvent:event];
}
Here's what this code does: if the tableview is touched, and the touch occurred in the bounds of a UIButton, the tableview will forward the touch event to the UIButton.
There's a caveat here: the touch event may get sent to the UIButton several times (I'm seeing the call being made 3 times for each touch). This code was sufficient for my needs, but if you're using this code in your app, watch out for that issue - you may have to modify the above code to account for this.
This is what's supposed to happen.
The user is used to this behavior - it allows them to stop the table view's scrolling when they see what they wanted to touch.
Imagine I was scrolling through a table looking for the row 'unicorns', and I was scrolling really fast (because it's a big table, and I'm not sure where unicorns is). When 'unicorns' crosses my vision, I will instinctively stop the table to select it.
With the current system, I can tap anywhere on the table view and it will stop. If table views were implemented your way, I would tap 'underground rockchucks' to stop the table, and all of a sudden I am looking at information about rockchucks - and I want information about Unicorns.
I now become a disgruntled user, return your app to Apple, and get my $0.99 back.
You don't want users doing that.
Related
I have a small 320x144viewcontroller named SubViewController.h which has a UITableView in it with 3 cells with a single section. I have made the tableView unscrollable and also put some shadow effect behind the tableView by grace of CALayer.
In another viewcontroller named as MainViewController.m i have added SubViewController.h as a subview to this MainViewController. Using UIPanGestureRecognizer i have successfully able to drag the SubViewContoller anywhere i want.
I make this subView visible with a UIBarButtonItem. And after selecting a cell in the tableView of the subview i made it disappear from main view with some animation.
Everything works fine.
But when i drag the subview and then try to select one cell i have to tap the cell twice. In first tap nothing actually happens except the cell turns blue(like it happens normally when you select a cell in tableView) but does not go Hidden. If i tap again then it will go hidden.
Without dragging the subview i can select one cell with a single touch and also the view goes hidden.
I have written the code for hiding the subview in didSelectRowAtIndexPath: method of the subview. And I have checked this method is not called when i select first time after dragging the subview.In the second tap or touch it is called though. And again if the user moves the subview again same problem occurs.
Surely some property of the subview got changed after dragging which i cant able to figure out.
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
NSUInteger tapCount = [touch tapCount];
switch (tapCount) {
case 1:
[self performSelector:#selector(singleTapMethod) withObject:nil afterDelay:.4];
break;
case 2:
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:#selector(singleTapMethod) object:nil];
[self performSelector:#selector(doubleTapMethod) withObject:nil afterDelay:.4];
break;
. . .
}
First when u want your subView to be shown ,that is on click of your UIBarButtonItem:
-(IBAction)buttonClick
{
//setup ur view dynamically as you like//
PSview=[[UIView alloc]initWithFrame:CGRectMake(5, 5, 310,450)];
PSview.backgroundColor=[UIColor blackColor];
PSview.alpha=0.8;
[PSview.layer setBorderColor: [[UIColor whiteColor] CGColor]];
[PSview.layer setBorderWidth: 3.0];
PSview.contentMode=UIViewContentModeScaleAspectFill;
PSview.clipsToBounds=YES;
[PSview.layer setBorderColor: [[UIColor whiteColor] CGColor]];
[PSview.layer setBorderWidth: 3.0];
[PSview addSubview:subView];
[self.view addSubview:PSview];
}
then later :
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
//since there are two tables in one view, you can differentiate them using if()
if(tableView==subView)
{
// ...ur code . ..
// write your code what needs to happen when you click a row of your subView.
[PSview removeFromSuperview];
}
if(tableView==mainView)
{
// write your code , what happens when user clicks row of the main table
}
}
I have a scrollview in my Main view and I have three subviews on my scrollview. And I have UIButtons in all my subviews.
Now, I want to drag those buttons from one subview to another subview (while dragging the buttons, the scrollview should not scroll).
How do I do this?
I'm not completely sure if this snippet works for this particular case (an UIControl inside a UIScrollView), but my understanding of UIResponder chain suggests me that it should :)
- (void)viewDidLoad { // or wherever you initialize your things
...
// Add swipe event to UIButton so it will capture swipe intents
UIPanGestureRecognizer *panGR = [[UIPanGestureRecognizer alloc] init];
[panGR addTarget:self action:#selector(panEvent:)];
[button addGestureRecognizer:panGR];
[panGR release];
}
- (void)panEvent:(id)sender {
button.center = [sender locationInView:self.view];
}
If this works (can't test it right now, but it did work for me in a similar situation), then you should add more code to handle the drag & drop related events (maybe disable Clip Subviews option in the UIScrollView, add the button to the new superview if the location intersects with the CGRect of the destination, return the button to the original location if it doesn't, etc).
So, what's happening in those lines? When you begin touching the UIButton, the order doesn't get to the UIScrollView because the event could follow as a touch event (handled by the UIButton), or as a pan event (handled by the UIScrollView). When you move your finger, the event is dismissed by the UIButton's responder because there's no Gesture Recognizer that knows how to proceed if the finger is moved.
But when you add a Gesture Recognizer to the UIButton who actually knows what to do when the finger is moved, everything is different: the UIButton will not dismiss the event, and the UIScrollView will never realize that there was a touch moving over it.
I hope my explanation is accurate and comprensible enough. Let me know if a) it doesn't work or b) there's something unclear.
Good luck :)
Try
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
if (allowAppDrag && [gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]]) {
return NO;
}
return YES;
}
I have an issue regarding Touches, maybe several people have had it though.
I have a view that has some Labels and imageviews on it. If i disabled the touches of my View by saying userinteraction disabled then all the touches of all subviews get disabled, what if i may want to have touches enabled for a few and disabled for a few, when userinteraction of view is disabled.
Is this the only solution: Create two seperate views, out of which one's user interaction would be enabled and others' would be disabled and implement my stuff on top of it?
Regards,
Reno Jones
Usually UILabel and UIImageView don't have touch event by default. So you only need to enable touch event where you want to have them.
i strongly recommmend to use always UIButton like this:
- (void)viewDidLoad {
UIButton *buttonActionA = [[UIButton alloc] initWithFrame:CGRectMake(50.0, 50.0, 100.0, 100.0)];
[buttonActionA addTarget:self action:#selector(doActionA) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:buttonActionA];
[buttonActionA release];
}
- (void) doActionA {
NSLog(#"This is Action A");
}
and avoid subclassing UIView just to implement
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
did that answer your question ?
Right now I have 2 different UIPickerView in side my UITableViewController. I only show them upon tapping of certain cells in the table. What I'm trying to do is to hide the pickers whenever I touch outside the pickers. Is there a delegate method or something similar to achieve this? I prefer to keep my controller as a UITableViewController instead of a simple UIViewController since I have a textView in one of the cells and scrolling after the keyboard shows is just a bit too much in a UIViewController.
Thanks in advance.
One of the Possible solutions is that when a particular cell is tapped and you handle picker (to present the picker), you can insert a view called as MASK View over the tableview. (with Frame as self.tableview.frame - yourPicker.frame.size.height ). Now when ever you get any click on this view you can handle it as follows
-(void)showMaskView{
if (!viewMaskView) {
CGRect viewRect = CGRectMake(0, 0, self.tableView.frame.size.width, self.tableView.frame.size.height - yourPicker.frame.size.height);
viewMaskView = [[MaskView alloc] initWithFrame:viewRect];
viewMaskView.delegate = self;
}
[self.view addSubview:viewMaskView];
[self.view bringSubviewToFront:viewMaskView];
}
-(void)removeMaskView{
if (viewMaskView) {
[viewMaskView removeFromSuperview];
}
//Remove the Picker
}
In the MaskView class you can handle the touch as follows
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
if(self.delegate && [self.delegate respondsToSelector:#selector(removeMaskView)])
[self.delegate removeMaskView];
}
you can see the colored mask view over the Picker in the image. When tapped it removes picker.
I have an NSMutbleArray with images. I puted the first image in UIIageView and then I want that the user navigate between images. Should I make button or there is other method? Can I do it when user touch the image, like photo on the ios?
I have another question please. Some time I see this kind of button, but when I select .xib , I don't find them?
I have only basical button on object libraries
help please ( and sorry about my english )
Please watch the "Designing Apps with Scroll Views" talk from WWDC 2010.
Start here, log in, then look for that title, then click 'Watch in iTunes'.
Ummmmm, for a place to start:
-(void)viewDidLoad {
UIButton *image = [UIButton buttonWithType:UIButtonTypeCustom];
[self setCurrentIndex:0];
[image setImage:[array objectAtIndex:0] forState:UIControlStateNormal];
[image addTarget:self action:#selector(nextImage:) forControlEvents:UIControlEventTouchUpInside]
[[self view] addSubview:image];
}
-(void)nextImage:(id)sender {
UIButton *button = (UIButton *)sender;
[self setCurrentIndex:[self currentIndex] + 1];
if([self currentIndex] >= [array count]) {
[self setCurrentIndex:0];
}
[button setImage:[array objectAtIndex:[self currentIndex]] forState:UIControlStateNormal];
}
You will need to keep a reference variable named currentIndex to keep track of where you are in the array and which image to display the next image. This is a pretty awkward way to do it, but probably the easiest to understand and fastest to code.
To answer your second question: Those buttons are for mac applications, not iPhone applications. You will not see them when you create a xib because the iPhone doesn't support them.
Let me answer your second question first. The little control next to the pop-up menu controls how the library items are displayed:
Regarding your first question, I suggest you use swipe gesture recognizers. First, in your view controller, make actions for the swipe recognizers to send:
MyViewController.h
- (IBAction)showPriorImage;
- (IBAction)showNextImage;
MyViewController.m
- (IBAction)showPriorImage
{
// You'll need to change this based on how you track the current image.
if (self.currentImageIndex > 0)
self.imageView.image = [self.images objectAtIndex:--self.currentImageIndex];
}
- (IBAction)showNextImage
{
// You'll need to change this based on how you track the current image.
if (self.currentImageIndex < self.images.count - 1)
self.imageView.image = [self.images objectAtIndex:++self.currentImageIndex];
}
Then, in your XIB, find the swipe gesture recognizer in the library:
Drag a swipe gesture recognizer onto your image view. In the Attributes Inspector, set the swipe direction to Left. Then find the recognizer in the object list (on the left side of the XIB) and control-drag from it to File's Owner. Choose showPriorImage from the pop-up.
Now drag another swipe gesture recognizer onto your image view. Set its swipe direction to right, and connect it to the showNextImage action on your File's Owner.