Im facing a problem with adding a UIImageView to a single UITableViewCell. I add the subview like this in the cellForIndexPath delegate method which is ONLY added to the cell if the self.mediaTypeArray contains the string: "Image" at the index: indexPath.row
let cell = self.timelineTableView.dequeueReusableCellWithIdentifier( NSStringFromClass(ClassicCaseCell), forIndexPath: indexPath) as? ClassicCaseCell
if self.mediaTypeArray[indexPath.row] == "Image" {
let imageView = UIImageView()
imageView.frame.size = (cell?.evidenceView.frame.size)!
imageView.frame.origin = CGPointZero
imageView.image = img
cell?.evidenceView.addSubview(imageView)
}
Which it works great however, I find that every other two cells contains that same imageView even when self.mediaTypeArray[indexPath.row] is NOT equal to "Image" and I don't understand why. I think it may have something to do with reusable tableviewcells but I still don't see why it would do this. Please help!
I'm getting the feeling that this is because of cells being re-used.
A way you can work around this is to override your ClassicCaseCell's prepareForReuse method to remove any image view from its evidenceView.
I would however recommend that you don't add the image view in this fashion (through the delegate's cellForIndexPath method). Instead, your ClassicCaseCell should hold the image view as an instance variable which you can then set in cellForIndexPath. This way, there is only at most one. You can also make sure to set the image view to be nil in prepareForReuse, making sure that it won't appear in the cell if it is not set.
First, don't use tag property as recommended elsewhere. That was a technique used a long time ago, but Apple discourages that practice now. Second, I'd suggest you simplify your life and simply don't programmatically add image views in cellForRowAtIndexPath. If you programmatically add image views, cell reuse introduces a clumsy process of determining whether (a) you need an image view; (b) whether there is an existing image view; and (c) possibly adding/removing image view and/or getting reference to existing one.
One very simple solution is to just have two cell prototypes, one with an image view and another without. Then, based upon the media type, dequeue a cell with the appropriate storyboard identifier and use it.
The other alternative is to have the image view in the cell regardless, and hide/show it as appropriate. The challenge then becomes how to best manage two sets of the constraints, one for when the image view is visible and one when it's not. You can do this with judicious choice of constraint priorities, activating/deactivating the appropriate constraints in cellForRowAtIndexPath, etc. It can be done, but this is more cumbersome than the above approach, whereby you just employ two cell prototypes.
You only need to add the UIImageView once so if the cell is re-used again, it (might) already be there. Your problem is to detect if you've already added it or not. Here are a couple suggestions:
1) ALWAYS create it (and just don't set the image, or hide it)
2) assign it a unique tag and look for the tag when you need to set it... no tag, then create it
Override prepareForReuse delegation in your tableview cell and remove imageview from there
Related
I have a UICollectionView which allows for a user to select a cell and upon doing so view 'A' will appear. I am wondering if it is possible for the subviews of this cell, ex: UIlabel and UIImageView to provide a different functionality for when they alone are selected. For example, if the UIImageView is selected, I want to segue to view 'B' as opposed to 'A'.
I have attempted to implement a UITapGestureRecognizer for both the label and the image, however, the cell's functionality overrules and the resulting view is still 'A'. Any ideas?
Thank you in advance.
What you want to achieve is possible through delegates if you don't have custom cell make a custom cell class then inside custom cell declare your protocol
I assume you have to disable the default behavior of the collection view cells:
cell.selectionStyle = UITableViewCellSelectionStyle.none
However, if you set that and you encounter an overlap issue, please take a look at the 'cancelTouchesInView' property of the 'UITapGestureRecognizer'. Basically, by setting that to false, you allow the children to also receive touch actions.
Furthermore, do not forget each gesture recognizer should have it's own method for you to be able to segue into two different places.
I'm struggling to find a way do something like this:
I'm using mostly UICollectionView to make my custom lists. And this time I want to add another UICollectionView inside a UICollectionViewCell.
The Layout is formed by a list of card, containing the informations below and inside it a list of items that came inside the same array.
Any ideias of how should I do this?
P.S: I'm not concerned about performance or something like this. So any idea would be welcome.
For your case I see some solutions:
Use only one UICollectionView and reuse needed cells for correct layout;
Use 'UICollectionView' inside UICollectionViewCell. You need to add another collection view as subview in one of your cells;
Configure subviews in your UICollectionViewCell as you want to showing needed content correct.
In my opinion you can choose first or last method, based on layout of your content and possibly dynamic height (if it's needed in your app).
I managed to do this in another way. Instead of embed a UICollectionView inside another, I iterated through the array of elements and created subviews above the label.
if let item = info["PRODUTOSITENS"] as? [[String:Any]] {
for produto in item {
// All the business logic
let DynamicView = UIView(frame: CGRect(x:Double(cell.status.frame.origin.x), y:y, width:Double(collectionView.frame.size.width - 20.0), height:80.0))
cell.addSubview(DynamicView)
}
}
I know this question is dumb. But suddenly got stuck up with this question.
When I use dequeReusableCellWithIdentifier, the cells are reused. To be more specific, first 'n' set of cells is reused - along with their references
As the same references are reused, I can't actually store local variables to the cells. I actually need to assign them everytime in cellForRowAtIndexPath
Assume I'm using a custom complex UITableviewcell. (I know we should reduce complexity. But still...)
Some views are to be added to the cell.
Best example i could think of is an image slider.
So the number of images in the image slider differs based on the datasource. So i need to add the views in cellForRowAtIndexPath. Can't avoid it.
Since the same reference of cells are reused, I need to add these views everytime cellForRowAtIndexPath is called. I think that is a bit of heavy load.
I thought of using drawRect method, but it does not work with UITableViewAutomaticDimension
If I don't use dequeReusableCell, I will have individual referenced cells, but it will cost the memory and performance.
So what is the best solution?
Can I use dequeReusableCell and still need not rewrite its content?
Edit 1:
By mentioning dequeReusableCell, I did mention dequeueReusableCellWithIdentifier - forIndexPath. I'm sorry for the confusion.
Edit 2:
I feel, I'm not so clear in my question.
Lets consider I have a array in my viewcontroller.
I'm configuring my cell in cellForRowAtIndexPath with dequeueReusableCellWithIdentifier - forIndexPath
So what happens is, everytime i scroll, when the invisible rows become visible, cellForRowAtIndexPath is called.
Lets say I have a image slider with n imageviews. For each cell, this 'n' differs. So I'm forced to draw the cell's view based on its dataSource.
So everytime the tableView calls cellForRowAtIndexPath, the cell is configured again and again.
I want to avoid this to improve performance.
what I do in this case is the following:
I always use dequeReusableCell:, for reasons you already said
I write a cleanCell method in the custom UITableViewCell class in order to clean everything has been already set on the cell (just to make it ready for reuse)
then in the cellForRowAtIndexPath: I configure my cell as desired
That's what I would do: I would move the logic of adding those heavy views inside the cell's class itself. So that in cellForRowAtIndexPath I would just set an object that has to be displayed inside the cell, and cell would create all the necessary views on its own (it's a view layer, after all). So inside cellForRowAtIndexPath it would look something like this:
cell = [tableView dequeueReusableCellWithIdentifier...forIndexPath...];
cell.modelObject = myModelObject;
and that's it.
Then inside the cell class I would implement optimizations for switching between different model objects. Like, you could defer releasing the heavy views, and reuse them, when the cell is reused.
To be honest, I don't get your example with the slider, but let's suppose you have a star rating with 5 stars, which can be visible or not. There are various way to do it, but let's assume you're just adding / removing a UIImageView for each star. The way you do it now, you would have an empty cell and create / add views in cellForRow. What I'm suggesting is to have 5 UIImageViews as part of your cell, and inside the cell set their visibility based on the modelObject.rating. Something like that. I hope, it helps to illustrate my point :)
UPDATE: when your cell can have an arbitary number of images inside of it, I would probably create them inside the cell's class. So, for instance, for the first model object we need 3 image views. So we create them. But then we don't release them in prepareForReuse, we wait for the next model object to come. And if it has, say, 1 image, we release 2 image views (or we don't, so that we didn't have to recreate them later, it depends on what is more critical: performance, or memory usage), and if it needs 5, we create two more. And if it needs 3, we're all set already.
In your case you should use dequeueReusableCellWithIdentifier(_ identifier: String, forIndexPath indexPath: NSIndexPath) method. This method automatically decides whether the cell needs to be created or dequeued.
I need to add these views everytime cellForRowAtIndexPath is called. I think that is a bit of heavy load.
You don't need to add those views every time cellForRowAtIndexPath gets called. Those views should be already added as a cell is instantiated. In cellForRowAtIndexPath you only assign images and other values to those views.
In case if you use storyboards and prototype cells your code should look like this:
var items = [UIImage]()
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("ImageCell", forIndexPath: indexPath)
let item = items[indexPath.row]
let recipient = item.recipients.first
// I have given my UIImageView tag = 101 to easily access it from the view controller.
// This allows me to avoid defining a custom UITableViewCell subclass for simple cells.
if let imageView = cell.viewWithTag(101) as? UIImageView {
imageView.image = item
}
return cell
}
1)I think you need to use "lazy load" of cells.
http://www.theappguruz.com/blog/ios-lazy-loading-images (for example)
2) You can create a property in your viewController, and create an NSMutableDictionary property, which will store the some data by the some key,
if your model have things like "ID" of the cells or you can use indexPath.row for it, then you can load it only one time, and next time when -cellForRow will be called, you can retrieve data from your dictionary by the
[self.dataDictionary objectForKey:#"cellID"];
And with this you, solve your repeating load.
In -cellForRow you can set the check,like.
if ([self.dataDictionary objectForKey:#"cellID"]) {
cell.cellImage = [self.dataDictionary objectForKey:#"cellID"];
} else {
UIImage *img = [uiimage load_image];
cell.cellImage = img;
[self.dataDictionary setObject:img forKey:#"cellID"];
}
And so on. Its like example, the dataType's you can choose by yourself, it's example.
Thanks for everyone helping me out. I think the better answer was from #Eiko.
When you use reusable cells, it must be completely reusable. If you are going to change its views based on the data, you either have to use a new identifier or avoid using reusable cells.
But avoiding reusable cells, will take a load. So you should either use different identifiers for similar cells or as #FreeNickName told - you can use an intermediate configuration and alter it on your need.
Our app has a social feed with a name label similar to Facebook with a FeedController and FeedCell class. Each time we scroll back to a previously loaded cell, it reloads, so the name label becomes darker, the text becomes bolder and starts to overlap. How do we fix this? We tried the prepareToReuse method and setting the cell's label to nil, but that did not help. Any help would be greatly appreciated!
It appears that you're regenerating the label and adding it to your cell's content view every time that the label is reused.
Inside your FeedCell class, make sure to only initiate your interface elements in your initializer. From there, you can fill your cell with data in the cellForRowAtIndexPath: method of your UITableViewDataSource.
Inside cellForRowAtIndexPath:, make sure to not add any views to the cell as this could contribute to the issue you're describing. As a rule of thumb, I make sure to initialize the views in the cell subclass's initializer, and only manipulate properties of these views (like hidden, alpha, etc) when working with the cells.
Moving forward, you can use prepareForReuse to clear any data from your cell that may be leftover from the previous time the cell was used. In many cases though, this isn't necessary as the data will be changed in cellForRowAtIndexPath:.
// actual question
I need help in loading the nib file (tableviewcell) using a different cell Identifier than the one specified in the nib.
// background on what I am doing
I have a custom UITableViewCell with few labels, few images, and few buttons. All this is laid out in a nib file. The content of the tableview cell needs to change dynamically based on certain conditions i.e if landscape a button is not displayed; If there is no content to display in a label then the label is not displayed etc. Adjacent label/view on the left should extend to fill up the space of the missing label/button.
I am able to get this to work. The way I did this is in cellForRowAtIndexPath I
- remove the views that doesn't need to be displayed using removeFromSuperView and by
- adjusting the frame and call setNeedsDisplay for the view that needs adjusting.
I use the same cell Identifier mentioned in the nib file to dequeue the cell. If the returned cell is nil then I use the loadNibNamed:withOwner:options to load the nib. However I am afraid that this will cause issues when scrolling back and forth as the cell being reused might not have the views that are required to display content when the conditions aren't met as they might have already been removed.
To fix this issue, I am planning to use a different cell identifier for different conditions that trigger a different look and feel of the cell. Alternatively I am also considering to just hide the view and extend the adjacent view over the hidden view. This way I don't need to deal with different cell identifiers.
Edit2:
My alternative approach of hiding and adjusting frame of adjacent view worked for my needs.
However I still want to find the answer to my actual question I described above.
// potential solution
I was wondering if I could pass the cell identifier to the nib loading code via the options parameter of loadNibNamed function. Is this possible? If you guys can provide a sample code to achieve this that would be awesome.
Thanks for your time.
All you need to do is create multiple cells in the nib with different identifiers and then call dequeueReusableCellWithIdentifier with the appropriate identifier in order to get a reference to the appropriate type of cell.
I'm not very proud of this solution and may do problems, but I would try set cell's identifier after loading from nib.
- (id)initWithReuseIdentifier:(NSString *)reuseIdentifier
{
self = [[NSBundle mainBundle] loadNibNamed:#"MyCell" owner:nil options:nil].lastObject;
[self setValue:reuseIdentifier forKeyPath:#"_reuseIdentifier"];
// ...
return self;
}
You can't change the cell reuse identifier from that specified in the nib. Reference. The options dictionary you are talking about won't do this for you either. The options are related to proxy objects in the nib.
Rather than removing subviews, you would be better off simply hiding them. You could have IBOutletCollections to make bulk hiding/unhiding easier. You could reset the cell to its default state in prepareForReuse if needed.
Look at UINib as well - this gives faster creation of objects from a nob than loadNibNamed, which will help your scrolling performance.