Calling setCollectionViewLayout:animated does not reload UICollectionView - iphone

I am using a UICollectionView with two custom layouts - which are subclassed from UICollectionViewFlowLayout
When my view first loads I set it to a default view using:
[self.collectionView setCollectionViewLayout:self.tableViewLayout];
This calls: cellForItemAtIndexPath - so no problem there. This is in viewWillAppear method.
Then I use a UIButton to change the layout from current view to the next layout like so:
-(void)changeViewLayoutButtonPressed
{
self.changeLayout = !self.changeLayout;
if (self.changeLayout){
[self.tradeFeedCollectionView setCollectionViewLayout:self.grideLayout animated:YES];
}
else {
[self.tradeFeedCollectionView setCollectionViewLayout:self.tableViewLayout animated:YES];
}
}
After this call - numberOfItemsInSection is called. I have checked in the debugger that the return account is not zero.
After that call, nothing else is called and my UICollectionView changes the layout but since cellForItemAtIndexPath is not called - the cells are not setup correctly.
if I put [self.collectionView roloadData] inside of numberOfItemsInSection - then cellForItemAtIndexPath is called and cells are setup.
I shouldn't need to do a manual call to reload the data in numberOfItemsInSection. Can someone tell me whats going on?
Edit
I forgot to mention I am using two different UINibs for the two different cells as they need slightly different layouts applied to them. Would this be an issue?
Thanks!
Edit 2
Right, so I have gotten it to work almost like I want it to. Here is all the code used for the collection view and how I change its layouts.
The Custom Layouts
These are subclassed from UICollectionViewFlowLayout - The reason I subclassed is simply due to the face my UICollectionView needs to look and behave very close to Apple's pre-made layout. Just different cell sizes, though.
TableViewLayout
- No code in the header file.
- BBTradeFeedTableViewLayout.m
#implementation BBTradeFeedTableViewLayout
-(id)init
{
self = [super init];
if (self){
self.itemSize = CGSizeMake(320, 80);
self.minimumLineSpacing = 0.1f;
}
return self;
}
#end
Gride Layout
- No code in the header file.
- BBTradeFeedGridViewLayout.m
#implementation BBTradeFeedGridViewLayout
-(id)init
{
self = [super init];
if (self){
self.itemSize = CGSizeMake(159, 200);
self.minimumInteritemSpacing = 0.1f;
self.minimumLineSpacing = 0.1f;
}
return self;
}
#end
Then in the ViewController that has my UICollectionView - I declare the two custom layouts like so:
#property (strong, nonatomic) BBTradeFeedTableViewLayout *tableViewLayout;
#property (strong, nonatomic) BBTradeFeedGridViewLayout *grideLayout;
I then register the two .xib files for the collectionView:
[self.tradeFeedCollectionView registerNib:[UINib nibWithNibName:#"BBItemTableViewCell" bundle:nil] forCellWithReuseIdentifier:#"TableItemCell"];
[self.tradeFeedCollectionView registerNib:[UINib nibWithNibName:#"BBItemGridViewCell" bundle:nil] forCellWithReuseIdentifier:#"GridItemCell"];
Then, I have a UIButton that changes the layout:
-(void)changeViewLayoutButtonPressed
{
if (!self.gridLayoutActive){
self.gridLayoutActive = YES;
[self.tradeFeedCollectionView setCollectionViewLayout:self.grideLayout animated:YES];
self.lastLayoutUsed = #"GridLayout";
}
else {
self.gridLayoutActive = NO;
[self.tradeFeedCollectionView setCollectionViewLayout:self.tableViewLayout animated:YES];
self.lastLayoutUsed = #"TableLayOut";
}
[self.tradeFeedCollectionView reloadItemsAtIndexPaths:[self.tradeFeedCollectionView indexPathsForVisibleItems]];
}
Finally - when a web service call is done - it calls [self.tradeFeedCollectionView reloadData];
So there my UICollectionView gets reloaded and the last method:
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{ static NSString *tableCellIdentifier = #"TableItemCell";
static NSString *gridCellIdentifier = #"GridItemCell";
if (indexPath.item < [self.tradeSearchArray count]){
if (self.gridLayoutActive == NO){
BBItemTableViewCell *tableItemCell = [collectionView dequeueReusableCellWithReuseIdentifier:tableCellIdentifier forIndexPath:indexPath];
if ([self.tradeSearchArray count] > 0){
self.toolBarButtomItem.title = [NSString stringWithFormat:#"%d Results", self.searchResult.searchResults];
tableItemCell.gridView = NO;
tableItemCell.backgroundColor = [UIColor whiteColor];
tableItemCell.item = self.tradeSearchArray[indexPath.row];
}
return tableItemCell;
}else
{
BBItemTableViewCell *gridItemCell= [collectionView dequeueReusableCellWithReuseIdentifier:gridCellIdentifier forIndexPath:indexPath];
if ([self.tradeSearchArray count] > 0){
self.toolBarButtomItem.title = [NSString stringWithFormat:#"%d Results", self.searchResult.searchResults];
gridItemCell.gridView = YES;
gridItemCell.backgroundColor = [UIColor whiteColor];
gridItemCell.item = self.tradeSearchArray[indexPath.row];
}
return gridItemCell;
}
The problem with the above code
The above code does work. However, the cells don't animate nicely and it looks odd, almost as if a refreshed happened. Which is due to calling:
[self.tradeFeedCollectionView reloadItemsAtIndexPaths:[self.tradeFeedCollectionView indexPathsForVisibleItems]];
But that is the only way I could get it to be close to what I want.

-setCollectionViewLayout: or -setCollectionViewLayout:animated: won't cause your UICollectionView reload its data.
If you want to change your cell style or update the data, call [self.collectionView reloadData] and UICollectionViewDataSource protocol methods will be called.
If you want to change your UICollectionView layout to another, call -setCollectionViewLayout:. Or if you want to update your UICollectionView layout, just call [self.collectionView.collectionViewLayout invalidateLayout].
Cell layout and data are two different things in UICollectionView.
Update
you should discard all the stale data, probably by -reloadData. And calculate the new frames for every cell in your UICollectionViewLayout subclasses. Override - (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath and - (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect.
You decide the new contentOffset. For instance, you could keep the indexPath of some visible cell and decide to scroll to that indexPath or setContentOffset: to the frame.origin of the new cell at the same indexPath after your layout changed.
And I find this answer may give you help.
Also check the awesome video WWDC 2012 Advanced Collection Views and Building Custom Layouts

Simply call reloadData before you switch your layout (the performBatchUpdates call is not mandatory):
[self.collectionView reloadData];
[self.collectionView.collectionViewLayout invalidateLayout];
[self.collectionView setCollectionViewLayout:yourLayout animated:YES];
In Swift:
self.collectionView?.reloadData()
self.collectionView?.collectionViewLayout.invalidateLayout()
self.collectionView?.setCollectionViewLayout(yourLayout, animated: true)

You should call [self.tradeFeedCollectionView invalidateLayout]; before
if (self.changeLayout){
[self.tradeFeedCollectionView setCollectionViewLayout:self.grideLayout animated:YES];
}
else {
[self.tradeFeedCollectionView setCollectionViewLayout:self.tableViewLayout animated:YES];
}

(Posted solution on behalf of the question author).
I finally found a nice and simple solution.
The marked answer was correct, that I needed to call, -reloadData on my UICollectionView - However, this destroyed the animation when switching between the two cells.
After taking in suggestions from the marked answer - I just reworked my `UIButton' method, that changes the cells
I found that if I call:
[self.collectionview performBatchUpdates:^{ } completion:^(BOOL finished) {
}];
According to the WDDC video - this animates the changes works with -reloadData method:
Example:
[self.collectionView reloadData];
[self.collectionView performBatchUpdates:^{
[self.collectionView.collectionViewLayout invalidateLayout];
[self.collectionView setCollectionViewLayout:self.grideLayout animated:YES];
} completion:^(BOOL finished) {
}];
}

You should only have to call this method:
[self.collectionView setCollectionViewLayout:layout animated:YES];
I had a similar bug, but it ended up that my subviews of the cells were not setup with Auto Layout. The cell was resizing correctly, but my subviews were not, so it looked like the cell size never changed. Check this in your code quickly by setting clipsToBounds = YES on your cell.contentView.

Here is the simple trick if you want to update layouts Use it wherever you are updating the collectionView:
[self.collectionView removeFromSuperview]; //First remove the Collection View
//Relocate the view with layouts
UICollectionViewFlowLayout *layout=[[UICollectionViewFlowLayout alloc] init];
layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
layout.minimumInteritemSpacing = 10;
layout.minimumLineSpacing = 10;
self.collectionView = [[UICollectionView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.width,100) collectionViewLayout:layout];
[self.collectionView setDataSource:self];
[self.collectionView setDelegate:self];
[self.collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:#"cellIdentifier"];
[self.collectionView reloadData];
[self.view addSubview:self.collectionView];

I found that to easily and nicely animate both cells' kind and layout simultaneously it is best to reloadSections (instead of reloadData) followed by setting a new layout with animation:
[self.collectionView reloadSections:[NSIndexSet indexSetWithIndex:0]];
[self.collectionView setCollectionViewLayout:newLayout animated:animated];

Related

Custom Cell, only last cell gets [layoutSubviews]?

I'm creating a Settings View for my app, and in that view is a UITableView. I'm creating custom cells to meet my needs, but I'm having issues - only the last cell is getting [layoutSubviews]. Am I doing something wrong?
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
//int type = (indexPath.row == 0?1:0);
//if(indexPath.row == 6) type = 2;
NSLog(#"row %i created", indexPath.row);
TableCell *cell = [[TableCell alloc] initWithType:indexPath.row];
cell.textLabel.text = #"Test cell";
return cell;
}
And in my custom cell:
#implementation TableCell
UIImageView *shadowView;
int row;
- (id) initWithType:(int)type {
row = type;
self = [super initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil];
self.backgroundColor = [UIColor clearColor];
self.backgroundView = [[UIView alloc] init];
UIImage *shadowImage = [UIImage imageNamed:#"CellShadow"];
shadowImage = [shadowImage resizableImageWithCapInsets:UIEdgeInsetsMake(14, 14, 14, 14)];
shadowView = [[UIImageView alloc] initWithImage:shadowImage];
[self.contentView addSubview:shadowView];
//[self.contentView sendSubviewToBack:shadowView];
NSLog(#"agreed, row %i created", row);
[self layoutSubviews];
return self;
}
- (void) layoutSubviews {
NSLog(#"row: %i", row);
[super layoutSubviews];
shadowView.frame = CGRectMake(
0, 0,
self.contentView.frame.size.width,
self.contentView.frame.size.height
);
}
#end
Continuously, only the last cell #6, is reported when I rotate, or when layoutSubviews should be called. Any suggestions?
Do not call layoutSubviews directly. Use [self setNeedsLayout] or [self layoutIfNeeded]. But do not call these at all in the cell's init method.
Also, do not call [[TableCell alloc] initWithType:indexPath.row]; directly, either. Instead, use...
- (id)dequeueReusableCellWithIdentifier:(NSString *)identifier forIndexPath:(NSIndexPath *)indexPath
Once you've built that cell, you can tell it it's row, but be aware that the cells get recycled as the table scrolls, so you must update that value on every call to cellForRowAtIndexPath.
The cells ought to get layout again (without you making any calls direct or indirect) when the table view is resized.
See the tableview doc here.
You should never call layoutSubviews directly, it will be called automatically by iOS once the cell is ready to display. You should also deque the cell as #danh is recommending. If you're not very comfortable with all this, then I'd really recommend you have a look at the free Sensible TableView framework, which automates creating these kind of settings views (I create mine in a couple of lines, really).
The issue was of my own poor code. Using cell.backgroundView helped a lot here.
Never Call layoutSubviews by yourself. It will be called when ever frames of subview in cell are changed. Even if just change the text of labels in your custom cell wont call layoutSubviews. Ue the deque of cells for reusing for better performance. As it wont allocate cell every time. And in you code looks like has lot of memory issues since cell allocated wont be released and new cell is created.

CellForRowAtIndexPath is not invoked

I'm writing an app hat has many views and I used sliding views library (ECSlidingViews). The problem is when I fill an array with objects and fill the table with the objects in tableView:cellForRowIndexPath: method, it does present the data in the table, but when I go to other view and come back the data disappears because tableView:cellForRowIndexPath: is not called. Here is the code:
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(#"begin of cellForRowAtIndexPath");
SchedualeCell *cell = (SchedualeCell *)[tableView dequeueReusableCellWithIdentifier:#"Cell"];
Course *temp = [array objectAtIndex:indexPath.row];
cell.nameLb.text = temp.name;
cell.hourLb.text = [NSString stringWithFormat:#"%d",temp.hour];
cell.startTimeLb.text = temp.startTime;
cell.endTimeLb.text = temp.endTime;
NSLog(#"End of cellForRowAtIndexPath");
return cell;
}
tableView:numberOfRowsInSection: and numberOfSectionsInTableView: is invoked when I come back to the view that has the table view.
P.S.: The view is UIViewController that has table view inside of it, and I searched all StackOverflow before posting my problem.
EDIT : this is where I set the delegate and datasource
- (void)viewDidLoad
{
[super viewDidLoad];
NSLog(#"Did Appear");
// Do any additional setup after loading the view.
self.view.layer.shadowOpacity = 0.75f;
self.view.layer.shadowRadius = 10.0f;
self.view.layer.shadowColor= [UIColor blackColor].CGColor;
if (![self.slidingViewController.underLeftViewController isKindOfClass:[MenuViewController class]])
{
self.slidingViewController.underLeftViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"Menu"];
}
[self.view addGestureRecognizer:self.slidingViewController.panGesture];
if (array == nil)
{
array = [[NSMutableArray alloc]init];
}
table.delegate = self;
table.dataSource = self;
[table reloadData];
}
and I did included The delegate and datasource in the header
#interface MainViewController : UIViewController<UITableViewDataSource , UITableViewDelegate,addDelegate>
First of all , it's not numberOfRowsAtSection and numberOfTableView. It's numberOfSectionsInTableView and numberOfRowsInSection.
Things you can do :
1) NSLog your numberOfRowsInSection. Note that , If it's "0" then your cellForRowAtIndexPath is never going to be called.
2) If you are using your UITableView inside some UIView then you should add :
[self.view addSubview:table];
3) Don't Forget to include :
#interface yourViewController : UIViewController<UITableViewDataSource,UITableViewDelegate>
Once check that you reframed the tableview in ViewWillAppear.
Some times if we set out of frame, CellForRowAtIndexPath will not call.

uiImageView showing borders between images in UICollectionViewCell

We have a row of 139x127 images that are displaying with borders in our collection view cell. we tried UIViewContentModeScaleAspectFill (and Fit) but it is still showing the borders.
The code is below:
// uicollectionviewcell
(id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
self = [super initWithFrame:frame];
if (self) {
// Initialization code
NSArray *arrayOfViews = [[NSBundle mainBundle] loadNibNamed:#"ProfilePhotoCell" owner:self options:nil];
if ([arrayOfViews count] < 1) {
return nil;
}
if (![[arrayOfViews objectAtIndex:0] isKindOfClass:[UICollectionViewCell class]]) {
return nil;
}
self = [arrayOfViews objectAtIndex:0];
}
[self.imageView setContentMode:UIViewContentModeScaleAspectFill];
return self;
}
return self;
}
// uicollectionview/viewcontroller
// 1
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
// 2
CGSize retval = CGSizeMake(100, 66);
return retval;
}
// 3
- (UIEdgeInsets)collectionView:
(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section {
return UIEdgeInsetsMake(20, 0, 0, 0);
}
Here is a screenshot: ! http://m.mirror.co/collection.png
What is going on with your collectionViewLayout property of the UICollectionView? I am guessing that the UICollectionView is padding the cells it is displaying.
For debugging purposes i have found it priceless to use LLDB by pausing the app and running...
po [[UIWindow keyWindow] _autolayoutTrace]
This will give you the pointers to each view object. Then i will run...
po [0xabcd123 constraints]
...to see if any auto constraints were added at runtime you didn't know about. Also, you can get the frame size of your cell and your view and see if they match. You may need to programmatically delete auto constraints and apply new constraints pinning your view to your cell. I ran into some of this while making a view cell "flip" from view to view.
Don't forget to turn off the auto constraints in your init or IB...
[self.imageview setTranslatesAutoresizingMaskIntoConstraints:NO];
Good luck!

uitableview cellforrowatindexpath not called but no:of rows in section is called

in my tableview no of rows in section method is called and it returns value 17,but the cellforrowatindexpath is not getting called.i have put breakpoints in the first line of this method but this point is never shown when debugging,i have followed the tableviewdelegate and datasource.and the tableviews datasource,delegate are properly set in the Int builder.
i am also posting some of the code
in my viewcontroller.m
-(void)viewDidLoad
{
tweets=[[NSMutableArray alloc]init];
[self updateStream:nil];
[self.tweetsTable setDelegate:self];
[self.tweetsTable setDataSource:self];
}
-(NSInteger)tableView:(UITableView *)tweetsTable numberOfRowsInSection:(NSInteger)section {
return [tweets count];
}
-(UITableViewCell *)tableView:(UITableView *)tweetsTable cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"twitCell";
TwitCell *cell = (TwitCell *)[tweetsTable dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = (TwitCell *)[[[TwitCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
}
cell.tweet = [tweets objectAtIndex:indexPath.row];
[cell layoutSubviews];
return cell;
}
cellForRowAtIndexPath won't get called if your tableview has height of 0
make sure your tableview always has VISIBLE rows
CGRect frame = self.tableView.frame;
frame.size.height = 100;
self.table.frame = frame;
It will not get called if you are returning 0 rows in numberOfRowsInSection method.
Please check the number of rows that you return.
Given the code you've shown us there only a few possibilities. Either self.tweetsTable is nil, tweets is nil, or tweets contains no element and count is returning zero. Now I know you say that everything is correct, but clearly something is up! You can add a bit of defensive code to detect these problems.
-(void)viewDidLoad
{
tweets=[[NSMutableArray alloc]init];
[self updateStream:nil];
NSAssert(self.tweetsTable, #"self.tweetsTable must not be nil.");
[self.tweetsTable setDelegate:self];
[self.tweetsTable setDataSource:self];
}
-(NSInteger)tableView:(UITableView *)tweetsTable numberOfRowsInSection:(NSInteger)section {
NSAssert(tweets, #"tweets must not be nil here");
NSUInteger n = [tweets count];
if(n == 0)
NSLog(#"WARNING: %# returning 0", __PRETTY_FUNCTION__);
return (NSInteger)n;
}
If you do this and one of the asserts fires you'll know where your problem is. If no assert fires then something is going on outside the scope of the code you have shown (e.g. something is getter released to soon or memory getting clobbered). Oh and one final thing -- can you see the empty table view on the screen? If you table is not visible AFAIK cellForRowAtIndexPath won't be called.
check this
or else use viewDidLoad code in viewWillAppear
Here is my function. After I placed "dispatch_async(dispatch_get_main_queue()..." inside of "tweets = [NSJSONSerialization.....", it works.
tweets = [NSJSONSerialization JSONObjectWithData:responseData options:NSJSONReadingMutableLeaves error:&jsonError];
if (tweets) {
// We have an object that we can parse
NSLog(#"%#", tweets);
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
});
}
else {
// Inspect the contents of jsonError
NSLog(#"%#", jsonError);
}
In my cases I had created a UITableView as a property on my view controller, but I forgot to add it as a subview to self.view
Strangely you will get the symptoms that sujith described: numberOfRowsInSection will be called, but cellForRowAtIndexPath will not!
This was my missing line:
[self.view addSubView:self.myTableViewProperty];
And to defend against it:
NSAssert(self.myTableViewProperty.superview != nil, #"The table view dose not have a superview");
[self.myTableViewProperty reloadData];
You can refer following code to reload your tableView
[self.tableView performSelectorOnMainThread:#selector(reloadData) withObject:nil waitUntilDone:NO];
In my case I was using custom subclass of UITableView and I have not called super super.layoutSubviews()
override func layoutSubviews() {
super.layoutSubviews() // Forgotten to call super implementation
self.layer.borderColor = Constants.primaryColor.cgColor
self.layer.borderWidth = Constants.primaryBorderWidth
self.indicatorStyle = .white
}

Custom UITableViewCell redraw issues

I have a custom UITableView cell that I've added a textbox to for editing, that shows and hides based on the edit mode. I've also tried adding a vertical line that shows when editing, and it does that, but I'm running into some drawing issues. I just added a green checkmark rightView to start working on input validation feedback, and I'm seeing similar issues.
Here is the code for the cell, and part of my cellForRowAtIndexPath.
#import <UIKit/UIKit.h>
#interface EditableCellStyle2 : UITableViewCell {
CGRect editRect;
UITextField *editField;
UIView *lineView;
}
#property (nonatomic, readonly, retain) UITextField *editField;
#property (nonatomic, readonly, retain) UIView *lineView;
#end
#import "EditableCellStyle2.h"
#implementation EditableCellStyle2
#synthesize editField;
#synthesize lineView;
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
// Initialization code.
editRect = CGRectMake(83, 12, self.contentView.bounds.size.width-83, 19);
editField = [[UITextField alloc] initWithFrame:editRect];
editField.font = [UIFont boldSystemFontOfSize:15];
editField.textAlignment = UITextAlignmentLeft;
editField.textColor = [UIColor blackColor];
editField.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleHeight;
[self.contentView addSubview:editField];
self.editField.enabled = NO;
self.editField.hidden = YES;
lineView = [[UIView alloc] initWithFrame:CGRectMake(80, 0, 1, self.contentView.bounds.size.height)];
self.lineView.backgroundColor = [UIColor lightGrayColor];
[self.contentView addSubview:lineView];
self.lineView.hidden = YES;
}
return self;
}
- (void)setSelected:(BOOL)selected animated:(BOOL)animated {
[super setSelected:selected animated:animated];
// Configure the view for the selected state.
}
-(void)layoutSubviews
{
[super layoutSubviews]; // layouts the cell as UITableViewCellStyleValue2 would normally look like
editRect = CGRectMake(83, 12, self.contentView.frame.size.width-self.detailTextLabel.frame.origin.x-10, 19);
editField.frame = editRect;
}
- (void)willTransitionToState:(UITableViewCellStateMask)state {
[super willTransitionToState:state];
if (state & UITableViewCellStateEditingMask) {
self.detailTextLabel.hidden = YES;
self.editField.enabled = YES;
self.lineView.hidden = NO;
self.editField.hidden = NO;
}
}
- (void)didTransitionToState:(UITableViewCellStateMask)state {
[super didTransitionToState:state];
if (!(state & UITableViewCellStateEditingMask)) {
self.editField.enabled = NO;
self.editField.hidden = YES;
self.lineView.hidden = YES;
self.detailTextLabel.hidden = NO;
self.editField.text = self.detailTextLabel.text;
}
}
- (void)dealloc {
[editField release];
[lineView release];
[super dealloc];
}
#end
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// handling every section by hand since this view is essentially static. Sections 0, 1, 2, and 4 use a generic editable cell.
// Section 3 uses the multiline address cell.
static NSString *CellIdentifier = #"Cell";
EditableCellStyle2 *cell = (EditableCellStyle2 *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (indexPath.section == 0 || indexPath.section == 1 || indexPath.section == 2 || indexPath.section == 4) {
if (cell == nil) {
cell = [[[EditableCellStyle2 alloc] initWithStyle:UITableViewCellStyleValue2 reuseIdentifier:CellIdentifier] autorelease];
}
}
// Configure the Odometer
if (indexPath.section == 0) {
NSArray *array = [sectionsArray objectAtIndex:indexPath.section];
NSDictionary *dictionary = [array objectAtIndex:indexPath.row];
cell.textLabel.text = #"Odometer";
cell.detailTextLabel.text = [NSString stringWithFormat:#"%#", [dictionary objectForKey:#"Odometer"]];
cell.tag = kOdometer;
cell.editField.text = cell.detailTextLabel.text;
cell.editField.placeholder = #"Odometer";
cell.editField.tag = kOdometer;
cell.editField.keyboardType = UIKeyboardTypeNumberPad;
// Create a view for the green checkmark for odometer input validation and set it as the right view.
UIImage *checkImage = [UIImage imageNamed:#"tick.png"];
UIImageView *checkImageView = [[[UIImageView alloc] initWithImage:checkImage] autorelease];
cell.editField.rightView = checkImageView;
cell.editField.rightViewMode = UITextFieldViewModeAlways;
}
return cell;
}
There is more to it but all the cells are built the same way.
The problems are that, when in edit mode, the vertical lines will display properly. When I leave edit mode, any cells that were off screen when I go to normal mode still have the vertical line (it doesn't get hidden). Also, now that I've added the imageView for the checkmark indicator, any cells that are off screen when switching modes gain the checkmark. (only section 0 sets it up).
I've also noticed that if i do cell.setNeedsDisplay, the text label and detail text label won't update if the data source has been updated. I have to do [self.tableView reloadData] which skips any active animations.
I'm sure these issues are related to me using a custom cell + dequeueReusableCellWithIdentifier, but I can't find exactly what.
Any feedback or a push in the right direction would be appreciated.
Edit:
Not using reusable cells seems to have resolved the above issues. I'm still open to feedback on the cell code.
I forgot one other issue that may or may not be related. One of my cells has a "tap to view list" button. If I enter data into the cells while in edit mode, then hit that button to choose some info from a list (it displays a modal table view), when I dismiss the modal view, all of the cells' edited data has reverted to their original state. I'm not calling reload data when I dismiss the modal view controller. I thought this might be fixed by not using reusable cells but it isn't.
You need to prepare the cell for reuse. Try adding this to the EditableCellStyle2 implementation:
- (void)prepareForReuse {
[super prepareForReuse];
[self didTransitionToState:UITableViewCellStateDefaultMask];
}
Maybe you trimmed too much for your post, but in the posted code your reusable cell handling is all wrong.
First of all, each different type of cell needs its own CellIdentifier. In your case (judging from your code comment), that means at least a different identifier for section 3 versus sections 0, 1, 2, and 4. You may also want to do a separate identifier for section 0, so you don't have to keep removing and readding that checkmark. The different identifier needs to be used for both the dequeueReusableCellWithIdentifier: and initWithStyle:reuseIdentifier:` for the appropriate sections.
The second problem is that you are not resetting the cells correctly. There are two "kinds" of initialization that must be done to a UITableViewCell: initialization that is the same for every cell of its type, and initialization that depends on the specific row being displayed. The first kind can (and should) only be done once, when a new cell is allocated. The second kind must be done every time through tableView:cellForRowAtIndexPath:. You seem to be doing the first correctly for your EditableTableCell2 class in its init method, but I see nowhere in there where you do the per-row initialization: you never reset selected, or the cell state, or the contents of the edit field, or remove the checkImageView since you are using the same kind of cell for section 0 versus the other sections. If you want, the reset selected, state, and clearing out the checkbox image and field contents can be done in prepareForReuse on your EditableTableCell2 class.
The third problem, which is almost certainly due to over-trimming, is that you never create this "multiline address" cell for section 3. You'll end up maybe reusing a random EditableTableCell2, or maybe crashing on an exception from the framework when you return nil from tableView:cellForRowAtIndexPath:.