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 }
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'm getting all the visible cells in a UICollectionViewController like this.
NSArray<__kindof UICollectionViewCell *> *cells = [self.collectionView.visibleCells visibleCells];
How do I find the frame for the cell in the window?
Try this:
Swift
guard
let indexPath = collectionView.indexPath(for: yourVisibleCell),
let attributes = collectionView.layoutAttributesForItem(at: indexPath)
else {
assertionFailure("Can't get required attributes")
return
}
let frameInWindow = collectionView.convert(attributes.frame, to: nil)
Objective C
NSIndexPath *indexPath = [self.collectionView indexPathForCell:yourVisibleCell];
UICollectionViewLayoutAttributes *theAttributes = [collectionView layoutAttributesForItemAtIndexPath: indexPath];
CGRect cellFrameInSuperview = [collectionView convertRect:theAttributes.frame toView:nil];
Xcode friendly Swift variant of iPrabu's answer:
var screenCellFrame: CGRect? {
if let cell = <#visibleCell#>, let indexPath = collectionView?.indexPath(for: cell), let attributes = collectionView!.layoutAttributesForItem(at: indexPath) {
return collectionView!.convert(attributes.frame, to: nil)
} else {
return nil
}
}
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
}
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)
}
UITableView has the method rectForRowAtIndexPath:, but this does not exist in UICollectionView. I'm looking for a nice clean way to grab a cell's bounding rectangle, perhaps one that I could add as a category on UICollectionView.
The best way I've found to do this is the following:
Objective-C
UICollectionViewLayoutAttributes *attributes = [self.collectionView layoutAttributesForItemAtIndexPath:indexPath];
Swift
let attributes = collectionView.layoutAttributesForItem(at: indexPath)
Then you can access the location through either attributes.frame or attributes.center
Only two lines of code is required to get perfect frame :
Objective-C
UICollectionViewLayoutAttributes * theAttributes = [collectionView layoutAttributesForItemAtIndexPath:indexPath];
CGRect cellFrameInSuperview = [collectionView convertRect:theAttributes.frame toView:[collectionView superview]];
Swift 4.2
let theAttributes = collectionView.layoutAttributesForItem(at: indexPath)
let cellFrameInSuperview = collectionView.convert(theAttributes.frame, to: collectionView.superview)
in swift 3
let theAttributes:UICollectionViewLayoutAttributes! = collectionView.layoutAttributesForItem(at: indexPath)
let cellFrameInSuperview:CGRect! = collectionView.convert(theAttributes.frame, to: collectionView.superview)
in swift you can just do:
//for any cell in collectionView
let rect = self.collectionViewLayout.layoutAttributesForItemAtIndexPath(clIndexPath).frame
//if you only need for visible cells
let rect = cellForItemAtIndexPath(indexPath)?.frame
How about
-(CGRect)rectForCellatIndexPath:(NSIndexPath *)indexPath {
UICollectionViewCell *cell = [self cellForItemAtIndexPath:indexPath];
if (!cell) {
return CGRectZero;
}
return cell.frame;
}
as a category on UICollectionView?
#import <UIKit/UIKit.h>
#interface UICollectionView (CellFrame)
-(CGRect)rectForCellatIndexPath:(NSIndexPath *)indexPath;
#end
#import "UICollectionView+CellFrame.h"
#implementation UICollectionView (CellFrame)
-(CGRect)rectForCellatIndexPath:(NSIndexPath *)indexPath {
UICollectionViewCell *cell = [self cellForItemAtIndexPath:indexPath];
if (!cell) {
return CGRectZero;
}
return cell.frame;
}
#end