I'm trying to create a scrollview with paging that is smaller than the screen size to snap between the buttons in my scrollview. After much headache and searching I found a solution to create the custom paging size explained below. The problem is the buttons inside my scrollview aren't clickable because the hitTest is overriding it?
This answer solves half the problem https://stackoverflow.com/a/6948934/12264367 ... the code works to create the snapping effect but my buttons aren't clickable I'm guessing because the hitTest but I'm not sure how to fix that?
-(UIView *) hitTest:(CGPoint) point withEvent:(UIEvent *)event
{
UIView* child = [super hitTest:point withEvent:event];
if (child == self && self.subviews.count > 0)
{
return self.subviews[0];
}
return child;
}
Related
I have been trying to forward touch events from one view to a UITableView. But I have come across a problem.
In the figure is a prototype view to illustrate the problem. The white view in the bottom half is a normal UITableView. The yellow view is a UIView that forwards touches to the middle of the UITableView using hitTest:withEvent:. The red view is a UIButton.
The goal is to forward the up and down panning touches from the yellow view to the UITableView at the bottom. However, tapping the red UIButton should still work.
I have tried 2 main approaches both of which have problems.
The first approach is to add UIPanGestureRecognizer to the yellow view that can be used to set the contentOffset of the UITableView. The problem is that inertial scrolling of the table view does not work with this approach.
The second approach is to forward touches using hitTest:withEvent: in the yellow view.
-(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
if([self.button_red pointInside: [self convertPoint:point toView:self.button_red] withEvent:event]) {
return self.button_red;
} else {
return [self.view_table hitTest:CGPointMake(self.view_table.width/2.0,self.view_table.height/2.0) withEvent:event];
}
}
hitTest:withEvent: is only called when the touch starts in the yellow view. In other words, whenever a drag starts in the red view it won't forward the drag to the table view. Which is not good enough.
Is it possible to forward a pan gesture from the yellow view to the UITableView in a different way that keeps the red button working AND allows drags to work that start in the red button? Should I override the touch event methods in the yellow view to get this to work somehow? Is there way to do all this while keeping the inertial scrolling?
You need to add yellowView as a subview inside the UITableView.
You will also need to take care of the contentInset in order to
accommodate for yellowView height.
By default, cells will appear over this added yellowView, you need
to fix this by adjusting zPosition on the yellowView.
CGFloat height = CGRectGetHeight(yellowView.frame);
// fix yellowView's y position
yellowView.frame = CGRectMake(0, -height, CGRectGetWidth(yellowView.frame), height);
[tableView addSubview:yellowView];
yellowView.layer.zPosition = 10; // make it appear over cells
[tableView setContentInset:UIEdgeInsetsMake(height, 0, 0, 0)];
Hope this helps.
I have a map and above it UIScrollView with a subview of an UITableView (there is a map and you can scroll the tableView up and down on the screen using the scrollView).
I'm using -(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event to detect rather the user touches the tableView or the map in the background (if he's scrolling the tableView it will go up and down and if he's scrolling the map the map will change the position (like any regular map)).
The code:
-(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
CGPoint comparePoint=CGPointMake(point.x, point.y-[[UIScreen mainScreen] bounds].size.height+self.allCellsHeightDividedBy2);
if (comparePoint.y<self.contentOffsetRecivied.y) {
return nil;
}
return self;
}
It's all working fine but the problem is that you can't select a cell in the tableView for some reason, it just don't select it.
Does anybody have an idea? Thanks!
hitTest:withEvent: is supposed to return the deepest subview which the point intersects with. Since you're only returning self, subviews can't be touched.
You should probably replace return self; with return [super hitTest:point withEvent:event].
i'm trying to implement a specific tableview behaviour (Like on Facebook app).
I want to have a dynamic header that will be magnified every time the user scrolls up and will be shrieked when the user scroll down.
In addition i want the tableview to cause the effect of pushing the header and than scrolling the tableview cells.
I used the method:
- (void)scrollViewDidScroll:(UIScrollView *)aScrollView
in this method i calculated the offset and the direction and called a method that shrink or magnify the header accordingly
so far so good.
The thing is that the animation is being performed with the tableview scrolling.
To avoid it, I created a custom scrollview on to of the top of my tableview, I taged the two scrollviews differently.
In the scrollview i created a weak reference of the tableview and a boolean value that indicated if the scrollview should return the tableview touch.
When the shrinking\magnifying animation was finished i changed the boolean value so it will signal the custom scrollview to return the tableview in my HitTest methods that i implemented inside the scrollview.
But hitTest not called when the user keep scrolling (without leafing the finger), in additions now my buttons inside my tableViewCell aren't reacting.
Here is my HitTest Method:
- (UIView*)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
UIView* result = [super hitTest:point withEvent:event];
if (_recieveTouchOnTable)
{
return _table;
}
else
return result;
}
Here is my scrollViewDidScroll method:
(onProgress means that the animation is being performed, so keep returning the custom scrollview)
Tag = 2 = the custom scrollview
Tag = 1 = the tableview
- (void)scrollViewDidScroll:(UIScrollView *)aScrollView {
CGFloat yVelocity = [aScrollView.panGestureRecognizer velocityInView:aScrollView].y;
CGFloat offset = lastCustomScrollViewContentOffset.y-aScrollView.contentOffset.y;
lastCustomScrollViewContentOffset =aScrollView.contentOffset;
if (yVelocity<0)
offset = fabs(offset)*-1;
else if(yVelocity>0)
offset = fabs(offset);
if (offset!=0 && aScrollView.tag == 2)
[self layoutViewAccorrdingToTableviewScorlingVelocity:offset];
if (!onProgress ){
customScrollView.recieveTouchOnTable=YES;
}
}
Am i missing something, or maybe there's a more simple way to do it?
I'am facing a little problem with my App that has a bit complicated structure :
So first things first the views hirearchy :
|- UICollectionView (horizontal scrolling)
-> UICollectionViewCell
|-> UIScrollView A (PagingEnabled = YES, vertical scrolling only)
|-> UIImageView = page 1
|-> UIScrollView B (PagingEnabled = NO, vertical scrolling only) = page 2
|-> UIView (content...)
The problem is that when I scroll on UIScrollView A to make page 2 appear, the first scroll to the see the bottom on UIScrollView B is ignored, no scroll at all, but the second one has the right behaviour.
I think that it's a kind of "focusing" problem, but I can't figure out how to make this work properly.
Any clue on how to give this first touch on page 2 to the right UIScrollView ?
Have you tried hitTest:withEvent: method? This method helps you choose which view should receive the touch.
For example, you could subclass UIScrollView and set your UIScrollView A's class the new class you just created. Adding the code below to this custom UIScrollView class will let you deliver the touch to it's child view's first
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
for (UIView *subView in self.subviews) {
// Convert the point to the subView's coordinate system.
CGPoint pointForTargetView = [subView convertPoint:point fromView:self];
if (CGRectContainsPoint(subView.bounds, pointForTargetView)) {
// Calling subView's hitTest:withEvent get the right view
return [subView hitTest:pointForTargetView withEvent:event];
}
}
UIView *hitView = [super hitTest:point withEvent:event];
// // Uncomment if you want to make the view 'transparent' to touches:
// // meaning the parts of the view without any subview will deliver the touch
// // to the views 'behind' it
// if (hitView == self) {
// return nil;
// }
return hitView;
}
For more detailed info please check WWDC - Advanced Scrollviews and Touch Handling Techniques (around 15:00 is where this subject starts).
Also, thanks to Tim Arnold for the clue.
I have a UITableview with a subclass of UIVIew on top of a section of the table, like so:
-uitable--
|--uiview-|
|| ||
|| ||
|---------|
| |
| |
-----------
The view has the same superview as the tableview, but covers the tableview partially. There are a few buttons on the UIView. I want the user to be able to scroll on the view and subsequently move the tableview (as if he were scrolling on the table). But, if he taps a button on the view, I want that tap to register on the view, not get sent down to the tableview. Right now, I am overriding the view's - (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event method to always return NO, which works for the scrolling but has the effect of sending all touches down to the tableview, rendering my buttons ineffective. Is there any way for me to pass down swipe/pan gestures to the tableview but keep the tap gestures?
Thanks,
Instead of always returning NO from pointInside, check to see if the point intersects any of your UIButton subviews - and return YES if it does.
- (BOOL) pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
for ( UIView* sv in self.subviews )
{
if ( CGRectContainsPoint( sv.frame, point) )
return YES;
}
return NO;
}
EDIT: alternate solution
Per your comment you'd like the user to begin scrolling the UITableView with a touch-down on one of the subview buttons. To make this work you'll need to make the UIView that contains your buttons a subview of the UITableView.
By doing this the UIView will then begin to scroll along with the UITableViewCells. To prevent this you need to adjust the frame as scrolling happens, or possibly lock it in place using constraints.
- (void) viewDidLoad
{
[super viewDidLoad];
// instantiate your subview containing buttons. mine is coming from a nib.
UINib* n = [UINib nibWithNibName: #"TSView" bundle: nil];
NSArray* objs = [n instantiateWithOwner: nil options: nil];
_tsv = objs.firstObject;
// add it to the tableview:
[self.tableView addSubview: _tsv];
}
// this is a UIScrollView delegate method - but UITableView IS a UIScrollView...
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
[scrollView bringSubviewToFront:_tsv];
CGRect fixedFrame = _tsv.frame;
fixedFrame.origin.y = 100 + scrollView.contentOffset.y;
_tsv.frame = fixedFrame;
}