Find frame in window of collection view cell - ios

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
}
}

Related

Check if `UICollectionView` index path is valid?

I am trying to scroll to a specified index path in a UICollectionView:
if ([self collectionView:self.collectionView numberOfItemsInSection:0] > self.activeIndexPath.row && self.activeIndexPath.row > 0)
[self.collectionView scrollToItemAtIndexPath:self.activeIndexPath atScrollPosition:UICollectionViewScrollPositionLeft animated:NO];
But I keep getting a crash with this error:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'attempt to scroll to invalid index path: <NSIndexPath: 0x1ff80a30> {length = 2, path = 0 - 40}
How can I verify the index path before scrolling? My collection view only has 1 section.
Check and verify the indexPath that you are passing.
If its ok, try logging the numberofItemsInSection for your collectionView.
Then, try reloading your collectionView before scrolling to the indexPath like:
[self.collectionView reloadData];
First you need to get the indexPath of your collectionView for this my code is as -
Objective C Code -
- (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...
}
}
SWIFT 2.x Code -
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
}
You can access the indexPath from any where you wants on you CollectionView page like this -
guard let cell = UIView.superviewWithClassName("UICollectionViewCell", fromView: sender as? UIView) as? UITableViewCell else {return}

getting value from uitextfield in uitableview

i have a lot of uitableviewcells with uitextfields inside. And i am catching the editing of the user with the delegate of the textField. That works fine as long as i don't use sections. Because i use the tag of the textField to save the indexPath.row of the cell. The problem is that i have to use sections now and with the sections i would have to save the indexPath.row and .section somehow.
Here is some Code.
func textFieldDidEndEditing(textField: UITextField) {
let indexPath = NSIndexPath(forRow: textField.tag, inSection: 0)
let cell : UITableViewCell? = self.tableView.cellForRowAtIndexPath(indexPath) as UITableViewCell?
if data[(cell?.contentView.viewWithTag(textField.tag + 10000) as! UILabel).text!] != textField.text {
newData[fields[indexPath.row].ID] = textField.text
}
print((cell?.contentView.viewWithTag(textField.tag + 10000) as! UILabel).text)
print(textField.text)
}
Is there a better way to catch the edit of the textfields? Or how could i save both informations in the tag of the textfield?
greetings Adarkas.
Lets take a different approach.
Instead of using tag, you could convert your textfield location and get the index path of its cell superview.
func textFieldDidEndEditing(textField: UITextField) {
let origin: CGPoint = textField.frame.origin
let point: CGPoint = textField.convertPoint(origin, toView: self.tableView)
let indexPath = self.tableView.indexPathForRowAtPoint(point)
let cell : UITableViewCell? = self.tableView.cellForRowAtIndexPath(indexPath) as UITableViewCell?
if data[(cell?.contentView.viewWithTag(textField.tag + 10000) as! UILabel).text!] != textField.text {
newData[fields[indexPath.row].ID] = textField.text
}
print((cell?.contentView.viewWithTag(textField.tag + 10000) as! UILabel).text)
print(textField.text)
}
I don't remember where on SO I saw this to give proper credit, but here's what I'm using in my app. You get the indexPath based on the textField's superview.
let textField = sender as! UITextField
let view = textField.superview!.superview!
let currentCell = view.superview as! UITableViewCell // Or substitute your custom cell class name
let indexPath = tableView.indexPathForCell(currentCell)
I have resolved my problem like follows.
I have created a unique ID for my Datasource and i use that to determine with cell has been updated.
var row = 0
var section = 0
for var k = 0; k < fieldsWSections.count; k++ {
for var kk = 0; kk < fieldsWSections[k].count; kk++ {
if fieldsWSections[k][kk].ID == String(textField.tag) {
section = k
row = kk
}
}
}
if data[fieldsWSections[section][row].name] != textField.text {
newData[fieldsWSections[section][row].ID] = textField.text
}
So i don't need to know the indexPath anymore.
Using tags is a bad idea for reusable code, but the other answers rely upon a precise structure of the hierarchy.
This helper function only expects that the UITextField is a child of the UITableViewCell and that the UITableViewCell is a child of the their UITableView, which should always be true when the UITextFieldDelegate is called. If either is not true, this returns nil.
func indexPath(for view: UIView) -> IndexPath? {
// find the cell
var sv = view.superview
while !(sv is UITableViewCell) {
guard sv != nil else { return nil }
sv = sv?.superview
}
let cell = sv as! UITableViewCell
// find the owning tableView
while !(sv is UITableView) {
guard sv != nil else { return nil }
sv = sv?.superview
}
let tableView = sv as! UITableView
// locate and return the indexPath for the cell in this table
return tableView.indexPath(for: cell)
}

How do I get rect for UICollectionView

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 }

how to access from UICollectionViewCell the indexPath of the Cell in UICollectionView

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)
}

How to get the rect of a UICollectionViewCell?

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

Resources