UITableViewCell Z-Order - ios

I have a uitableview which contains cells. I am trying to figure it out how to adjust the z order of the cells. I am trying to make this:
But when I load the tableview I get this:
I've already tried [tableView sendSubViewToBack:cell]. Also when I scroll to the bottom and then return to the top the result is like in picture 1 but when I scroll down the result is like in picture 2. I appreciate your help.
static NSString *CellIdentifier = #"Cell";
NSString *row = [NSString stringWithFormat:#"%li",(long)indexPath.row];
GetMoreTableViewCell *cell = [_chapters dequeueReusableCellWithIdentifier:CellIdentifier];
NSMutableDictionary *deckCategory = [[_data valueForKey:key] valueForKey:row];
if (cell == nil) {
cell = [[GetMoreTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
[tableView bringSubviewToFront:cell];
if([[[_data valueForKey:key] valueForKey:[NSString stringWithFormat:#"%i",indexPath.row]] valueForKey:#"isDefault"] == [NSNumber numberWithBool:YES]) {
cell.addButton.hidden = YES;
}
cell.ttitle.text = [[[_data valueForKey:key] valueForKey:row] valueForKey:#"Name"];
cell.backgroundColor = [UIColor clearColor];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
CALayer * l = [cell.cellImage layer];
[l setMasksToBounds:YES];
[l setCornerRadius:10.0];
CALayer * b = [cell.cellBackground layer];
[b setMasksToBounds:YES];
[b setCornerRadius:19.0];
cell.cellImage.image = [UIImage imageNamed:[deckCategory valueForKey:#"Image"]];
if(indexPath.row == 0) {
cell.cellBackground.image = [UIImage imageNamed:#"card_green.png"];
}
else if(indexPath.row == 1) {
cell.cellBackground.image = [UIImage imageNamed:#"card_orange.png"];
}
else if(indexPath.row == 2) {
cell.cellBackground.image = [UIImage imageNamed:#"card_red.png"];
}
return cell;

I faced the same problem when using shadows on my UITableViewCells, the cells further down the screen (In the Y direction) had shadows overlapping from the higher ones at the top of the screen.
My solution was to set the Z Position on the layer of UITableViewCell based upon the indexPath.
cell.layer.zPosition = CGFloat(indexPath.row)
My issue was only occurring within individual sections. If your cells from subsequent sections also face this problem then you will need to add section number into the calculation.
cell.layer.zPosition = CGFloat(indexPath.section) * 1000.0 + CGFloat(indexPath.row)
If you have more than 1000 rows in each section then increase the magic number, or think about why you have 1000 rows in your App ;-)

It should work by changing the zPosition of the cell layer like this : cell.layer.zPosition = CGFloat(indexPath.row) in the tableView:cellForRowAtIndexPath: dataSource method. And don't forget to set the clipsToBound property to false for the cell.

I faced the same issue some time back.
The solution that worked for me is using a Collection view instead of a TableView. Create a custom class by extending it from UICollectionViewFlowLayout and use that in your Collection View instead.
class OverlappedCustomFlowLayout: UICollectionViewFlowLayout {
override func prepare() {
super.prepare()
// This allows us to make intersection and overlapping
// A negative number implies overlapping whereas positive implies space between the adjacent edges of two cells.
minimumLineSpacing = -100
}
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
let layoutAttributes = super.layoutAttributesForElements(in: rect)
for currentLayoutAttributes: UICollectionViewLayoutAttributes in layoutAttributes! {
// zIndex - Specifies the item’s position on the z-axis.
// Unlike a layer's zPosition, changing zIndex allows us to change not only layer position,
// but tapping/UI interaction logic too as it moves the whole item.
currentLayoutAttributes.zIndex = currentLayoutAttributes.indexPath.row + 1
}
}
return layoutAttributes
}
P.S. - As per Apple's developer document of zIndex
This property is used to determine the front-to-back ordering of items
during layout. Items with higher index values appear on top of items
with lower values. Items with the same value have an undetermined
order. The default value of this property is 0.
Hope it helps! :)

Unfortunately you can't control the order that cellForRow... is called in, so sending the cell to the back or the front isn't going to do it.
You would probably need to make a method which passed through the visible cells of the table and reordered them according to index path, but you're messing with views that you don't have control over. The table view can rearrange or add subviews at any time and you'd be trying to catch all of those events.
I would implement a view like this with a collection view rather than a table view, which would allow you to specify a z position for each indexPath using a custom layout, and would allow you to overlap the cells by a varying amount. Creating a custom layout is pretty simple, particularly for a table-type layout.

If you want to have descending zPositions (so that the first cell is on top), use the following code based on the answer from Brett. With this approach, there's no need to recalculate the zPosition value manually.
func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) {
DispatchQueue.main.async {
for index in 0 ..< tableView.visibleCells.count {
let zPosition = CGFloat(tableView.visibleCells.count - index)
tableView.visibleCells[index].layer.zPosition = zPosition
}
}
}

Related

Is it possible to have differing heights in a UITableView Cell when I use several different ways of displaying the cell?

I've spent several days trying to figure this out, but there doesn't seem to be a solution. I have a very basic UITableView cell with two labels in it. One of them will be one line, the second will be multiline. The second one will be varying in heights. Here is an example of what I'm trying to produce ( UIStackViews inside of UITableViewCells ) :
From reading hundreds of SO posts, hundreds of blogs, scouring the Apple Developer sites, there seems to be an unlimited amount of ways one could write this. After having experimented with hundreds of different ways, I still can't manage to display the text like so. I think some of the trouble comes from the fact that I'm using 3 different UITableView cell variations in one UITableView.
Here is my code from my latest attempt cellForRowAtIndexPath :
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell : UITableViewCell!
let (parent, isParentCell) = self.findParent(indexPath.row)
if !isParentCell {
let categories = ["words", "translation","commentary"]
if categories[parent] == "words" {
cell = tableView.dequeueReusableCellWithIdentifier(childWordsCellIdentifier, forIndexPath: indexPath) as UITableViewCell
// Reset content
for subview in cell.contentView.subviews {
subview.removeFromSuperview()
}
cell.prepareForReuse()
let words = self.page.valueForKey("words")!.allObjects as! [Word]
let wordsInOrder = words.sort({Int($0.order!) < Int($1.order!) })
let word = wordsInOrder[indexPath.row - 1]
let labelWidth = (self.view.frame.width - 80) / 2
let wordLabel = UILabel(frame: CGRectZero)
wordLabel.text = word.valueForKey("sanskrit") as? String
wordLabel.font = UIFont(name: "EuphemiaUCAS-Bold", size: 16)
wordLabel.numberOfLines = 0
wordLabel.translatesAutoresizingMaskIntoConstraints = false
wordLabel.layer.borderColor = UIColor.greenColor().CGColor
wordLabel.layer.borderWidth = 1.0
let widthWordConstraint = wordLabel.widthAnchor.constraintEqualToConstant(labelWidth)
widthWordConstraint.priority = 300
widthWordConstraint.active = true
let englishWordLabel = UILabel(frame: CGRectZero)
englishWordLabel.text = "Foobar don't want to play my game with my anymore."
englishWordLabel.font = UIFont(name: "STHeitiTC-Light", size: 16)
englishWordLabel.numberOfLines = 0
englishWordLabel.preferredMaxLayoutWidth = labelWidth
englishWordLabel.translatesAutoresizingMaskIntoConstraints = false
let englishWordConstraint = englishWordLabel.widthAnchor.constraintEqualToConstant(labelWidth)
englishWordConstraint.priority = 300
englishWordConstraint.active = true
englishWordLabel.layer.borderColor = UIColor.blueColor().CGColor
englishWordLabel.layer.borderWidth = 1.0
let stackView = UIStackView()
stackView.axis = .Horizontal
stackView.distribution = .FillProportionally
stackView.alignment = .FirstBaseline
stackView.spacing = 15
stackView.layoutMargins = UIEdgeInsets(top: 0, left: 20, bottom: 0, right: 20)
stackView.layoutMarginsRelativeArrangement = true
stackView.addArrangedSubview(wordLabel)
stackView.addArrangedSubview(englishWordLabel)
stackView.translatesAutoresizingMaskIntoConstraints = false
englishWordLabel.topAnchor.constraintEqualToAnchor(stackView.topAnchor).active = true
englishWordLabel.bottomAnchor.constraintEqualToAnchor(stackView.bottomAnchor).active = true
cell.contentView.addSubview(stackView)
cell.contentView.layoutIfNeeded()
} else {
cell = tableView.dequeueReusableCellWithIdentifier(childCellIdentifier, forIndexPath: indexPath) as UITableViewCell
// Reset content
cell.textLabel!.text = ""
for subview in cell.contentView.subviews {
subview.removeFromSuperview()
}
cell.prepareForReuse()
cell.textLabel!.text = self.page.valueForKey(categories[parent]) as? String
cell.textLabel!.textColor = UIColor(red: 35/255.0, green: 31/255.0, blue: 32/255.0, alpha: 1.0)
cell.textLabel!.font = UIFont(name: "STHeitiTC-Light", size: 16)
}
}
else {
// Parent
cell = tableView.dequeueReusableCellWithIdentifier(parentCellIdentifier, forIndexPath: indexPath)
cell.textLabel!.text = self.dataSource[parent].title
cell.textLabel!.textColor = UIColor(red: 66/255.0, green: 116/255.0, blue: 185/255.0, alpha: 1.0)
cell.textLabel!.font = UIFont(name: "STHeitiTC-Light", size: 20)
}
cell.selectionStyle = .None
cell.textLabel!.translatesAutoresizingMaskIntoConstraints = false
cell.textLabel!.numberOfLines = 0
cell.textLabel!.lineBreakMode = .ByWordWrapping
return cell
}
Which produces this :
I suggest you don't use stack view for this . Just take custom cell with Label according to your requirement and cell will resize automatically according to Label text size.
Check , here i have attached demo.
UITableview cell Autoresize According to textsize
Output :-
In viewdidload
super.viewDidLoad()
self.tblView.estimatedRowHeight = 100;
self.tblView.rowHeight = UITableViewAutomaticDimension;
self.tblView.setNeedsLayout()
self.tblView.layoutIfNeeded()
Dont forget to set Numberoflines=0 of UILabel property.
Edit :- If you need step by step guide regarding how to set constrain to UILabel ,
check this link ,
Adjust UILabel height depending on the text
Edit :- Here i have take 2 label in cell according to your above image. Just set constrain like this .
Label 1 :- Top , Leading , (Height and width According to your requirement)
Label 2 :- Top , Bottom , Leading from Label 1, Trailing from Superview
The height of a table view cell is decided by the table based on the rowHeight property, or on the return value of optional func tableView(_ tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat from the table view delegate.
The height that may be set by the programmer for the UITableViewCell through its frame property is ignored!
Hence you need to figure out the desired height of each cell programatically. Then you need to implement optional func tableView(_ tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat from the table view delegate for each of the cells.
If the size of a cell changes while the table view is displayed, you need to either:
reload the table with reloadData() from UITableView, or
update the cell with func reloadRowsAtIndexPaths(_ indexPaths: [NSIndexPath], withRowAnimation animation: UITableViewRowAnimation) from UITableView.
Edit
Actually since you are using constraints in you view it should work without using ``.
Try to replace the code in if categories[parent] == "words" { .. } with:
cell = tableView.dequeueReusableCellWithIdentifier(childWordsCellIdentifier, forIndexPath: indexPath) as UITableViewCell
// Reset content
for subview in cell.contentView.subviews {
subview.removeFromSuperview()
}
cell.prepareForReuse()
let words = self.page.valueForKey("words")!.allObjects as! [Word]
let wordsInOrder = words.sort({Int($0.order!) < Int($1.order!) })
let word = wordsInOrder[indexPath.row - 1]
let wordLabel = UILabel(frame: CGRectZero)
wordLabel.text = word.valueForKey("sanskrit") as? String
wordLabel.font = UIFont(name: "EuphemiaUCAS-Bold", size: 16)
wordLabel.numberOfLines = 0
wordLabel.translatesAutoresizingMaskIntoConstraints = false
wordLabel.layer.borderColor = UIColor.greenColor().CGColor
wordLabel.layer.borderWidth = 1.0
let englishWordLabel = UILabel(frame: CGRectZero)
englishWordLabel.text = "Foobar don't want to play my game with my anymore."
englishWordLabel.font = UIFont(name: "STHeitiTC-Light", size: 16)
englishWordLabel.numberOfLines = 0
let stackView = UIStackView()
stackView.axis = .Horizontal
stackView.distribution = .FillProportionally
stackView.alignment = .Fill // EDIT
stackView.spacing = 15
stackView.layoutMargins = UIEdgeInsets(top: 0, left: 20, bottom: 0, right: 20)
stackView.layoutMarginsRelativeArrangement = true
stackView.addArrangedSubview(wordLabel)
stackView.addArrangedSubview(englishWordLabel)
stackView.translatesAutoresizingMaskIntoConstraints = false
cell.contentView.addSubview(stackView)
cell.contentView.leadingAnchor.constraintEqualToAnchor(stackView.leadingAnchor).active = true
cell.contentView.topAnchor.constraintEqualToAnchor(stackView.topAnchor).active = true
cell.contentView.trailingAnchor.constraintEqualToAnchor(stackView.trailingAnchor).active = true
cell.contentView.bottomAnchor.constraintEqualToAnchor(stackView.bottomAnchor).active = true
cell.contentView.layoutIfNeeded()
Also I would have used two different cells classes for this task rather than removing the views in the contentView.
Please let me know how it goes!
First of all, unless you need a UIStackView for some other purpose that you're not mentioning in your question, don't use them. Multi-line labels and dynamic height cells work well without them (don't needlessly complicate the code if they are not necessary).
There are no bugs in iOS that stop this from working, that I'm aware of. Dynamically sized cells work great with Auto Layout, all you need to do is set the correct constraints in the cells and set the rowHeight and estimatedRowHeight properties of the table view.
The most likely reason this is not working for you is that you have not put all the needed constraints in your cell. The needed constraints are:
A top constraint from your cell content (i.e. your multi-line label) to the top of the cell content view
A leading constraint from your single-line label to the left of the cell
A trailing constraint from your multi-line label to the right of your cell
A bottom constraint from the bottom of your multi-line label to the bottom of your cell
I think you're missing point 4.
Edit
According to your comments, you're using a UIStackView so as to align the top baselines between the single and the multi-line labels. By this, I understand you want your labels to align like this:
To get this behaviour, you don't need a UIStackView. Just give the labels constraints to allow the multi-line label to define the cell height. The following image shows the top baselines aligned between the two labels (i.e. the first lines of each label are aligned).
Notice how the left label only needs a leading and top constraint, while the right label needs a top, trailing and bottom, because it defines cell height.
In summary, dynamic table view cells require three things:
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 100
The cell content must have all four constraints (leading, trailing, top and bottom) to define cell height.
What you want to achieve can be done by a simple implementation of heightForRowAtIndexPath from the tableView delegate.
If you're unsure what cell should have what height you can just calculate it on runtime.
For this you need to make use of Self sizing table view cell i.e. table view itself calculates height required for each cells to display it's content it can be achieved in two lines of code:
Step1: set estimate height for table view
tableView.estimatedRowHeight = 33.0
step2: set tableView's row height to UITableViewAutomaticDimension
tableView.rowHeight = UITableViewAutomaticDimension
Here is the tutorial for detail description http://www.appcoda.com/self-sizing-cells/
The approach i will be following:
I will create a UIView SubClass as a container view which will contain "word label" and "english word label". it's much easier, seperate code and maintanable.
I will create a cell subclass and use this container view as subView of cell'scontent view.
I will then use this whole set up in cellForRowAtIndex.
I will also make some code implementation in heightForRow. As to make cell row dynamic height at run time u must implementing this method heightForRow.
The sample Output will some thing like below, it has one UIImageView, and Two UILabel and custom gray seperator. This dynamic cell height is based on what ever image height and what ever string length to be shown in UILabel:
I will share some code snippet for you help. So here goes some conceptual code example for container view:
Most important constraint setup for container view say (VenueViewContainer:UIView) is as below, (for your case you can make labels horizontally, instead of vertical):
"V:|-0-[venueImage]-10-[venueName]-2-[sportName]-10-[lblSeperator(10)]",
"H:|-0-[venueImage]-0-|",
"H:|-20-[venueName]-20-|",
"H:|-20-[sportName]-20-|",
"H:|-0-[lblSeperator]-0-|",
Be sure to set these two properties for your labels:
[lbl setNumberOfLines:0];
[lbl setLineBreakMode:NSLineBreakByWordWrapping];
In your VenueViewContainer implementation file make a method:
/*!This method is responsible for getting view dynamic height .*/
-(float)getViewHeight
{
[self layoutIfNeeded];
CGFloat maxY = 0;
for (UIView *subview in self.subviews) {
maxY = MAX(CGRectGetMaxY(subview.frame),maxY);
}
return maxY;
}
/*!
This method is responsible for set up Venue.
#param venue object
*/
#pragma -mark data population
- (void)setupViewWithVenue:(Venue *)venue
{
self.venueLabel.text = #"my venue my venue my venue my venue my venue my venue my venue my venue my venue my venue my venue my venue my venue my venue last";
self.sportName.text = #"my sport my sport my sport my sport my sport my sport my sport my sport my sport my sport my sport my sport my sport my sport last";
[self.venueImage setImageWithURLString:venue.venueImageUrl defaultImage:[UIImage imageNamed:#"venueinfo_main"]];
}
Now lets talk about custom cell. the UITableViewCell subclass. The implementation is some thinh like this:
#implementation VenueCellTableViewCell
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
self.selectionStyle = UITableViewCellSelectionStyleNone;
[self setUpContainer];
[self setUpConstraints];
}
return self;
}
/*!
This method is responsible for set up Container
*/
-(void)setUpContainer{
aViewContainer = [[VenueViewContainer alloc] initWithFrame:CGRectZero];
[aViewContainer setTranslatesAutoresizingMaskIntoConstraints:NO];
[self.contentView addSubview:aViewContainer];
}
/*!constraints setup*/
-(void)setUpConstraints{
/*!
dictionary of views for autolayout
*/
NSMutableDictionary* views;
views = [NSMutableDictionary new];
UIView *parentView = self.contentView;
views[#"aViewContainer"] = aViewContainer;
views[#"parentView"] = parentView;
NSArray* constraints;
NSString* format;
/*!
layouting horizontal
*/
format = #"|-0-[aViewContainer]-0-|";
constraints = [NSLayoutConstraint constraintsWithVisualFormat:format options:0 metrics:nil views:views];
[parentView addConstraints:constraints];
/*!
layouting vertical
*/
format = #"V:|-0-[aViewContainer]-0-|";
constraints = [NSLayoutConstraint constraintsWithVisualFormat:format options:0 metrics:nil views:views];
[parentView addConstraints:constraints];
}
/*!
This method is responsible for set up venue
#param venue object
*/
- (void)setupCellWithVenue:(Venue *)venue
{
[aViewContainer setupViewWithVenue:venue];
}
/*!
This method is responsible to get cell height dynamically
#param venue object
*/
-(float)getCellHeight
{
return [aViewContainer getViewHeight];
}
Thats it related to customixation of your cell.
now talk about cellForRow and heightForRow:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
VenueCellTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:kVenueCellIdentifier];
if (nil == cell) {
cell = [[VenueCellTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:kVenueCellIdentifier];
}
[cell setBackgroundColor:[[AWPViewFactory sharedInstance]getColor:#"colorWhite"]];
#pragma uncomment below line as per ur need
// Venue *venue = [_dataSourceArray objectAtIndex:indexPath.row];
[cell setupCellWithVenue:nil];
if ([cell respondsToSelector:#selector(setLayoutMargins:)]) {
[cell setLayoutMargins:UIEdgeInsetsZero];
}
return cell;
}
#pragma mark - UITableViewDelegate methods
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
// Use the dictionary of offscreen cells to get a cell for the reuse identifier, creating a cell and storing
// it in the dictionary if one hasn't already been added for the reuse identifier.
// WARNING: Don't call the table view's dequeueReusableCellWithIdentifier: method here because this will result
// in a memory leak as the cell is created but never returned from the tableView:cellForRowAtIndexPath: method!
VenueCellTableViewCell *cell = [self.offscreenCells objectForKey:kVenueCellIdentifier];
if (!cell) {
cell = [[VenueCellTableViewCell alloc] init];
[self.offscreenCells setObject:cell forKey:kVenueCellIdentifier];
}
// Configure the cell for this indexPath
#pragma uncomment below line as per ur need
// Venue *venue = [_dataSourceArray objectAtIndex:indexPath.row];
[cell setupCellWithVenue:nil];
return [cell getCellHeight];
}
couple of things i have to not use in above aproach:
1. No use of automatic row height calculation property.
2.No use of estimated height
3.No need of unnecessary updateConstraints.
4.No use of Automatic Preferred Max Layout Width.
5. No use of systemLayoutSizeFittingSize (should have use but not working for me, i dont know what it is doing internally), but instead my method -(float)getViewHeight working and i know what it's doing internally.
i have shared you most relevant code blocks, i have not still worked on swift. But basics remains same.
Thanks
This is very easy.lets go step by step.
1. set the estimatedRowHeight of table view.It makes easy and fast to load the cell.
2.set the rowHeight property of tableview as UITableViewAutomaticDimension.and don't use heightForRowAtIndexPath.
3.put the all top bottom leading and trailing constraint on both Label.its important.
Lets rock and roll
no need to use stackView
As of iOS 9.1 UIStackView doesn't implement intrinsicContentSize: so the table view can't calculate the height of each cell, thus they are displayed at the estimated height instead.
So, ideally you would simplify your code to mean you don't use a stack view and you don't keep adding and removing (and creating and destroying) views all the time. The root of the solution is to not use a stack view though.
You can continue to use a stack view if you want, but you'll need to create a subclass and implement intrinsicContentSize:. Your reason for using the stack view shouldn't be required though as you can configure the constraints to match first baselines.
As others have mentioned, make sure you're letting the table view cells dynamically resize with:
tableView.estimatedRowHeight = <estimatedHeightValue>
tableView.rowHeight = UITableViewAutomaticDimension
But you've mentioned that you're already doing this.
The rest of the problem is a case of missing constraints. You've already told autolayout that you want your multi-line label to match the top and bottom of the stack view, as in the code below:
englishWordLabel.topAnchor.constraintEqualToAnchor(stackView.topAnchor).active = true
englishWordLabel.bottomAnchor.constraintEqualToAnchor(stackView.bottomAnchor).active = true
But you haven't mentioned to autolayout how this stack view relates to the cell's content view (its superview). This is necessary for dynamically-sized cells because according to Apple:
To define the cell’s height, you need an unbroken chain of constraints and views (with defined heights) to fill the area between the content view’s top edge and its bottom edge. If your views have intrinsic content heights, the system uses those values. If not, you must add the appropriate height constraints, either to the views or to the content view itself.
Your labels have intrinsic content, so that's no problem, but in order to complete the "unbroken chain," you need to tell autolayout that the stack view's top and bottom should be equal to the content view's top and bottom, like so:
// stackView.superview! is the table view cell's content view
stackView.topAnchor.constraintEqualToAnchor(stackView.superview!.topAnchor).active = true
stackView.bottomAnchor.constraintEqualToAnchor(stackView.superview!.bottomAnchor).active = true
Add these two lines after you've added the stack view to the content view, and I believe you'll be in business.
FYI, even if you're programmatically adding these views to your cells like you are, you can still visually debug layout issues quite well with Xcode's awesome Capture View Hierarchy ability. While your app is running from Xcode, click the menu item Debug > View Debugging > Capture View Hierarchy. It's very easy to see what's going on in your view hierarchy, what constraints are active, where your disappearing views went, etc..
2nd FYI, destroying views and then reinstantiating new ones every time a cell appears on screen significantly degrades performance while scrolling. As much as you can, you'll want to take existing views from dequeued cells and reassign their contents with the proper content for that row (and thus avoid view instantiation). You can still do this using your purely-programmatic way of doing things by dequeueing a cell and calling cell.viewWithTag for each possible view. If the method returns nil, you do a one-time instantiation of the view for the cell and give the view a tag (let's say 1 for wordLabel and 2 for englishWordLabel). Then assign the proper content for the row to the view.
I've had very similar problems with autoLayout time and time again. I don't use stackViews much, I typically use them when I have a requirement to add / remove or show / hide a view. When not doing that I opt for just using constraints. I feel the stackviews are quite buggy in quite a few circumstances. Particularly in xib's. I don't know why that makes a difference, but it seems to always cause warnings that I can't remove, when using more than 1 label.
That being said, autoLayout isn't without its issues either, and they all mainly center around preferredMaxLayoutWidth.
To explain, in order for AutouLayout to calculate the height of a label, it needs to know what width the label can be. When you have multiple labels together, particularly in a tableViewCell, this causes all manner of issues.
I asked and subsequently answered a related question here several days later.
What I did is added a subclass of UILabel that always ensures preferredMaxLayoutWidth is updated whenever the label is modified. I've recently updated it for iOS 8, iOS 9 and a separate issue I found with modal views.
So the full steps are:
Create the UIlabel subclass.
Create a cell with your labels laid without UIStackViews.
Set the number of Lines to 0 for one or both labels depending on your usecase.
Set the Labels to be of the type of your subclass.
Implement the estimatedHeightForRowAtIndexPath method to make a guess at what the height might be.
Calculate the height of the cell inside heightForRowAtIndexPath.
To Calculate the height, you can use something like this (which I have abstracted away into a helper method that I can reuse more easily)
// Set the cell data first, i.e. the label text, any programmatic fonts, font sizes etc.
if let tempCell = cell as? UITableViewCell
{
tempCell.width = tableView.width
let size = tempCell.contentView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize)
if tableView.separatorStyle != UITableViewCellSeparatorStyle.None
{
// +0.5 for seperator
return size.height+0.5
}
else
{
return size.height
}
}
I have combined this with a protocol that makes all cells take in a dictionary to set their data. Then I can wrap up this method to take in and pass in a dictionary and reuse the code over and over again.
This has been working seamlessly for me for a very long time.
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *firstLable = firstLable.text;
NSString *secondLable = SecondLable.text;
CGSize constraint = CGSizeMake(cell.frame.size.width/2, 20000.0f);
NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init];
paragraphStyle.lineBreakMode = NSLineBreakByWordWrapping;
CGRect firstLableRect = [firstLable boundingRectWithSize:constraint
options:NSStringDrawingUsesLineFragmentOrigin
attributes:#{NSFontAttributeName:[UIFont fontWithName:#"Your App Font" size:16.0f],
NSParagraphStyleAttributeName: paragraphStyle.copy} context:nil];
CGRect secondLableRect = [secondLable boundingRectWithSize:constraint
options:NSStringDrawingUsesLineFragmentOrigin
attributes:#{NSFontAttributeName:[UIFont fontWithName:#"Your App Font" size:16.0f], NSParagraphStyleAttributeName: paragraphStyle.copy}context:nil];
float max = MAX(firstLableRect.size.height, secondLableRect.size.height) + 20;
//20 for spacing 10 pc above and 10 pc below
return max ;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellIdentifier=#"cellIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (!cell)
{
cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
}
cell.selectionStyle =UITableViewCellSelectionStyleNone;
NSString *lable1 = #"first label text";
NSString *lable2 = #"second lable text";
CGSize constraint = CGSizeMake(cell.frame.size.width/2-10, 20000.0f);
NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init];
paragraphStyle.lineBreakMode = NSLineBreakByWordWrapping;
CGRect firstLableRect = [lable1 boundingRectWithSize:constraint
options:NSStringDrawingUsesLineFragmentOrigin
attributes:#{NSFontAttributeName:[UIFont fontWithName:#"Your App Font" size:16.0f],
NSParagraphStyleAttributeName: paragraphStyle.copy} context:nil];
CGRect secondLableRect = [lable2 boundingRectWithSize:constraint
options:NSStringDrawingUsesLineFragmentOrigin
attributes:#{NSFontAttributeName:[UIFont fontWithName:#"Your App Font" size:16.0f], NSParagraphStyleAttributeName: paragraphStyle.copy}context:nil];
UILabel *firstLable = [[UILabel alloc]initWithFrame:CGRectMake(5,10,constraint.width,firstLableRect.size.height)];
[firstLable setLineBreakMode:NSLineBreakByWordWrapping];
firstLable.minimumScaleFactor = 15.0f;
[firstLable setNumberOfLines:0];
firstLable.textAlignment = NSTextAlignmentLeft;
[firstLable setFont:[UIFont fontWithName:#"your App font" size:16.0f]];
[cell.contentView addSubview:firstLable];
UILabel *secondLable = [[UILabel alloc]initWithFrame:CGRectMake(cell.frame.size.width/ 2+5,10,constraint.width,secondLableRect.size.height)];
[secondLable setLineBreakMode:NSLineBreakByWordWrapping];
secondLable.minimumScaleFactor = 15.0f;
[secondLable setNumberOfLines:0];
secondLable.textAlignment = NSTextAlignmentLeft;
[secondLable setFont:[UIFont fontWithName:#"your App font" size:16.0f]];
[cell.contentView addSubview:secondLable];
return cell;
}

UITableView inside UITableViewCell with dynamic cell height

I have a dynamic list of items, each item could have different teamplate/layout (and height). And one those item types could have an internal list of items to select from, regularly 5-6 rows, each has different height.
If I try to describe it further, in my scenario I have one tableview (#slave) inside tableviewcells (#master-cell) of another tableview (#master). Moreover cells (#slave-cell) in my #slave tableview could have different height as well. So I need to layout my #slave to have #master automatically calc and update its size.
I have the issue with the inner table (#slave). In case of auto-layout, to fit all the cell space, the table will be collapsed unlike UILabel or other controls. So what I need here is to get the projected size of #slave table and set the height of the #slave = content height of the #slave.
I found similar post and it works if all rows have the same height but I'm using custom rows with dynamic height so the tableView.contentSize.Height gives me invalid result (basically just multiply rowNumbers * estimatedRowHeight, you can see it on the screenshot, master item #3 has 4 inner cells). Even after calling #slave.reloadData I couldn't get that size.
What is the proper way to build that kind of UI?
Source code with a test project attached (Xamarin.iOS)
I just ran into the same problem a few days ago,and tried to work it around.
The #master-cell works like a childViewController,it's the delegate datasource of the #slave TableViewController.But you cann't have a childViewController in the UITableViewcell.
Customize UITableViewCell to hold necessary property and acts as #slave TableViewController's delegate datasource,and configure #slave-cell's
height and data.
The real problem is the height for #master-cell,
If your data is simple and static,you can compute the height in advance,and return it in method func tableView(_ tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat of the ViewController.
Otherwise,add a method to #master-cell which return the height for the whole cell when its property is set.And create a proxy #master-cell to compute the height and return it :
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
let cell = CustomUITableViewCell();
let model = self.getModel(indexPath)
cell.model = model
let height = cell.requiredHeight()
return height;
}
It's complex and expensive,but it works.
I think you do not have need of take UITableView inside UITableView. You can take more than one section in UITableView. And use different cellReuseIdentifier. This way your goal will be achieved.
For such a layout ios provide section in tableview, for master items use SectionView(there is delegate method for sectionView -> in which you can provide view for a section) and as different section may have different type of row so make rows according your need and return them according to section.
Perhaps it is because I do not know the background of you project or what you are trying to accomplish, but tableViews inside of tableVIew cells sounds unnecessarily trivial. Rather than using a master tableView with #slave tableViews, it would be cleaner to just break things out by section in a single tableView as stated in a previous answer. There are UITableViewDelegate methods designed to streamline this for you!
first you have to get string's height then the height have to give in below tableView delegate
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return stringHeight;
}
it is working for me.
I'm using Xcode 8.3.2 and Swift3.1.
I had the same requirement, have tried all, nothing worked for me.
Finally, UIStackView is what worked for me.
In a tableviewcell, I have added a UIStackView(Verticle), keep adding sub cells to that UIStackView. And it automatically increased the cell height.
Check the following to add UIStackView programmatically.
Add views in UIStackView programmatically
If you Use Different Sections and Rows use the below format, its working for me,
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
if (indexPath.section == 0) {
return 121;
}
if(indexPath.section==1)
{
return 81;
}
if (indexPath.section%2 == 0 && indexPath.row == 1) {
return 161;
}
if (indexPath.section%2 != 0 && indexPath.row == 0) {
return 81;
}
if (indexPath.section==16 && indexPath.row==0) {
return 161;
}
else
{
return 44;
}
}
i have Template code, different section and row, its each row have different sizes, so i have give this type of code, if you get idea see the above code then its helpful for you,
or
If you change the height for Content text size use the below code, its calculate the content size then change the height(UILabel) size, its working for me
-(CGFloat)tableView:(UITableView*)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
ListModel *model = [ListArray objectAtIndex:indexPath.row];
CGRect labelRect = [model.content boundingRectWithSize:CGSizeMake(tableView.frame.size.width - 90 - 15, 0)
options:NSStringDrawingUsesLineFragmentOrigin
attributes:#{
NSFontAttributeName : [UIFont fontWithName:#"Arial" size:14.0]
}
context:nil];
CGFloat heightOfCell = labelRect.size.height + 60;
if(heightOfCell > 106)
return heightOfCell;
return 106;
}
hope its helpful
yes of course you can have as many prototypes cells as you want for example check this piece of code:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
if indexPath.section == 0 {
let cell = tableView.dequeueReusableCellWithIdentifier("TodayWeatherCell", forIndexPath: indexPath) as! SITodayWeatherTableViewCell
cell.setupCell(upCommingWeather)
cell.aboutCityUpdateTableViewClousure = {
self.tableView.reloadData()
}
return cell
}else {
let cell = tableView.dequeueReusableCellWithIdentifier("cityDetailCell", forIndexPath: indexPath) as! SICityDetailTableViewCell
let detail = detailCity[indexPath.row]
cell.setupCityDetail(detail)
return cell
// Configure the cell...
}
}
There are two different cells in one single UITableView.
Hope it helps.

iOS Set Height of Expanding TableCell with Table View

I created a TableView with Expandable cell as shown here. In my case the expanded area contains a TextView. My view contains a tag (as label text) and its' meaning (in text view). And things are working fine.
I have come up with a situation where a single tag can have multiple meanings. So I need to show all the meanings. So I got to have that many textview's as many meanings for each tag.
I have added a TableView (with TextView in TableCell) inside the expandable part of the table cell. So can add X number of rows in the expanded table cell with each meaning of the tag in single cell. I want to set the height of TextView, TableView and finally TableViewCell. I tried different ways, but am not able to achieve it. Can you help me achieve it the way I am looking for.
Screen shot of something that I am trying to achieve -
UPDATE :-
My cellForRowAtIndexPath method where I am trying to set properties :-
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSLog(#"Table View TAG = %d ROW = %d", tableView.tag, indexPath.row);
// TABLE VIEW IN THE CELL
if (tableView.tag == 10) { // cell.valuesTv
CannedValueCell *ccell = (CannedValueCell *) [tableView dequeueReusableCellWithIdentifier:#"cannedValueCell"];
if (ccell == nil) {
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"CannedValueCell" owner:self options:nil];
ccell = [nib objectAtIndex:0];
}
// Populate valuesTv
ccell.valueTextView.text = [valuesArray objectAtIndex:indexPath.row];
UITapGestureRecognizer *gestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(valueTextViewTapped)];
[ccell.valueTextView addGestureRecognizer:gestureRecognizer];
//[ccell.valueTextView sizeToFit]; // Makes no difference
// CGRect frame = ccell.valueTextView.frame;
// frame.size.height = ccell.valueTextView.contentSize.height;
// ccell.valueTextView.frame = frame;
NSLog(#"*** TEXTView HEIGHT = %f C_CELL HEIGHT = %f ** TV HEIGHT = %f", ccell.valueTextView.frame.size.height, ccell.frame.size.height, cell.valuesTv.frame.size.height );
// *** TEXTView HEIGHT = 79.000000 C_CELL HEIGHT = 80.000000 ** TV HEIGHT = 80.000000
// AS SET HEIGHT IN STORY BOARD - NO DIFFERENCE
return ccell;
} else {
// MAIN TABLE VIEW
static NSString *cellIdentifier = #"CannedCell";
cell = (CannedCell *) [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if(cell == nil) {
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"CannedCell" owner:self options:nil];
cell = [nib objectAtIndex:0];
}
// Set datasource and delegate for the cell's TableView
cell.valuesTv.dataSource = self;
cell.valuesTv.delegate = self;
.......
}
}
Table view delegate method
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
if (tableView == self.cannedTV) {
if (selectedIndex == indexPath.row)
return 150; //(30 + (80 * valuesArray.count));
else
return 30;
}
return 30;
}
}
This is what the result is :-
I tried various things, searched a lot on net, but couldn't find solution for the following :-
Set height of Text View as much as the contents of it.
The inner table should be completely visible - in all cells full textview should be visible with its complete contents properly. So each cell may have different height.
Thus the outer/main table expandable cell should also be accordingly set. - For this I tried (30 + (80 * valuesArray.count));, but the inner table doesn't occupy that much space.
How to get which textview is tapped ? When textview is tapped, the method valueTextViewTapped is fired, but yet am not able to find which textview is tapped on. How can I pass the indexPath.row to valueTextViewTapped method, so can find which textview was tapped on ??
Can you please guide me set the height of the components accordingly and achieve the requirements. Any help is highly appreciated. Thanks.
A table view and table view controller are very powerful tools.
Based on the information provided, your suggested implementation is satisfactory.
A UITableViewCell can have its height set dynamically using the table view delegate method heightForRowAtIndexPath.
You can respond to a user selecting the UITableViewCell using the table view delegate method didSelectRowAtIndexPath.

Effective long text drawing in UITableViewCell

I'm fetching an array of comments, which I need to display in a tableView.
The tableView has custom cells for displaying content. The problem is that comments text can vary in its length and I'm facing a serious memory growth while scrolling the tableView.
Several approaches I've tried to display the text:
UILabel;
UITextView;
drawing on a custom view;
Besides the tableView seems to not be dequeueing my custom cells, so the memory keeps growing while the tableView is being scrolled (my cells are now having 1.5К symbols of text)
Cells are created like this, so nothing special
- (UITableViewCell*) tableView: (UITableView*) tableView
cellForRowAtIndexPath: (NSIndexPath*) indexPath
{
[tableView registerClass: [CommentCell class] forCellReuseIdentifier: kCommentCellId];
CommentCell* cell = [tableView dequeueReusableCellWithIdentifier: kCommentCellId
forIndexPath: indexPath];
Comment* comment = [self.comments objectAtIndex: indexPath.row];
cell.comment = comment;
return cell;
}
Custom setter for comment property
- (void) setComment: (Comment*) aComment
{
self.commentLabel.text = aComment.comment;
[self setNeedsLayout];
}
Adding comment label inside the cell
- (id) initWithStyle: (UITableViewCellStyle) style
reuseIdentifier: (NSString*) reuseIdentifier
{
self = [super initWithStyle: style reuseIdentifier: reuseIdentifier];
if (self)
{
// Comment label
//
self.commentLabel = [UILabel new];
self.commentLabel.textColor = [UIColor colorWithRed: 0.21f green: 0.21f blue: 0.21f alpha: 1.00f];
self.commentLabel.font = [UIFont helveticaNeueRegularOfSize: 13.33f];
self.commentLabel.numberOfLines = 0;
self.commentLabel.lineBreakMode = NSLineBreakByWordWrapping;
[self.contentView addSubview: self.commentLabel];
}
return self;
}
The problem is presumably that you are wrongly retaining and never releasing cell objects in code you are not showing, such as whatever it is you are doing to calculate the varying row heights.
It has nothing to do with the labels themselves, or anything having to do with displaying text of varying heights. I've made tables whose rows vary in height with text of varying length displayed in labels or text drawn directly, and there's no such leakage.
You might want to look over the section from my book on this topic.
The problem was that I somehow was setting the tableview frame height to the height of tableviews' content, so in fact all the cells were visible - thus not being reused.
Thanks #matt for pushing me in the right direction.

Getting the screen location of a cell from a UICollectionView

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.

Resources