I want to remove specific views from my UITableViewCell based on some condition, for example:
if (!model.isThumbnail) {
// remove thumbnail UIImageView
[cell.thumbnailView removeFromSuperview];
}
but this will remove UIImageView from all following cells as well..
Is it possible to remove it only per one specific cell (without creating several different cells)?
Thanks!
Since UITableViewCells are reused somewhat automatically, removing individual views from cells would be problematic. Say that you're using two cell layouts, one, layout1, removes view1, the other, layout2, removes view2. Now if a cell is initially configured for layout1, view1 is removed. The next time it gets reused, it gets reused for layout2, so view2 is removed and view1 was removed the last time, so now it's missing both views.
As implied in CEAFDC's answer, a better approach might be to hide (and subsequently show) those views that aren't necessary.
IMO, a better approach would be to use completely different cell layouts (you can even use the same sub-class, just different use different prototypes in the storyboard) and create whichever layout you need at the time.
Maybe you can add a tag to the specific cell...
Can you show how you create the cell? or tell why you need to remove it?
Not sure if I understand your problem, but I think this would solve:
if (!model.isThumbnail) {
cell.tumbnailView.hidden = YES;
} else {
cell.tumbnailView.hidden = NO;
}
or
cell.tumbnailView.hidden = !model.isThumbnail;
try like this,
if (!model.isThumbnail) {
// remove thumbnail UIImageView
[cell.thumbnailView removeFromSuperview];
} else {
if there is no thumbnailView on cell {
// create thumbnailView and add to cell
}
}
But anyway this isn't good solution.
Related
I have a simple application whereby a UITableView is populated by a user tapping on an item in the UINavigationBar and then getting taken to another view to fill in UITextFields and UITextView. When the user clicks save, the information is saved to Core Data and then represented in this UITableView with the use of NSFetchedResultsController.
One of the attributes is a "Notes" attribute on a "Transaction" Entity. Filling in the Notes in the UITextView is completely optional, but if the user adds in a note, I want to show a custom image that I've created on the cell for the entry that has the note.
When the app is run in this version alone (so deleted and installed with this developer release), it works very well and the cells show the notes only for the cells that have the notes. However, when updating from a previous version of the app, this is where the problem occurs.
The previous version of the app didn't have a Notes attribute and so I used CoreData lightweight migration to set up a new model with a Notes attribute. That part works.
The Problem
Because of the reusing of cells, I'm noticing that when I've updated from an old version to this new version, none of the cells have the custom image and that's good because the notes doesn't exist. However, if I go in and add a new entry with a note and then scroll through my UITableView, I notice the cells start showing the custom image randomly, based on scrolling. So it disappears from one cell and shows up on another. This is a big mis-representation for my users and I'm not quite sure what to do to fix this.
In my cellForRow I have the following code:
self.pin = [[UIImageView alloc] initWithFrame:CGRectMake(13, 30, 24, 25)];
self.pin.image = [UIImage imageNamed:#"pin"];
self.pin.tag = 777;
if (!transaction.notes) {
dispatch_async (dispatch_get_main_queue (), ^{
[[customCell viewWithTag:777] removeFromSuperview];
});
}
if ([transaction.notes isEqualToString:#""] || ([transaction.notes isEqualToString:#" "] || ([transaction.notes isEqualToString:#" "] || ([transaction.notes isEqualToString:#" "]))))
{
[[customCell viewWithTag:777] removeFromSuperview];
}
else
{
[customCell addSubview:self.pin];
}
So the first if statement is to check whether the notes exist and that returns true when updating from an old version of an app to this version. The second if statement just checks if the value of the notes is equal to a few spaces and if so, then to remove the note.
I just can't figure out what's going on here.
Possible Solution
In the same UITableView cell, I also have a green/red dot UIImageView which is displayed depending on whether the user selected a Given or Received Status when adding a new entry. With this in mind, one image is ALWAYS displayed, whether it's the green or red dot. So what I'm thinking about here is creating a transparent square and just changing the if statement to say "If note, show pin image and if not, show transparent image".
That feels a bit like a hack though and I'd prefer a proper way to fix this.
Any thoughts on this would really be appreciated.
First of all, bad practice to allocate views in cellForRow. If you really need to allocate views in cellForRow do it just when it's needed, in your case in the else statement.
Second, do not use dispatch_async to dispatch on main thread if you are already on main thread (cellForRow it's on main thread).
The above points are just some suggestions for performance improvement.
As a solution of your problem, I would create a custom UITableViewCell and in it's method prepareForReuse I would remove the imageView.
EDIT
YourCustomCell.m
- (void)prepareForReuse {
[super prepareForReuse];
[[self viewWithTag:YOUR_TAG] removeFromSuperview];
}
This is a straightforward implementation, but you have to take in consideration that is more expensive to alloc/dealloc the UIImageView than keep a reference to the image and hide it when you don't need it. Something like:
YourCustomCell.h
#interface YourCustomCell : UITableViewCell {
IBOutlet UIImageView *theImageView; // make sure you link the outlet properly ;)
}
YourCustomCell.m
- (void)prepareForReuse {
[super prepareForReuse];
theImageView.hidden = YES;
}
And in cellForRow you just have to check if you have notes and make the imageView visible (probably you will make theImageView a property)
Because table view cells are reused you must have a default value for your image. So for example set your image to transparent by default and change it under some condition. This will stop your image being shown in reused cells.
Why do you have a dispatch_async here?
if (!transaction.notes) {
dispatch_async (dispatch_get_main_queue (), ^{
[[customCell viewWithTag:777] removeFromSuperview];
});
}
Because you cannot be sure when the function inside it will execute. Suppose that transaction.notes is nil. All the isEqualToString functions will return false and the else condition of addSubView will be called. But sometime after this function is exited the code inside dispatch_async will be run and remove the pin view. I'm not whether this is the intended behavior.
I'm trying to create a view similar to add/edit contact in iOS and there are a few things that are happening, and I'm not sure how they are implemented. Any help in understanding is greatly appreciated.
For each section in contacts i.e. name, phone number, email, etc are these each their own tableview or are these sections within a larger tableview?
When clicking done when adding or editing a contact, the unused tableview cells disappear. Is this using deleteRowsAtIndexPaths:withRowAnimation: or is there a hide method I haven't found? If it is using that method, then when clicking the edit contact button, how does the view brings back these unused tableview cells?
When clicking on a cell in the tableview cell when editing a contact, you are able to change the text. Is this a textfield within a tableview cell or is it actually modifying the label of the tableview cell?
I am not looking for any specific code, as a fairly new programmer I am just trying to understand the strategies/best way to implement these features.
I tried a lot different ways to implement that. the easiest one: Subclass UITableViewCell and overwrite setFrame:. note that this is easy to achieve for grouped tables, but hard for plain ones. in the datasource's tableView:cellForRowAtIndexPath: create an object of this custom cell for the first section. use another identifier for cells of that section, so that only the correct cells will be re-used.
yes, I assume that. The controller has some sort of definition how many cells has to be show in edit mode and how many are actually used with some sort of information. you can easily create a array of indexPaths that must be deleted.
I would do it in tableView:didSelectRowAtIndexPath: by fetching the cell via tableView:cellForRowAtIndexPath:, hide the label and unhide or add a textfield and make this first responder.
code for 1.
the cell
#interface InsetCell : UITableViewCell
#property(nonatomic)CGFloat inset;
#end
#implementation InsetCell
- (void)setFrame:(CGRect)frame {
CGFloat inset;
if (_inset == 0) {
inset = 70; //default value
} else {
inset = _inset;
}
frame.origin.x += inset;
[super setFrame:frame];
}
-(void)setInset:(CGFloat)inset
{
_inset = inset;
[self setNeedsLayout];
}
#end
a project that uses similar code
I am making a custom header view on my tableview. The custom view has a gradient on it.
I only want that gradient to show if it is the only header visible.
So if a user is scrolling and happens to see two sections of the tableview, the second section on the tableview should not have a gradient.
What is the best approach to do this?
Here are some thoughts:
Perhaps in your table's delegate, you can cache the header views, and every time one is requested, check it's peers to see if they are on screen (determined by UIView's .window property being non-nil).
- (UIView *)tableView:tableView viewForHeaderInSection:section {
if([_headerViews objectAtIndex:section-1].window || [_headerViews objectAtIndex:section+1].window) {
// there are peers on screen
} else {
// this is the only one onscreen
}
}
This is just sample code, and does not ensure that the views are properly initialized, etc. just an idea.
I have a table whose cells contain labels. Whenever I dequeue a reusable cell, the old labels are still lingering on it. I was able to remove them with this:
for(int a=[[newcell subviews]count]-1; a>=0;a--)
{
if([[[[newcell subviews]objectAtIndex:a]class] isSubclassOfClass:[UILabel class]])
{
[[[newcell subviews] objectAtIndex:a] removeFromSuperview];
}
}
But when I select the cell, I can see the old text on top of the new. I tried this:
[[newcell.selectedBackgroundView subviews] makeObjectsPerformSelector: #selector(removeFromSuperview)];
[[newcell.backgroundView subviews] makeObjectsPerformSelector: #selector(removeFromSuperview)];
But it didn't work. How can I make the old labels disappear from the selected cell as well as the regular view of the cell?
Subclass UITableViewCell (if you aren't already). Override prepareForReuse and remove the labels there. Might work
This kind of problem tends to happen when you add subviews to your cells in cellForRowAtIndexPath: regardless of whether it's being dequeued or newly created. As a result, you end up creating a new subview each time the row is reused, and the old subviews accumulate.
What you instead want to do is to use the same subview each time, but just set the relevant attributes (e.g., labels or color) each time. Check out the answers to How do I clear a cell completely when I reuse it? to see some possible approaches.
I kinda did what Yuji suggested. Instead of putting in new labels on each iteration, I checked whether the cell contained labels and then either edited the labels if they were there or put them in if they weren't. Code goes like this:
if([[newcell.contentView subviews] count]>=2 && [[[[newcell.contentView subviews] objectAtIndex:0]class] isSubclassOfClass:[UILabel class]] &&
[[[[newcell.contentView subviews] objectAtIndex:1]class] isSubclassOfClass:[UILabel class]])
{
//change the text of the labels
}
else
{
//add the labels to the cell
}
In my app I have a table view with 12 types of custom cells.
I designed each within the storyboard.
In general everything works fine - thanks to your tutorials :)
My problem is, that I have individual styles an additional data in each cell - therefore I don't want them to be reused.
How can I generate a individual custom cell from storyboard to provide it in the -
tableView:cellForRowAtIndexPath:indexPath?
Any suggestions?
Thanks
Dominic
Added:
newCell = [tableView dequeueReusableCellWithIdentifier:cellType];
cell = newCell;
[cell styleWithElement:element andResubItem:resubItem];
my problem is, I need another way to create a custom-styled cell from the storyboard than the quouted above - I have no XIB and the above way is the only way I know to create my cell.
Could it be a solution to create a celltype once the above way and then copy the cell? Is there a way to copy a cell-object?
You can generate a random cell ID each time so it won't be reused.
However, that would not be very good for scroll performance and memory consumption, so consider actually reusing the cells (i.e. come up with a way to replace data in them).
Edit:
you can always just copy the views from one to another, something along these lines:
UITableViewCell* myCell = [UITableViewCell alloc] initWithStyle:YOUR_CELL_STYLE reuseIdentifier:YOUR_RANDOM_ID];
for (UIView *view in newCell.subviews) {
[view removeFromSuperview];
[myCell addSubview: view];
}
You may also need to adjust the frame of myCell to be the same as that of newCell, and if newCell is an action target for any events (e.g. clicking a control element within the cell triggers some action defined in the cell class) you'll need to reassign those to the myCell, too.
Edit2:
To move actions you could do something like this:
NSSet* targets = [control allTargets];
for(id target in targets) {
NSArray* actions = [control actionsForTarget:target forControlEvent:UIControlEventTouchUpInside];
for(NSString* selectorName in actions) {
[newControl addTarget:target action:NSSelectorFromString(selName) forControlEvents:UIControlEventTouchUpInside];
}
}
Substitute your new cell for the target - and make sure it implements all the necessary selectors (or you can put a new selector). The code above will replace targets for UIControlEventTouchUpInside - you can use the ones you need instead (or use allControlEvents to enumerate the ones used by a control).