I have a UITableView which is embedded in the root view of the ViewController. This UITableView contains multiple columns. And these columns can not be shown in one screen, so I make the UITableView can scroll horizontally. Here is the code which make the tableview scrolling horizontally:
- (void)viewDidAppear:(BOOL)animated
{
CGSize size = self.tableView.contentSize;
size.width = 450;
self.tableView.contentSize = size;
[self.tableView layoutIfNeeded];
}
This works fine and the table can scroll horizontally and vertically. And in the original screen(where no scroll happening), I can select one row and the selected row can be highlighted.
What my problem is: If I scroll the UITableView a little bit left, and in the area where is not shown before scrolling, the tap event cannot be responded by the UITableView. And thus, the row under my finger will not be selected and highlighted. But I can scroll if I swipe in the same area.
Anyone knows what I am doing wrong? Appreciated for your comments and thanks in advance.
I understand your problem and there is always a simple way to do a complex problem.Your required functionality can be achieved by using UICollectionView.
In your UITableViewCell add UICollectionView.In this approach your code will get cleaner and you can achieve your functionality without any headache.
Here is very simple tutorial about UICollectionView.
http://www.appcoda.com/ios-programming-uicollectionview-tutorial/
At last, I think this is a bug of UITableView. I get a workaround for the problem by subclass UITableView. Here it is:
// in my own UITableView subclass .m file
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
NSLog(#"MonthDetailTableView: touchesEnded");
UITouch * touch = [touches anyObject];
CGPoint point = [touch locationInView:self];
if (point.x < self.frame.size.width)
{
[super touchesEnded:touches withEvent:event];
}
else
{
// if not adjust by mod, the NSIndexPath returned by indexPathForRowAtPoint
// will be nil. I think this is a bug of UITableView
point.x = (int)point.x % (int)self.frame.size.width;
NSIndexPath *path = [self indexPathForRowAtPoint:point];
[self selectRowAtIndexPath:path animated:NO scrollPosition:UITableViewScrollPositionNone];
if (self.delegate != nil &&
[self.delegate respondsToSelector:#selector(tableView:didSelectRowAtIndexPath:)])
{
[self.delegate tableView:self didSelectRowAtIndexPath:path];
}
}
}
Related
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 am trying to insert a single custom image as the accessoryView for the cells of a UITableView. Yet I have a funny effect: at first the icon is inserted any two cells, then when I scroll up, all of the icons disappear and the image just shows on the bottom cell, to disappear as soon as the next one is shown scrolling. This is the code I am using:
NearCell *cell = [myTableView dequeueReusableCellWithIdentifier:CellIdentifier];
nearContents* contents=[[self sourceForTableKind:tableView==self.myTableView favorites:NO] objectAtIndex:indexPath.row];
cell.bus.text=contents.bus;
cell.destination.text=contents.destination;
cell.selectionStyle=UITableViewCellAccessoryDetailDisclosureButton;
cell.accessoryView=[[UIImageView alloc] initWithImage:[UIImage imageNamed:#"pinPoint"]];
return cell;
While in the UITableViewCell subclass nearCell I have:
- (void)layoutSubviews {
[super layoutSubviews];
CGRect accessoryFrame = self.accessoryView.frame;
accessoryFrame.size.height = self.frame.size.height;
accessoryFrame.size.width = self.frame.size.height;
accessoryFrame.origin.y=self.frame.origin.y+21;
accessoryFrame.origin.x=self.frame.origin.x+self.frame.size.width- accessoryFrame.size.width-5;
self.accessoryView.frame = accessoryFrame;
}
I also tried to allocate the accessoryView lazily, thinking the problem could have been with the repeated allocations, but if I use the same image in all the cells the image is not shown altogether.
That is how the thing shows, the icon is just in the last cell :
:
I also installed it in another tableView and there, for some reason, the layoutSubview of the subclass is not called and the accessoryViews regularly appears on all cells, albeit at a wrong size. Thus the problem seems resting in the layoutSubview callback, even if I ignore what it might be.
The problem in both cases, though, is that the accessoryButtonTappedForRowWithIndexPath callback is not called upon touching the image, rather the didSelectRowAtIndexPath callback is invoked instead.
In fact when I dispensed of the layoutSubviews callback (by manually resizing the image), all the images are correctly shown even on the original table. The only persistent problem is that the accessoryButtonTappedForRowWithIndexPath is not called upon clicking the image.
I found this solution to also solve the accessory callback problem:
Using a custom image for a UITableViewCell's accessoryView and having it respond to UITableViewDelegate
I am actually managing the issue with a block in order to pass some internal parameters. This is the final solution:
nearContents* contents=[[self sourceForTableKind:tableView==self.myTableView favorites:NO] objectAtIndex:indexPath.row];
cell.bus.text=contents.bus;
cell.destination.text=contents.destination;
cell.selectionStyle=UITableViewCellAccessoryDetailDisclosureButton;
cell.accessoryView=button;
[self registerButton:button blockForKind:tableView==self.myTableView favorites:NO];
-(void) registerButton:(UIButton*)button blockForKind:(BOOL)normal favorites:(BOOL)favorites{
[button addEventHandler:^(id sender, id event) {
NSSet *touches = [event allTouches];
UITouch *touch = [touches anyObject];
CGPoint currentTouchPosition = [touch locationInView:self.myTableView];
NSIndexPath *indexPath = [self.myTableView indexPathForRowAtPoint: currentTouchPosition];
if (indexPath != nil)
{
[self tableView: self.myTableView accessoryButtonTappedForRowWithIndexPath: indexPath];
}
}
forControlEvents:UIControlEventTouchUpInside];
}
I overrode hitTest, and that works just fine. I want it to behave as if I hadn't overridden this method under certain conditions, and that's where the problem lies.
I'm using a subclassed UICollectionView to render cells over a MKMapView using a custom UICollectionViewLayout implementation. I needed to override hitTest in the UICollectionView subclass so that touch events can be passed to the mapView and it can be scrolled. That all works fine.
I have a toggle mechanism which animates between my UICollectionViewLayout (map) to a UICollectionViewFlowLayout (animate items on a map to a grid format). This works good too, but when I'm showing the flow layout, I want the user to be able to scroll the UICollectionView like a normal one (act as though hitTest isn't overridden). I can't figure out what to return in hitTest to have it's default behavior.
-(UIView*)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
if(self.tapThrough == YES){
NSArray *indexPaths = [self indexPathsForVisibleItems];
for(NSIndexPath *indexPath in indexPaths){
UICollectionViewCell *cell = [self cellForItemAtIndexPath:indexPath];
if(CGRectContainsPoint(cell.frame, point)){
return cell;
}
}
return nil;
} else {
return ???
}
}
I've tried returning a number of things. self, self.superview, etc... Nothing get it to behave normally (I cannot scroll the cells up and down).
If you want the normal behaviour of your hit test:
return [super hitTest:point withEvent:event];
This will return what the hit test usually returns when it is not overridden.
I have a draggable view (UIImageView) which I control its positioning and dragging using the UIPanGestureRecognizer and this method:
- (void)imageDragged:(UIPanGestureRecognizer *)gesture
{
UIImageView *img = (UIImageView *)gesture.view;
CGPoint translation = [gesture translationInView:img];
img.center = CGPointMake(img.center.x + translation.x,
img.center.y + translation.y);
// reset translation
[gesture setTranslation:CGPointZero inView:img];
}
Other than the draggable view, I also have a UICollectionView, built of course by UICollectionViewCells. What Im trying to do, is to identify when my draggable view is dragged on top of one of the collection view cells.
I thought of adding a boolean to the imageDragged method, to know when the view is currently being dragged, but I wasn't able to figure out how to know when my dragged touch is on top of a collection view cell. The UICollectionView has the didSelect delegate method, but that doesn't really help me if my tap didn't occur on the cells, but on my draggable view.
Any help would be much appreciated!
I put this snippet inside the imageDropped method to achieve what I wanted (thanks to Martin's help):
CGPoint tapCoordinates = [gesture locationInView:self.view];
NSArray * visibleCells = [self.collectionView visibleCells];
for(UICollectionViewCell *cell in visibleCells) {
CGRect frame = [self.collectionView convertRect:cell.frame toView:self.view];
if(CGRectContainsPoint(frame, tapCoordinates)) {
NSLOG(#"Success");
break;
}
}
I don't know if there's a tidier way, but perhaps you can get the point of the tap and/or some points on the corners of the dragged UIView and then use CGRectContainsPoint to see if those points are within the rect of the collection view cell.
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;
}