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.
Related
I have a UICollectionView that i need to detect which cell is in view.
I already have the content offset already using the code below:
float scrollPoint = self.collectionView.contentOffset.y;
now where i am stuck is at finding which cells are at that point.
How do I find out which cell(s) are at that point.
Use - indexPathForItemAtPoint:(CGPoint)point:
NSIndexPath* path = [self.collectionView indexPathForItemAtPoint:contentOffset];
if (path) {
// Do stuff.
}
Documentation.
Actually, your contentOffset will give you the wrong cell. You probably just want to use the point 0, 0 or whatever to get the cell that's at the left/top of the scrollview.
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 an UICollectionView. When I touch one of the cells I present a popover view from its location with the arrow pointing to the cell with some extra information about the cell.
When I rotate the device, the UICollectionView automatically repositions its cells.
What I'd like to do, is to reposition my popover view automatically so it points to the same cell as before (which is now at a different location in the UICollectionView)
What I'm having trouble is tracking (finding out) what is my cell's new location, so I can manually represent the popover view from the cell's new location.
I tried attaching and storing an "idString" for the cell for comparison, but this for some reason returns the cell's old frame from before the screen rotation
NSArray* visibleCells = [UIAppDelegate.ocollectionView visibleCells];
for (UICollectionViewCell *cell in visibleCells) {
if ([cell.idString isEqualToString:self.idString] ) {
NSLog (#"we have a match!!! %#", cell);
CGRect rectInCollectionView = cell.frame;
rect = [UIAppDelegate.collectionView convertRect:rectInCollectionView toView:[UIAppDelegate.collectionView superview]];
}
}
any ideas appreciated. thank you.
Where is this code being placed? I guess it should be placed on a delegate method like this kind:
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation;
So the UICollectionView has redrawn its new cell's position.
I want to show links between two cells of uiTableView.
For Ex:
To show links between cells 1 and 5, it could be shown like:
Does any one has any idea how this can be achieved. Also when table scrolls, these links should be scrolled with it.
This looks like you want to build hierarchical view. Your implementation might be rejected by Apple due to not taking HIG into account.
Also what will be in case when lower part is not seen to user? Arrow with no end and need to scroll down for the user?
You might want to do a tree like structure (anything hierarchical) instead of ugly (sorry for that) arrows.
If you want arrow between two cell then make a separate UIView class for the Tablecell, in that UIView add one UILabel for text and one UIImageView for arrow, adjust there position as per your requirement.
Now pass this UIView to cell.
Hope this will help you.
UITableViewCell is just a subclass of UIView and UITableView is a subclass of UIScrollView. The only fanciness that UITableView provides is creating/reusing the cells and laying them out in the scroll view. (That's a gross over-simplification but for this It'll do the trick.)
So if I have a UIView subclass that draws an arrow, then it's just a matter of getting the frame for the cells I want to point to. CGRect frame1 = [[self.tableView cellForRowAtIndexPath:indexPath] frame];
Some pseudocode...
topCellFrame = get top cell frame;
bottomCellFrame = get bottom cell frame;
arrow = new arrow view;
arrow set frame = frame with origin of top cell frame origin, and height of distance from topCellFrame to bottomCellFrame;
tableView add subview = arrow;
There are edge cases to think about, If the top cell or bottom cell are offscreen the cellForRowAtIndexPath: will return nil and frame will be CGRectZero.
But I'll leave that as an exercise for you.
Edit: (I haven't done this exact thing, but I have done some similar things with the frames of cells)
This isn't so much a question as an explanation of how to solve this problem.
The first thing to realize is that the UICollectionView does inherit from a UIScrollView - so doing a standard lookup with a scroll view's content is the best solution.
Here's the problem I was addressing:
I had a UICollectionView that had differing items in each cell - along with differing types of cells. I need the selection of a cell to cause an effect of the image in the cell to appear to expand and take over the whole screen. How I did the expansion is for another post.
The challenge was getting the cell's position on the screen so that the animating section would have a reference point from where to start.
So, to facilitate getting this information - consider the following code:
First note:
UICollectionView *picturesCollectionView;
DrawingCell cell; // -> instanceof UICollectionViewCell with custom items.
// first, get the list of cells that are visible on the screen - you must do this every time
// since the items can change... This is a CRITICAL fact. You do not go through the
// entire list of cells - only those the collectionView indicates are visible. Note
// there are some things to watch out for - the visibles array does not match the indexPath.item
// number - they are independent. The latter is the item number overall the cells, while
// the visibles array may have only 2 entries - so there is NOT a 1-to-1 mapping - keep
// that in mind.
NSArray *visibles = [self.picturesCollectionView visibleCells];
// now, cycle through the times and find the one that matches some criteria. In my
// case, check that the cell for the indexPath passed matches the cell's imageView...
// The indexPath was passed in for the method call - note that the indexPath will point
// to the number in your datasource for the particular item - this is crucial.
for (int i=0; i<visibles.count; i++) {
DrawingCell *cell = (DrawingCell *)visibles[i];
if (cell.imageView.image == (UIImage *)images[indexPath.item]) {
// at this point, we've found the correct cell - now do the translation to determine
// what is it's location on the current screen... You do this by getting the contentOffset
// from the collectionView subtracted from the cell's origin - and adding in (in my case)
// the frame offset for the position of the item I wish to animate (in my case the
// imageView contained within my custom collection cell...
CGFloat relativeX = cell.frame.origin.x - self.picturesCollectionView.contentOffset.x + cell.imageView.frame.origin.x;
CGFloat relativeY = cell.frame.origin.y - self.picturesCollectionView.contentOffset.y + cell.imageView.frame.origin.y;
// I now have the exact screen coordinates of the imageView - so since I need this
// to perform animations, I save it off in a CGRect - in my case, I set the size
// exactly to the size of the imageView - so say you were doing a Flicker display
// where you wanted to grow a selected image, you get the coordinates of the image
// in the cell and the size from the displayed image...
UIImageView *image = cell.imageView;
// selectedCell is a CGRect that's global for the sake of this code...
selectedCell = cell.frame;
selectedCell.origin.x = relativeX;
selectedCell.origin.y = relativeY;
selectedCell.size.width = cell.imageView.frame.size.width;
selectedCell.size.height = cell.imageView.frame.size.height;
}
}
// done. I have my coordinates and the size of the imageView I wish to animate and grow...
Hopefully, this helps other folks that are trying to figure out how to say overlay something on the cell in an exact position, etc...
-(void)collectionView:(UICollectionView *)cv didSelectItemAtIndexPath:(NSIndexPath *)indexPath;
{
UICollectionViewLayoutAttributes *attributes = [cv layoutAttributesForItemAtIndexPath:indexPath];
CGRect cellRect = attributes.frame;
CGRect cellFrameInSuperview = [cv convertRect:cellRect toView:[cv superview]];
NSLog(#"%f",cellFrameInSuperview.origin.x);
}
It work for me.You can try yourself
Well the first part of your question is pretty much clear, the second one?? anyway
if what you want to get is the frame of the select cell in your collection you can use this :
UICollectionViewLayoutAttributes *attributes = [self.collectionView layoutAttributesForItemAtIndexPath:indexPath];
CGRect cellRect = attributes.frame;
More info here
#Alivin solution using layoutAttributesForItemAtIndexPath works but only for the presented/current scroll view that the user sees.
Meaning, if you select the first presented visible cells you will get the actual frame, but if you scroll, the frame will have a deviation and you won't get the coordinates you need.
This is why you need to use convertPoint:toView :
let realCenter = collectionView.convertPoint(cell.center, toView: collectionView.superview)
Basically this method takes a point (cell.center) in one view and convert that point to another view (collectionView.superview) coordinate system which is exactly what we need.
Thus, realCenter will always contain the coordinates to the actual selected cell.
I've done this before as well. it took a while but it is possible.
You need to use
[currentImageView.superview convertRect:currentImageView.frame toView:translateView]
Where currentImageView is the image that the user taps. It's superview will be the cell.
You want to convert the rect of your image to where it actually is on a different view. That view is called "translateView" here.
So what is translateView? In most cases it is just self.view.
This will give you a new frame for your imageview that will meet where your image is on your table. Once you have that you can expand the image to take up the entire screen.
Here is a gist of the code I use to tap an image then expand the image and display a new controller that allows panning of the image.
https://gist.github.com/farhanpatel/4964372
I needed to know the exact location of the cell's center that a user tapped relative to the UIWindow. In my situation the collectionView was a child of a view that took up 2/3 of the screen and its superview was a child of another view. Long story short using the collectionView.superView wasn't suffice and I needed the window. I used Ohadman's answer above and this answer from TomerBu to get the tapped location of the screen/window's coordinate system.
Assuming your app has 1 window that it isn't connected across multiple screens, I used both of these and the same exact location printed out.
It's important to know that this is going to give you the exact middle of the cell (relative to the window). Even if you touch the top, left, bottom or right side of the cell it's going to return the coordinate of the center of the cell itself and not the exact location that you tapped.
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
guard let cell = collectionView.cellForItem(at: indexPath) as? YourCell else { return }
guard let layoutAttributes = collectionView.layoutAttributesForItem(at: indexPath) else { return }
guard let window = UIApplication.shared.keyWindow else { return }
let touchedLocationInWindow = collectionView.convert(cell.center, to: window)
print("OhadM's version: \(touchedLocationInWindow)")
let cPoint = layoutAttributes.center
let tappedLocationInWindow = collectionView.convert(cPoint, to: window)
print("TomerBu's version: \(tappedLocationInWindow)")
}
An alternative is to use the events to provide most if not all the answers to your questions.
I presume that a touch event will initiate all of this, so lets implement something meaningful;
//First, trap the event in the collectionView controller with;
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath{
// lets ensure it's actually visible (yes we got here from a touch event so it must be... just more info)
if ([self.collectionView.indexPathsForVisibleItems containsObject:indexPath]) {
// get a ref to the UICollectionViewCell at indexPath
UICollectionViewCell *cell =(UICollectionViewCell *)[self.collectionView cellForItemAtIndexPath:indexPath];
//finally get the rect for the cell
CGRect cellRect = cell.frame
// do your processing here... a ref to the cell will allow you to drill down to the image without the headache!!
}
}
oh ... before you rush off for happy hour, lets not forget to read up on;
<UICollectionViewDelegate> (hint - it's needed)
SWIFTYFIED (v5) SHORT ANSWER
let attributes = collectionView.layoutAttributesForItem(at: indexPath)
let cellRect = attributes?.frame
let cellFrameInSuperview = collectionView.convert(cellRect ?? CGRect.zero, to: collectionView.superview)
All you need is the index path of the cell.