I am trying to get a reference to the first cell in collection view in order to move it (for some effect) .
first, can you move a certain cell that is inside the collection ?
second,how would i check if its on visible rect right now? (cells are reusable) .
ERROR: when i do this, i am also getting an error when i set the index path to non zero
-(void)scrollViewDidScroll:(UIScrollView *)scrollView
{
UICollectionViewCell *cell = [self.collectionView cellForItemAtIndexPath:4];//E
CGRect cellRect = cell.frame;
NSLog(#"%f",cellRect.origin.y);
}
"implicit conversion of int is disallowed in ARC ".
When its set to 0 , i always get position of 0, even when the cell is offscreen.
I guess i am missing the right way to get the first cell ..
You should create an instance of an NSIndexPath to use cellForItemAtIndexPath
Example
-(void)scrollViewDidScroll:(UIScrollView *)scrollView
{
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:4 inSection:0];
UICollectionViewCell *cell = [self.collectionView cellForItemAtIndexPath:indexPath];
CGRect cellRect = cell.frame;
}
Swift 3 version code: based on Luca Bartoletti answer
func scrollViewDidScroll(scrollView: UIScrollView) {
let indexPath = IndexPath(item: 4, section: 0)
var cell = self.collectionView.cellForItem(at: indexPath)
var cellRect = cell.frame
}
Related
I have looked for ways of getting the last indexPath of a UICollectionView, although below code works for a UITableView (having one section):
[NSIndexPath indexPathForRow:[yourArray count]-1 inSection:0]
but not been able to achieve the same thing for a UICollectionView.
You can find last index of UICollectionView like this.
NSInteger lastSectionIndex = MAX(0, [self.yourCollectionView numberOfSections] - 1);
NSInteger lastRowIndex = MAX(0, [self.yourCollectionView numberOfItemsInSection:lastSectionIndex] - 1);
NSIndexPath *lastIndexPath = [NSIndexPath indexPathForRow:lastRowIndex
inSection:lastSectionIndex];
You can also find last index of UITableView like this.
NSInteger lastSectionIndex = MAX(0, [self.yourTableView numberOfSections] - 1);
NSInteger lastRowIndex = MAX(0, [self.yourTableView numberOfRowsInSection:lastSectionIndex] - 1);
NSIndexPath *lastIndexPath = [NSIndexPath indexPathForRow:lastRowIndex
inSection:lastSectionIndex];
And if you want to detect last index of a specific section, you just need to replace the index of that section with "lastSectionIndex".
Simple way for the server side paging (that I use):
You can do the server side paging in willDisplay cell: delegate method of the collection/table view both.
You'll get the indexPath of the cell that's going to display then make a condition that will check that the showing indexPath.item is equal to the dataArray.count-1 (dataArray is an array from which your collection/table view is loaded)
i think,
you should do it in scrollView's Delegate "scrollViewDidEndDecelerating",
check your currently visible cells by
NSArray<NSIndexPath*>* visibleCells = [self.collection indexPathsForVisibleItems];
create last indexPath by,
NSIndexPath* lastIndexPath = [NSIndexPath indexPathForItem:(datasource.count-1) inSection:0];
check conditions,
if([visibleCells containsObject:lastIndexPath]) {
//This means you reached at last of your datasource. and here you can do load more process from server
}
whole code will be like, Objective C Code,
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
NSArray<NSIndexPath*>* visibleCells = [collection indexPathsForVisibleItems];
NSIndexPath* lastIndexPath = [NSIndexPath indexPathForItem:(Blogs.count - 1) inSection:0];
if([visibleCells containsObject:lastIndexPath]) {
//This means you reached at last of your datasource. and here you can do load more process from server
}
}
Swift 3.1 Code,
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
let visibleCells: [IndexPath] = collection.indexPathsForVisibleItems
let lastIndexPath = IndexPath(item: (Blogs.count - 1), section: 0)
if visibleCells.contains(lastIndexPath) {
//This means you reached at last of your datasource. and here you can do load more process from server
}
}
Swift 5
extension UICollectionView {
func getLastIndexPath() -> IndexPath {
let lastSectionIndex = max(0, self.numberOfSections - 1)
let lastRowIndex = max(0, self.numberOfItems(inSection: lastSectionIndex) - 1)
return IndexPath(row: lastRowIndex, section: lastSectionIndex)
}
}
I want to locate the frame of section header in UICollectionView. I have a similar situation for UITableView, and for that, I was able to get its rect by doing:
CGRect rect = [self.tableView rectForHeaderInSection:section];
Is there a way to get the same result in UICollectionView? Thanks!
Sorry, guys. It was actually easy.
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:0 inSection:section];
UICollectionViewLayoutAttributes *attributes = [self.collectionView layoutAttributesForSupplementaryElementOfKind:UICollectionElementKindSectionHeader atIndexPath:indexPath];
CGRect rect = [attributes frame];
Swift version of #tsuyoski's answer:
let section = 0 // or whatever section you're interested in
let indexPath = IndexPath(item: 0, section: section)
let attributes = collectionView.layoutAttributesForSupplementaryElement(ofKind: UICollectionView.elementKindSectionHeader, at: indexPath)
guard let rect = attributes?.frame else { return }
So I am trying to write some code that scrolls a collection view to a certain index, then pulls a reference to the cell and does some logic. However I've noticed if that cell wasn't presently visible prior to the scroll, the cellForItemAtIndexPath call will return nil, causing the rest of my logic to fail.
[_myView scrollToItemAtIndexPath:[NSIndexPath indexPathForItem:index
inSection:0]
atScrollPosition:UICollectionViewScrollPositionTop
animated:NO];
//Tried with and without this line, thinking maybe this would trigger a redraw
[_myView reloadData];
//returns nil if cell was off-screen before scroll
UICollectionViewCell *cell =
[_myView cellForItemAtIndexPath:
[NSIndexPath indexPathForItem:index inSection:0]];
Is there some other method I have to call to cause the cellForItemAtIndexPath to return something for a cell that suddenly came into view as a result of the scroll immediately preceding it?
I figured out a solution for now. If I call [myView layoutIfNeeded] right after my call to reloadData, but before my attempt to retrieve the cell everything works fine. Right now all the cells are cached so access is fast, but I am scared this might give me bad performance if I have to load from the web or an internal database, but we'll see.
If you want to access the cell and have it visible on screen:
NSIndexPath *indexPath = ...;
[collectionView scrollToItemAtIndexPath:indexPath atScrollPosition:UICollectionViewScrollPositionCenteredVertically | UICollectionViewScrollPositionCenteredHorizontally animated:NO];
UICollectionViewCell *cell = [collectionView cellForItemAtIndexPath:indexPath];
if(cell == nil) {
[collectionView layoutIfNeeded];
cell = [collectionView cellForItemAtIndexPath:indexPath];
}
if(cell == nil) {
[collectionView reloadData];
[collectionView layoutIfNeeded];
cell = [collectionView cellForItemAtIndexPath:indexPath];
}
I very rarely enter the second if statement, but having two fallbacks like this works very well.
Based on your comments, it sounds like what you're ultimately after is the cell's frame. The way to do that without relying on existence of the cell is to ask the collection view's layout:
NSIndexPath *indexPath = ...;
UICollectionViewLayoutAttributes *pose = [self.collectionView.collectionViewLayout layoutAttributesForItemAtIndexPath:indexPath];
CGRect frame = pose.frame;
hfossli's solution worked for me, so I've provided a Swift version below. In my case, I was unable to get a reference to a custom UICollectionViewCell, probably because it was not visible. I tried many things, but just as Shaybc said, this was the sole working solution.
Swift 3:
func getCell(_ indexPath: IndexPath) -> CustCell? {
cView.scrollToItem(at: indexPath, at: UICollectionViewScrollPosition.centeredHorizontally, animated: false)
var cell = cView.cellForItem(at: indexPath) as? CustCell
if cell == nil {
cView.layoutIfNeeded()
cell = cView.cellForItem(at: indexPath) as? CustCell
}
if cell == nil {
cView.reloadData()
cView.layoutIfNeeded()
cell = cView.cellForItem(at: indexPath) as? CustCell
}
return cell
}
usage:
let indexPath = IndexPath(item: index, section: 0)
cView.scrollToItem(at: indexPath, at: UICollectionViewScrollPosition(), animated: true)
if let cell = getCell(indexPath) {
cell. <<do what you need here>>
}
i want to animate the UICollectionViewCell when action is called.
i have done UICollectionViewCell in Interface Builder, the UICollectionView also.
Now i want to get the correct indexPath at my actionBtnAddToCard method.
thats the way i try it now (method in ProduktViewCell.m):
- (IBAction)actionAddToCart:(id)sender {
XLog(#"");
// see this line
NSIndexPath *indexPath = ??** how can i access the correct indexPath**??;
SortimentViewController *svc = [[SortimentViewController alloc] initWithNibName:#"SortimentViewController_iPad" bundle:[NSBundle mainBundle]];
[svc.collectionViewProdukte cellForItemAtIndexPath:indexPath];
[svc collectionView:svc.collectionViewProdukte didSelectItemAtIndexPath:indexPath];
}
SortimentViewController is the viewController which inherits the UICollectionView.
how to acces the correct indexPath?
UPDATE 1: edited post for better understanding.
- (IBAction)actionAddToCart:(id)sender {
NSIndexPath *indexPath;
indexPath = [self.collectionView indexPathForItemAtPoint:[self.collectionView convertPoint:sender.center fromView:sender.superview]];
...
}
if you know the view hierarchy it is easy.
UIButton *button = (UiButton *) sender;
if the button is like this - > UITableViewCell - > button
then you can get cell like this
UITableViewCell *cell = (UITableViewCell *)[button superview];
if the button is like this - > UITableViewCell - > content view -> button
UITableViewCell *cell = (UITableViewCell *)[[button superview] superview];
and finally index path can be extracted like this
NSIndexPath *indexPath = [self.table_View indexPathForCell:cell];
Do Not Depend on view.
Try this.
CGPoint buttonPosition = [sender convertPoint:CGPointZero toView:self.collectionView];
NSIndexPath *indexPath = [self.collectionView indexPathForItemAtPoint:buttonPosition];
NSLog(#"%ld", (long)indexPath.row);
Using code like [[button superview] superview] is fragile and not future-proof; indeed, it's not even guaranteed to work on all iOS versions unless you explicitly test it. I always use an iterative helper method for this purpose:-
- (UIView *)superviewWithClassName:(NSString *)className fromView:(UIView *)view
{
while (view)
{
if ([NSStringFromClass([view class]) isEqualToString:className])
{
return view;
}
view = view.superview;
}
return nil;
}
Then I call it from the button handler like so:-
- (IBAction)buttonClicked:(id)sender
{
UIButton *button = (UIButton *)sender;
UICollectionViewCell *cell = (UICollectionViewCell *)
[self superviewWithClassName:#"UICollectionViewCell"
fromView:button];
if (cell)
{
NSIndexPath *indexPath = [self.collectionView indexPathForCell:cell];
// do whatever action you need with the indexPath...
}
}
UPDATE: Swift version of superviewWithClassName. Made it a class method since it never references self.
static func superviewWithClassName(className:String, fromView view:UIView?) -> UIView? {
guard let classType = NSClassFromString(className) else {
return nil
}
var v:UIView? = view
while (v != nil) {
if v!.isKindOfClass(classType) {
return v
}
v = v!.superview
}
return nil
}
and some code to call it, either from prepareForSegue or a button handler:-
guard let cell = UIView.superviewWithClassName("UICollectionViewCell", fromView: sender as? UIView) as? UITableViewCell else {return}
Swift solution:
A UICollectionView extension like this one can be useful for this.
extension UICollectionView {
func indexPathForView(view: AnyObject) -> NSIndexPath? {
let originInCollectioView = self.convertPoint(CGPointZero, fromView: (view as! UIView))
return self.indexPathForItemAtPoint(originInCollectioView)
}
}
Usage becomes easy everywhere.
let indexPath = collectionView.indexPathForView(button)
You can do it like this, indexPathsForVisibleItems will return array of NSIndexPaths for items currently visible on view and first object returns the first one (if you have one cell per view).
NSIndexPath *indexPath = [[svc.collectionViewProdukte indexPathsForVisibleItems] firstObject]
If you want to animate a specific cell, you need to get a reference to that cell. Simply calling
[svc.collectionViewProdukte cellForItemAtIndexPath:indexPath];
does nothing. You need to keep the cell that the method returns, like this:
UICollectionViewCell *cell = [svc.collectionViewProdukte cellForItemAtIndexPath:indexPath];
After that, go ahead and animate:
[UIView animateWithDuration:0.2f animations:^{
cell.transform = CGAffineTransformMakeScale(0.5f, 0.5f);
}];
Swift 3 Solution : Based on Ishan Handa's Answer
extension UICollectionView {
func indexPathForView(view: AnyObject) -> IndexPath? {
let originInCollectioView = self.convert(CGPoint.zero, from: (view as! UIView))
return self.indexPathForItem(at: originInCollectioView) as IndexPath?
}
}
Usage:
func deleteCell(sender:UIButton){
var indexPath:IndexPath? = nil
indexPath = self.collectionView.indexPathForView(view: sender)
print("index path : \(indexPath)")
}
//Note: this is for a storyboard implementation
// here is code for finding the row and section of a textfield being edited in a uicollectionview
UIView *contentView = (UIView *)[textField superview];
UICollectionViewCell *cell = (UICollectionViewCell *)[contentView superview];
cell = (UICollectionViewCell *)[contentView superview];
// determine indexpath for a specific cell in a uicollectionview
NSIndexPath *editPath = [myCollectionView indexPathForCell:cell];
int rowIndex = editPath.row;
int secIndex = editPath.section;
Even though many answer i found here .this will be shortest and useful irrespective of the view hierarchy
- (void) actionAddToCart:(id)sender
{
id view = [sender superview];
while (view && [view isKindOfClass:[UICollectionViewCell class]] == NO)
{
view = [view superview];
}
NSIndexPath *thisIndexPath = [self.collectionView indexPathForCell:view];
NSLog(#"%d actionAddToCart pressed",thisIndexPath.row);
}
Xcode10. Swift 4.2 version.
extension UICollectionView {
func indexPathForView(view: AnyObject) -> IndexPath? {
guard let view = view as? UIView else { return nil }
let senderIndexPath = self.convert(CGPoint.zero, from: view)
return self.indexPathForItem(at: senderIndexPath)
}
}
Usage:
// yourView can be button for example
let indexPath = collectionView.indexPathForView(view: yourView)
You almost certainly have a UICollectionViewCell subclass. Just add a property and set the indexPath in cellForItemAtIndexPath.
internal func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: “cell_id”, for: indexPath)
let bttn_obj = UIButton(frame: CGRect(x: 5.5, y: 5.5, width: 22, height: 22))
bttn_obj.addTarget(self, action: #selector(bttn_action), for: UIControl.Event.touchUpInside)
cell.addSubview(bttn_obj)
return cell
}
#IBAction func bttn_action(_ sender: UIButton) -> Void {
let cell_view = sender.superview as! UICollectionViewCell
let index_p : IndexPath = self.collectionview.indexPath(for: cell_view)!
print(index_p)
}
I have a UITableView in which I have added a UIButton as accessory view for each cell. Note that I set the tag of the button as current row for future use.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
// Configure the cell...
if(cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
cellButton = [UIButton buttonWithType:UIButtonTypeCustom];
cellButton.frame = CGRectMake(0, 0, 30, 30);
[cellButton setBackgroundImage:[UIImage imageNamed:#"cellButton.png"] forState:UIControlStateNormal];
[cellButton addTarget:self action:#selector(cellButtonAction:) forControlEvents:UIControlEventTouchUpInside];
cellButton.tag = indexPath.row; // <= Will use this in the next method
cell.accessoryView = cellButton;
}
//Load cell with row based data
return cell;
}
Now when one of these buttons is tapped, I need to make changes to the cell. So I implement cellButtonAction where I use the tag to get back the cell:
-(void)editCommentButtonAction:(id)sender
{
UIButton *button = sender;
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:button.tag inSection:0];
UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
[self makeChangesToCell:cell];
}
But this seems like a very round about way. Is there a cleaner way to do this?
So assuming that the button is in the contentView directly:
ask "sender" (ie the button) for its superview, which is the cell's contentView
ask that view for its superView, which is the cell
ask the tabview for the index of this cell:
- (NSIndexPath *)indexPathForCell:(UITableViewCell *)cell
EDIT: Actually, I use a general purpose method or function now that just walks up the superviews, looking for a view that is 'KindOf' a UITableViewCell or a UICollectionViewCell. Works like a champ!
The code in Swift:
func containingUITableViewCell(tableView: UITableView, var view: UIView) -> (UITableViewCell, NSIndexPath)? {
while let v = view.superview {
view = v
if view.isKindOfClass(UITableViewCell.self) {
if let cell = view as? UITableViewCell, let indexPath = tableView.indexPathForCell(cell) {
return (cell, indexPath)
} else {
return nil
}
}
}
return nil
}
func containingUICollectionViewCell(collectionView: UICollectionView, var view: UIView) -> (UICollectionViewCell, NSIndexPath)? {
while let v = view.superview {
view = v
if view.isKindOfClass(UICollectionViewCell.self) {
if let cell = view as? UICollectionViewCell, let indexPath = collectionView.indexPathForCell(cell) {
return (cell, indexPath)
} else {
return nil
}
}
}
return nil
}
You can do it in a easier way. You will get the table view cell using the sender parameter.
Check the following code.
-(void)editCommentButtonAction:(id)sender
{
UIButton *button = (UIButton *)sender;
UITableViewCell *cell = (UITableViewCell*)[button superview];
[self makeChangesToCell:cell];
}
Here,
You are casting the sender of type id to a UIButton
You are calling the getter superview of that button, it will give you the UITableViewCell
Doing your customization.
You can get the cell as follows.
-(void)editCommentButtonAction:(id)sender
{
NSIndexPath* indexPath = 0;
//Convert the bounds origin (0,0) to the tableView coordinate system
CGPoint localPoint = [self.tableView convertPoint:CGPointZero fromView:sender];
//Use the point to get the indexPath from the tableView itself.
indexPath = [self.tableView indexPathForRowAtPoint:localPoint];
//Here is the indexPath
UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
[self makeChangesToCell:cell];
}
I had the same situation, the thing is that I had an imageView inside my tablecells, and I want to get the cell that holds the imageview that I tapped..
//MyCell is subclass of UITableViewCell
if ([[[[sender view] superview] superview] isKindOfClass:[MyCell class]]) {
MyCell *cell = (MyCell *)[[[sender view] superview] superview];
NSIndexPath *cellIndexPath = [myTable indexPathForCell:cell];
NSLog(#"cellIndexPath: %# - %#",cellIndexPath, [videoURLArray objectAtIndex:cellIndexPath.row]);
}
[sender view] - imageView
[[sender view] superview] -- where my imageView was placed (in this case, the superview of my imageView is the cell's contentView)
[[[sender view] superview] superview] --- where the contentView was placed -- the cell
The NSLog part should print the correct row and section of the tapped imageView.. Just Modify the code. ;)
Hello gigahari Use the following code.
- (void)cellStopDownloadButtonClicked:(id)sender
{
id viewType = [sender superview];
do {
viewType = [viewType superview];
}
while (![viewType isKindOfClass:[CustomCell class]] || !viewType);
CustomCell *cell = (CustomCell *)viewType;
// use the cell
}
This will work in all cases like ios 6 and ios 7. in ios 7 an extra view added in cell (content view).
if you use [[sender superview] superview] it will fail in some cases.
The way i usually do this:
cell stores the data for the given row or the index path
create a protocol with a method -didSelectSomething or -didSelectAtIndexPath:
the cell holds a reference to an instance of the protocol, which will be your datasource
wire the button action to the cell in your nib
have the cell call the delegate
DON'T FORGET to clean up the cell in prepareForReuse. Storing state in cells can lead to nasty bugs, so be sure to clean up on reuse.
The tag thing is a real hack, and it won't work if you have more than one section.
The sender superview will break if you reorder the views in your nib.
For this particular case (accessory view), isn't there a dedicated table delegate method?