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.
Related
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];
}
}
}
I would like to fire an event when the subview of a UITableviewCell reaches a certain point on the screen, say for example when its origin.y reaches 44 points. It would also be nice to know if it was being scrolled up or down when it reached that point. I was playing with KVO on the frame of the subview but this seems fixed to the cell so no changes with that. Is this task possible?
Vertical position of UITableViewCell is defined by its frame property, which represents position and size of that cell within its superview, UITableView. Typically, the frame property of the cell is changing only once for every time that UITableView requests a cell from its delegate for specific index path. That's it, UITableView gets a cell, places it in itself and that cell just lays there unchanged until rectangle stored in bounds property of UITableView ceases to include rectangle stored in the frame property of that cell. In that case UITableView marks that cell as hidden and places it into the pool of reusable cells.
Since the process of scrolling in essence is not a repositioning of subviews – it is merely a curious illusion of shifting a bounds viewport of UITableView – constant observing of UITableViewCell's properties are pointless.
Moreover, the frame property of subview of UITableViewCell also represents a position and size of that subview within its container, UITableViewCell. It is also will not change on scroll.
You need to observe changes in UITableView bounds property, which is also represented by contentOffset by the way. UITableView happens to be a subclass of UIScrollView, so you can use its delegate methods, such as -scrollViewDidScroll:, like in this simple example:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
UITableView *tableView = (UITableView *)scrollView;
// current position
CGFloat currentY = tableView.bounds.origin.y;
// current inset
CGFloat currentInset = tableView.contentInset.top;
// trigger line position
CGFloat triggerY = currentInset + currentY + kYourTriggerPosition;
// nice visual mark
UIView *line = [tableView viewWithTag:88];
if (!line) {
line = [[UIView alloc] initWithFrame:CGRectZero];
line.tag = 88;
line.backgroundColor = [UIColor redColor];
[tableView addSubview:line];
}
line.frame = CGRectMake(0, triggerY, tableView.bounds.size.width, 1);
// determine scroll direction
BOOL scrollingUp = currentY > self.previousY;
// all visible cells
NSArray *visibleCells = tableView.visibleCells;
[visibleCells enumerateObjectsUsingBlock:^(UITableViewCell *cell, NSUInteger idx, BOOL *stop) {
// subview
UIView *subview = [cell viewWithTag:kYourSubviewTag];
// subview frame rect in UITableView bounds
CGRect subviewRect = [subview convertRect:subview.frame toView:tableView];
// trigger line within subview?
BOOL triggered = (CGRectGetMinY(subviewRect) <= triggerY) && (CGRectGetMaxY(subviewRect) >= triggerY);
if (triggered) {
NSIndexPath *indexPath = [tableView indexPathForCell:cell];
NSLog(#"moving %#, triggered for cell at [%2d:%2d]", #[#"down", #"up"][scrollingUp], indexPath.section, indexPath.row);
[tableView selectRowAtIndexPath:indexPath animated:NO scrollPosition:UITableViewScrollPositionNone];
}
}];
// save current position for future use
self.previousY = currentY;
}
Reach that subview of UITableViewCell with cellForRowAtIndexPath or tableView.visibleCells, then call convertRectToView: on that subview.
convertRectToView: allows you to do translations on different coordinate systems. For example, you can detect where that subview appears on screen by translating its frame within its superview into viewController.view
For more: Apple Documentation
Since I can not comment I am writing as an Answer
Changing the answer for the requirement.
Here is how I think it can be done, you need to have your custom UITableViewCell which has a function which can take in co-ordinates (again based on your logic if you just want an intersection where a cell just touches a boundary or if it has to be at a precise position in a frame), so your function would take the co-ordinates and will return a true and a false if it will tell you if the condition is met, and in your cellForTable function you call the function of UITableView cell to check if your condition is met, if it is in your view you create a subview at the exact location. You can also modify the function to return you the exact frame-cordinates so you can use them to create a subview\
Here's a simple approach, which you can use if you have only one section without section header.
Add this to your implementation:
CGFloat lastContentOffSet;
And then add this delegate method of scrollview as tableview is also a scrollview.
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
CGFloat cellHeight = 50;
CGFloat touchingPoint = 44.0f;
NSInteger rowNo = floor(scrollView.contentOffset.y / cellHeight);
NSInteger startPoint = (rowNo * cellHeight);
if (scrollView.contentOffset.y > lastContentOffSet) {
NSLog(#"Row %ld scrolled down", (long)rowNo);
if (scrollView.contentOffset.y > startPoint + touchingPoint) {
// Do something here
NSLog(#"Do something here");
}
}
else {
NSLog(#"Row %ld scrolled up", (long)rowNo);
if (scrollView.contentOffset.y > startPoint + touchingPoint) {
// Do something here
NSLog(#"Do something here");
}
}
lastContentOffSet = scrollView.contentOffset.y;
}
Change value of the cellheight according to your tableview cell and the distance of that subview with the cell.
Let me know if this code helped. :)
I need to implement a drag and drop feature for a UIView added as a subview of UITableViewCell. I catch the drag according to MoveMe example from Apple by using touchesBegan: touchesMoved: events.
This is the view that is added inside the cell:
The gradientview is the subview of the cell. I need to move the white rectangles only (white boxes are the subviews of the orange-blue gradient view). My problem is that after a few pixels of upward or downward dragging the tableview starts to scroll. Horizontaly it is OK. Seems that the tableView catches the drag after a certain amount of drag. I need to prevent the vertical scroll until the drag is ongoing.
Is there a solution to prevent this from happening?
OK. I ended up using UIPanGestureRecognizer. That seems to block tableview underneath.
- (void)move:(UIPanGestureRecognizer*) recognizer {
CGPoint translation = [recognizer translationInView:self];
[recognizer setTranslation:CGPointMake(0, 0) inView:self];
recognizer.view.center = CGPointMake(recognizer.view.center.x + translation.x,
recognizer.view.center.y + translation.y);
}
I have a UICollectionView(One Row, Vertical align) with a UIPageControl associate with it.
The UIPageControl will have number of dots as the the number of items in my collection view.
I want to change/scroll the position of the collectionView when a dot is pressed.
Moreover, I want change the selected dot according to the scrolling of the collection view.
Any idea about some kind of algorithm for the scrollViewDidScroll method too accomplish that?
Thanks in advance
I got a simple solution.
Under the method scrollViewDidScroll just check the visible cell in the middle of the screen:
CGRect visibleRect = (CGRect){.origin = self.collectionView.contentOffset, .size = self.collectionView.bounds.size};
CGPoint visiblePoint = CGPointMake(CGRectGetMidX(visibleRect), CGRectGetMidY(visibleRect));
NSIndexPath *visibleIndexPath = [self.collectionView indexPathForItemAtPoint:visiblePoint];
[self.pageControl updateStateForPageNumber: (int)visibleIndexPath.row];
If I am understanding it correctly, I think you can do it via collectionView:cellForItemAtIndexPath: method of the UICollectionViewDataSource
Whenever an item is requested from the data source you can select the relevant dot on UIPageControl.
Did this help ?
I have a UICollectionView embedded in a scroll view:
Each white square is a collection view cell. The user can scroll horizontally to reveal additional cells.
When the user clicks on a cell, I have created an animation which causes that cell to expand from its own center outward as a transition is made to a new view controller.
Here is the code:
//cell is selected:
//grab snapshot of cell
UICollectionViewCell *cell = [collectionView cellForItemAtIndexPath:indexPath];
UIImage *cellImage = [self imageOfCollectionCell:cell];
//hold the snapshot in this dict
NSMutableDictionary *cellImageDict = [[NSMutableDictionary alloc]init];
[cellImageDict setObject:cellImage forKey:SELECTED_INBOX_EMAIL_IMAGE];
[ViewControllerSnapShotDictionary sharedInstance].vcdict = nil;
[ViewControllerSnapShotDictionary sharedInstance].vcdict = cellImageDict;
//get the center of the cell (HERE IS WHERE THE PROBLEM IS)
CGPoint cellCenter = CGPointMake(cell.center.x, cell.center.y);
//do the animation using the snapshot and cell center
[self startPinchingOutCellFromCenterPoint:cellCenter forTransitionTo:emailController withSnapShotImage:SELECTED_INBOX_EMAIL_IMAGE];
The code works fine, EXCEPT if the collection view has been scrolled. The animation requires that I know where the center of the cell is at the moment it is on screen, being touched and relative to the coordinates of the view I am looking at.
For example, if the collection view has not been scrolled, and I select the center cell of the above image, the center might return as:
cell.center.x = 490
cell.center.y = 374
However, if I do a scroll to the right, and then select the new center cell, I might get something like:
cell.center.x = 1770
cell.center.y = 374
My question is, is there either a better way to accomplish what I am trying to do, OR is there a way to get a handle on the center of the cell as it lies in its current position in self.view?
I think this is because the center coordinates are in the collectionView's coordinate system (which scrolls). You need to convert it to a coordinate system that doesn't scroll.
Try something like this:
CGPoint realCenter = [collectionView convertPoint:cell.center
toView:collectionView.superview];
What it does, is basically converts the center from the collectionView's coordinate system to it's parent which should be fixed (not scrolling).
You can even obtain the coordinate on the screen(window) by passing nil as parameter:
CGPoint realCenter = [collectionView convertPoint:cell.center
toView:nil];
It's up to you to decide to which coordinate you want to convert.