I am trying to create a project with a custom UITableViewCell. The custom cells never load, they're just blank. At this point in the project what I'm trying to do is placing a UITableViewCell in a .xib, designing it the way I want and specifying its reuse identifier along with tag IDs for the components so that I can use them in code later on.
I've googled a ton and found several tutorials that look like what I want to do, along with many SO questions that have answers that seem applicable. At this point it's probably just my head spinning with too many different angles and solutions.
This is my current attempt at trying to register the custom cell with my UITableView, yet when running this on a device the rows in the table view are entirely blank.
UITableViewCell* cell = [self.tableView dequeueReusableCellWithIdentifier:#"MyCell"];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"MyCell"];
}
UILabel* l1 = (UILabel*)[cell viewWithTag:1];
UILabel* l2 = (UILabel*)[cell viewWithTag:2];
UILabel* l3 = (UILabel*)[cell viewWithTag:3];
l1.text = #"Foobar";
l2.text = #"Foobar";
l3.text = #"Foobar";
I'm pretty certain that I've hooked up all the properties and such correctly, but at this stage I need a fresh pair of eyes to point out the facepalm for me.
The interesting files are FilmerView.m/h/xib and the cell is in FilmerViewCell.xib. When running the app this TableView is in the second tab of the tab bar controller.
Project:
http://speedy.sh/WhhpP/test12.zip
I can't provide a full answer atm but look up the tableview method. registerNib:forCellReuseIdentifier:
Also, stop using that dequeue method. Use the one that includes the indexPath.
Then you don't have to check if the cell is nil afterwards.
EDIT
In viewDidLoad (or somewhere like that)...
UINib *cellNib = [UINib nibWithNibName:#"MyCustomCellXibFileName" bundle:[NSBundle mainBundle]];
[self.tableView registerNib:cellNib forCellReuseIdentifier:#"CellIdentifier"];
Now in the table view datasource method...
- (UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"CellIdentifier" forIndexPath:indexPath];
// no need to check cell == nil.
// this method is guaranteed to return a non nil cell.
// if it doesn't then the program will crash telling you that you need to...
// register a class or nib (but we just did this in viewDidLoad) :D
// configure your cell here...
[self configureMyCell:(MyCustomCell *)cell atIndexPath:indexPath];
return cell;
}
- (void)configureMyCell:(MyCustomCell *)cell atIndexPath:(NSIndexPath *)indexPath
{
cell.nameLabel.text = #"Hello, world";
}
Hope this helps.
Make sure that you have set datasource and delegate properties of your tableView.
Make sure that you have implemented (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section method and it returns a positive value (>0).
Evaluate the following:
Is the ReuseIdentifier set in the XIB. See Properties Tab in Interface Builder on the right when selecting the cell.
Are the AutoresizingMasks set properly for the labels to be visible?
WrapModes: Which did you select? When having wrapmode WrapWord, is the font size too large for the text to be moved in the next line becoming invisible?
Set the background color of the UITableViewCellss content view to something else than white or transparent, as well as the background colors of the labels to see if the cell is even there.
Manually call numberOfRowsInSection on your table, pass the proper NSIndexPath identifying the target section and see if its greater 0 to confirm that the TableView even attempts to load the data, thus, the cells. ( Alternatively set a breakpoint in the method or do a NSLog. )
Do a NSLog in cellForRowAtIndexPath to confirm that the cell returned is not nil and the method is even called!
Related
I want to refactor some Code for better Performance, but my Problem is i´m not sure how to do it. At the Moment i have one UIViewController with a UIScrollView on it.
I also have 20 different Views which (each has its on .h and .m File) can be laid fully dynamically on my UIScrollView. Every Time i start the UIViewController i send a request to my Server and then i get the Response and then i know how many Views i have to put on the UIScrollView.
So you can imagine when theres a lot of different views on my UIScrollView it takes a few seconds because alle Views are getting fully loaded, before the User can finally interact with them.
So my idea is to replace the UIScrollView with a UITableView and change all the CustomViews (UIViews) to Custom UITableViewCells. So only the visible Cells would be loaded at the first start!
Know i have several Problems.
At the Moment, most of the Code from my CustomViews are build with Frames, but i want to change it completely to Autolayout, i don´t think it makes sense to build them all with the IB (xib files ...). So i have to do the whole Autolayout Stuff in Code?
Some of the Custom Views are really big, so they getting really high, and some can be really small. My concern is that the scrolling Perfomance will be really bad... because i cannot really use estimatedRowHeight(A Example: sometimes one Cell can get a height of 1000.0f and the next cell only 40.0f).And in Combination with Autolayout and the Time i have to wait until the Response frome my Server arrives, i think it could be really annoying for the User.
There can be up to 20 different!! custom rows, makes it really sense to use a UITableView in this case? As i mentioned before, they are all very different in their Size and Content!
Here is a little Part of my new Code:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [self.node.blocks count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
GTBlockView *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
GFBlock *block = [self.node.blocks objectAtIndex:indexPath.row];
//createInterfaceforBlock -- Here the Cell gets called and the Content and the Size gets defined
cell = [[GTAppController sharedInstance] createInterfaceForBlock:block];
// Make sure the constraints have been added to this cell, since it may have just been created from scratch
[cell setNeedsUpdateConstraints];
[cell updateConstraintsIfNeeded];
return cell;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
//GTBlockView is the SuperView of all my Custom Cells
GTBlockView *cell = [self.offscreenCells objectForKey:CellIdentifier];
if (!cell)
{
cell = [[GTBlockView alloc] init];
[self.offscreenCells setObject:cell forKey:CellIdentifier];
}
GFBlock *block = [self.node.blocks objectAtIndex:indexPath.row];
//createInterfaceforBlock -- Here the Cell gets called and the Content and the Size gets defined
cell = [[GTAppController sharedInstance] createInterfaceForBlock:block];
// Make sure the constraints have been added to this cell, since it may have just been created from scratch
[cell setNeedsUpdateConstraints];
[cell updateConstraintsIfNeeded];
cell.bounds = CGRectMake(0.0f, 0.0f, CGRectGetWidth(tableView.bounds), CGRectGetHeight(cell.bounds));
[cell setNeedsLayout];
[cell layoutIfNeeded];
// Get the actual height required for the cell
CGFloat height = [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
height += 1;
return height;
}
Maybe some of you have some better Ideas or some good Sources?
I have done exactly as you are thinking in an app of my own - I first went down the UIScrollview route, then I changed to a UITableview with many kinds of custom cells. It does make total sense and is very worth it - I gained a massive performance increase from the uitableview. One of the major problems with the UIScrollview is that it will work out the autolayout for all the content in your contentView, which as you say, can take several seconds, but the UITableview will handle this much more quickly and efficiently. So - go for it.
I would strongly suggest you use a unique XIB file for each custom cell. Do the autolayout in each one individually, and that way you are much more likely to avoid problems down the line. Programmatic constraints are far harder to maintain, and autolayout is often challenging.
To get it all working, I did the following : first I had a subclass of UITableviewCell that was the parent class for all the other cell objects. In my case this was called SNFormTableCell (UITableviewCell). Then all other cell objects were based on this class. In my cellForRowAtIndexPath method, I do this :
ReportItem *objectForCell = [self.reportSet reportItemForSection:indexPath.section andRow:indexPath.row]; //my personal data class - just contains the cell data I need
NSString *identifier = [self getNibNameAndReusableIdentifierNameForObjectType:objectForCell.cellType.integerValue];
SNFormTableCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier];
if (!cell)
{
[self registerNib:[UINib nibWithNibName:identifier bundle:nil] forCellReuseIdentifier:identifier];
cell = [tableView dequeueReusableCellWithIdentifier:identifier];
cell.contentView.clipsToBounds = YES;
}
[cell setSnFormTableCellDelegate:self]; //i have a delegate that calls back to the tableview when cells are interacted with
cell.reportItem = objectForCell; //put the data object onto the cell to do with as the cell requires
[cell refreshUI]; //update the UI using the data - this is over-ridden by the various subclasses of the cell
The method in there called getNibNameAndReusableIdentifierNameForObjectType .. looks like this (just gets the identifier we need for nib and re-use):
- (NSString*) getNibNameAndReusableIdentifierNameForObjectType:(SNFormTableObjectType)objectType {
if (objectType == SNFormTableObjectTypeBoolean) return #"SNBooleanCell";
if (objectType == SNFormTableObjectTypeDatePicker) return #"SNDatePickerCell";
if (objectType == SNFormTableObjectTypeDropDown) return #"SNDropDownCell";
if (objectType == SNFormTableObjectTypeDropDownPlusSingleLineText) return #"SNDropDownPlusSingleLineTextCell";
... etc
}
Then finally, the parent cell class has a method called -(void) refreshUI. So I put the data object onto that cell - this contains all the data I might need for the cell. The the subclasses over-ride this refreshUI method in their own specific way, to use the data as they need to.
Just to re-iterate, I gained enormously from going down this route. A scrollview with a lot of content, taking 5 or more seconds to load the nibs and calculate the autolayout (on the main thread too, making the app unresponsive), would appear instantly on the UITableview version. So go for it. If you need any more details on how to go about it, let me know.
I am trying to implement rating stars by using kDRATING VIEW .i have used following code in cellForRowAtIndexpath method but it causes my app to become slower.
If it try to allocate and initialise this in viewdidload method then it when i scroll up and down ,the stars fluctuates . please help in this regard
self.rating = [[KDRatingView alloc] initWithFrame:CGRectMake(0, 0, 60,20)];
[self.rating rateKDRatingView:2.80 outOf:3.0];
[cell.rating addSubview:self.rating ];
return cell;
It sounds like you need to look into UITableViewCell reuse because when you scroll a UITableViewCell out of the screen it will call cellForRowAtIndexPath again to remake this cell when it is back in view and that can cause flickering and memory consumption.
You are adding the KDRatingView to the rating view on the cell so I guess you have a custom cell, so why not instead have the KDRatingView inside the custom cell and just update its value when you need to.
Try this solution with some cell reuse:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"RatingCell";
RatingCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if(cell == nil){
// initialisation code
cell = [RatingCell new];
}
// setting code
[cell setRatingViewValue:2.8 outOf:3.0];
}
That way it only creates the cell once, so it won't slow down your app. Then each time the cell would have been recreated it just updates the cells rating (and anything else you need to set) instead.
Then you just need to implement the setRatingViewValue:outOf: method in your custom cell to update the KDRatingView that you need to have added and positioned in your custom cell upon initialisation.
Previously I had this set up with a storyboard, having dragged the UILabels, positioned them and sized them whatnot on the UITableViewCell I dragged them onto, and then do a different version of that for the other UITableViewCell.
For example, like follows (but in the picture they've yet to be customized with the labels):
Then in the datasource, I'd simply check the Identifier, and depending on what the Identifier was, customize the cell accordingly.
However, I've needed more customization than I can get from the storyboard, as each cell is going to have two UIViews (a top one and a bottom one to allow sliding of the top one) so I can't really do this with storyboarding, as I add the labels and everything to the UIView programmatically.
But my question is: When I do it programmatically, how can I tell which cell is which so I can customize the layout of the UILabels accordingly? With a storyboard I can obviously just drag a UILabel onto each one, but when doing it programmatically and setting up the UIView, I don't know how to say, "Hey, if the identifier is this, add the UILabels like so" because the UIViews aren't aware of any Identifiers.
Basically the structure looks like this:
UITableView -> UITableViewCell -> CellFront(UIView) & CellBack(UIView)
And the look of the cell comes from the labels added to the CellFront UIView. But there's two looks to the cells and I don't know how to do it without a storyboard.
Although UIViews are not aware of identifiers, they have a property called tag which can be used for any purpose that you would like. You can set the tag to, say, 1 on cells of one kind, and to 2 on cells of the other kind, and then use the tag to distinguish the cells in code. Moreover, once your views are tagged, you can call viewWithTag: on the containing view, and get back the view with the tag that you want.
If you are creating the cells solely in code, then you register your UITableViewCell subclass in the viewDidLoad method of your table view controller. That method sets the identifier. Then, you use that identifier in cellForRowAtIndexPath: just like you would for a xib or storyboard created cell.
[self.tableView registerClass:[MyCellSubclass class] forCellReuseIdentifier:#"MyIdentifier"];
Here is one approach:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// Adjust the condition to match your needs
if (indexPath.row == 0) {
static NSString *Identifier1 = #"CellType1";
// cell type 1
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:Identifier1];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:Identifier1];
// add subviews here
}
// set cell properties
return cell;
} else {
static NSString *Identifier1 = #"CellType2";
// cell type 2
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:Identifier2];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:Identifier2];
// add subviews here
}
// set cell properties
return cell;
}
}
When I push a UIViewController onto my UINavigation controller like:
[(UINavigationController *)self.parentViewController pushViewController:[[[Fonts alloc] initWithNibName:#"Fonts" bundle:nil] autorelease] animated:YES];
Where Fonts.xib is a UIView with only UITableView controlled by a Fonts object that is a subclass of UIViewController and acts as the UITableView's dataSource and delegate.
In the Fonts object I create a UITableViewCell like:
- (UITableViewCell *) tableView: (UITableView *) tableView cellForRowAtIndexPath: (NSIndexPath *) indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier: #"BlahTableViewCell"];
if (!cell) {
cell = [[UITableViewCell alloc]
initWithStyle: UITableViewCellStyleDefault
reuseIdentifier: #"BlahTableViewCell"];
[cell autorelease]; // Delete for ARC
}
return cell;
}
And then I change the font of the cell here:
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
[cell.textLabel setFont:[(UIFont *)[self.listOfFonts objectAtIndex:indexPath.row] fontWithSize:cell.textLabel.font.pointSize]];
cell.textLabel.text = [(UIFont *)[self.listOfFonts objectAtIndex:indexPath.row] fontName];
}
listOfFonts is an NSArray of UIFont objects.
When the view appears it looks like UITableView without changed fonts
If I call reloadData on the UITableView or if I drag the UITableViewCells off screen with my finger and let them bounce back they are redrawn and the view the cells display with the labels having their fonts changed.
It seems like the issue is the UITableViewCells are being drawn too early. If I delay the drawing of them everything looks correct but I want the UITableView to be displaying correctly when the UINavigationController slides my view into place.
Any idea what I am doing wrong?
EDIT: I uploaded a simple and straightforward example of my issue to Dropbox. http://dl.dropbox.com/u/5535847/UITableViewIssue.zip
SOLVED IT!
Ok so I was having exactly the same issues as the original poster and this was the problem.
The line that's causing issues is:
[cell.textLabel setFont:[(UIFont *)[self.listOfFonts objectAtIndex:indexPath.row] fontWithSize:cell.textLabel.font.pointSize]];
Specifically, your issue is because you're trying to feed the cell's textLabel its own pointSize, but pointSize doesn't exist yet so strange bugs occur instead. For me, I noticed that a "transform" was failing due to a singular matrix being non-invertible. As soon as I hardcoded a standard value as my pointSize I saw all my labels draw with the proper font instantly. Note: this makes sense as to why a redraw worked, because then your textLabel does indeed have a pointSize.
In any case, you need to explicitly set your pointSize here, no using what the textLabel "already has" because it doesn't have anything until you're "reloading" a cell.
Set the label font inside -tableView:cellForRowAtIndexPath:.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *identifier = #"identifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier] autorelease];
// do it here if your font doesn't change ....
}
// otherwise here with your font ...
cell.textLabel.font = [UIFont boldSystemFontOfSize:12];
return cell;
}
I'm not sure that table cells are designed to be customisable in this way. The table cell may assume that you won't customise the font, and so not draw itself in a way that's compatible with what you are trying to do.
You'd be better off creating a custom table cell, or appending a UILabel as a subview to the table cell when you create it, and them setting the font of that label instead.
It may seem like overkill for such a small customisation, but it's flexible and it's guaranteed to work.
I have a UITableViewController with UITextfield inside the tableview cells. If I scroll the table view, the user entered data in the textfields disappears. I tried to add the textfield data to a NSMutableArray but it still didn't work. Any help please.
When cellForRowAtIndexPath: is called, the cell you return has to be completely filled in with whatever data you want to show. So, if the cell includes a UITextfield, you'll need to set it's text property to the right value for that row in your data.
When a table cell disappears off the top or bottom of the screen, the UITableViewCell itself becomes available for re-use. (As you scroll, cells disappear, and new cells appear, but the UITableView class is re-using the UITableViewCell objects.) In cellForRowAtIndexPath: when you get a cached cell to use, you have to be sure to setup everything you want it to show for the row in question, otherwise you might see some odd behavior in your table.
Does this help?
EDIT:
Here's an example of the typical pattern used in cellForRowAtIndexPath:. Notice the use of dequeueReusableCellWithIdentifier:. That method returns a previously allocated but not in use UITableViewCell, if there is one. Notice further that if no cached cell is returned, the code creates a new one, and sets it up (with stuff that is independent of anything that might be row specific). Following that, you'd setup the cell as you need it for the row in question.
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *SearchResultsCellIdentifier = #"SearchResultsCellIdentifer";
UITableViewCell *cell = [tableView
dequeueReusableCellWithIdentifier:SearchResultsCellIdentifier];
if (cell == nil)
{
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle
reuseIdentifier:SearchResultsCellIdentifier] autorelease];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
}
// Your row-specific setup of the cell here
// ...
return cell;
}
Check the docs for specifics about these methods. There are LOTS of examples from Apple and elsewhere about how to implement tableViews.