I have an UIViewController with an UITableView inside him, I draw the items inside my Table using the Interface (Drag and drop Labels, and UITextViews) my table have 25 rows.
Now it's time to link my labels with IBOutlets for this I create a Subclass of TableViewCell Like this:
TableViewCell.h
#property (nonatomic, retain) IBOutlet UILabel *label1;
TableViewCell.m
#synthesize label1 = _label1;
And I use this code for change my first label:
- (UITableViewCell *)tableView:(UITableView *)tableView2 cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *CellIdentifier = [menuItems objectAtIndex:indexPath.row];
HobbiesTableViewCell *cell = [tableView2 dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
if (cell == nil) {
cell = [[HobbiesTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
cell.label1.text = #"GG";
return cell;
}
But I saw a little problem, When I open my View (in simulator) is too slow to open (I think my table is loading), for try to solve this problem I delete the nonatomic of my property and my View open faster.
But I have one question, When I create Properties I always put the command nonatomic, and in this project I had to take it off because the slowness, have a problem to take off the nonatomic in this property?(Since the labels will never be changed!)
You should make label1 weak, as it is is in Storyboard. You also don't need to #synthesize anymore.
Also, if you are dequeuing with different identifiers, you are really not dequeueing. Prototype cells should have one identifier in storyboard.
NSString *CellIdentifier = #"MyCellID"
HobbiesTableViewCell *cell = [tableView2 dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
Nonatomic should increase performance, and is not causing your problem.
From what you have listed, I would rather blame not the table view, but the way you fill menuItems. Does its contents come from the internet or a file? If yes, I guess the problem is that you perform access to a remote data source on the main thread.
UPD:
After a short talk with the PO it became clear the the issue was because of having 25 cells with different Reuse ID's.
Related
I have a UITableView which has another UITableView nested inside one its cells (I know this is bad practise, don't worry!).
The problem is that when I call dequeueReusableCellWithIdentifier: I am getting nil back. HOWEVER this works just fine when the UITableView is not nested inside another one.
Is there a way to NOT reuse a UITableViewCell, but instead directly instatiate it every time?
I've tried using this:
ContactFieldCell *cell = [[ContactFieldCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:thisCellIdentifier];
which doesn't return nil, but then nothing appears in my UITableView!
Here's the code for the "parent" UITableView:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"ContactCardCell";
ContactCardCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
NSArray *objects = [[sections objectAtIndex:indexPath.section] objectForKey:#"objects"];
CDCard *card = [objects objectAtIndex:indexPath.row];
cell.delegate = self;
cell.fieldsTableView = [[CardTableViewController alloc] initWithCard:card];
[cell.fieldsTableView.view setFrame:CGRectMake(17, 12, 256, 163)];
[cell.contentView addSubview:cell.fieldsTableView.view];
return cell;
}
and here's the code for the "child" UITableView:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *thisCellIdentifier = #"ContactFieldCell";
ContactFieldCell *cell = [self.tableView dequeueReusableCellWithIdentifier:thisCellIdentifier];
cell.delegate = self;
cell.field = [self.card.sortedFields objectAtIndex:indexPath.row];
return cell;
}
ContactFieldCell is a prototype cell within the storyboard. It has the following code:
#interface ContactFieldCell : UITableViewCell
#property (nonatomic, weak) id<ContactFieldCellDelegate> delegate;
#property (nonatomic, strong) CDField *field;
#property (nonatomic, strong) IBOutlet UILabel *displayNameLabel;
#end
dequeueReusableCellWithIdentifier: does not create a cell if none was found for dequeueing.
Create a cell manually, or use dequeueReusableCellWithIdentifier:forIndexPath:
Yes - #vikingosegundo is correct, but to expand his answer, you need to also register your cell first. dequeueReusableCellWithIdentifier: may return nil. And if it is you need to create your cell,s but dequeueReusableCellWithIdentifier: forIndexPath: will always return a valid cell, the catch is you need to tell it what kind of cell, that is what registerClass does.
Do this for both UITableViews.
- (void)viewDidLoad {
[super viewDidLoad];
[self.tableView registerClass:[ContactFieldCell class] forCellReuseIdentifier:#"ContactFieldCell"];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *thisCellIdentifier = #"ContactFieldCell";
ContactFieldCell *cell = [self.tableView dequeueReusableCellWithIdentifier:thisCellIdentifier forIndexPath:indexPath];
cell.delegate = self;
cell.field = [self.card.sortedFields objectAtIndex:indexPath.row];
return cell;
}
UITableViews are a very powerful element and can be used to build great apps.
The only thing to keep in mind is, the basics must be clear. Now from your code, I cannot make out whether you have assigned the delegates and dataSources properly, but I'll still mention it in case someone else needs it.
You have a subclassed UITableViewCell which in turn contains a UITableView. The UIViewController must be the delegate and dataSource for the outer UITableView. Make sure you have set it in both the .h and .m file.
Next, your custom cell must also be the delegate and dataSource, but for the inner UITablewView. I suppose here, you have created the inner UITableView in the init method of the UITableViewCell. Set the delegate and dataSource there itself. Then you set other runtime properties in the drawRect method (if needed) and call it's reloadData.
The UIViewController must override the delegate and dataSource methods for the outer table and the cell must override the methods for the inner table.
Also, make sure, the time the cells are plotted, your data is not nil or null.
And a very important fact, that people miss is the following code:
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
}
Just dequeueing the cell is not enough. The first time a cell is dequeued, it is nil because it has not been created yet. Hence the if condition. Once it is allocated and initialized and added to the table, the dequeue code works thereafter.
NOTE : After looking more closely to your code (sorry for not looking the first time), I noticed you have allocated a UITableViewController to your cell. How do you think the cell is going to display a controller? Use a UITableView instead. Try to follow the pattern I have mentioned in paragraph 3. Use a table in the custom cell as a private member (or property, your wish), allocate it in init. Assign the data to the cell from your view controller. Then use this data to set the inner table view cell's properties in it's drawRect. It should work fine.
This topic has been covered over and over, but after two days of researching and trying all of the solutions suggested, I still can't do what I want.
First of all, I'm creating an app for iOS 5, using storyboard.
I have a UITableViewController, with 2 types of cell (an original "message", and a number of "answers" to it). I created my table in my storyboard, checked "prototypes cells", designed 2 cells with my 2 or 3 labels, a textView, and an image. Then, I subclassed UITableViewCell with 2 new classes, which I called ThreadAlertCell and ThreadAnswerCell. I created properties for my cell's elements, so I can set the text of the labels and the image programmatically. I linked my graphic elements to their definition in the storyboard as usual. In my TableViewController, in cellForRowAtIndexPath, I create the cells and populate them. So far so good, everything is displayed correctly and how I want it.
But, I want a "touch" on the image of a cell, to pop a new view, showing the user's profile page (an other basic view, I can do it with performSegue, no problem for that).
I have tried so many things I'm not sure it's very useful to put everything in detail here. While looking for answers, I understood that using a UIImageView when you expect to handle gestures is not really the best way. So I changed it to a UIButton. But I can't get the touch event to do anything !
I'll give only the example of an "answer" cell.
Here is my ThreadAnswerCell header file (I won't give the .m, nothing interesting there) :
#interface ThreadAnswerCell : UITableViewCell
#property (nonatomic) IBOutlet UILabel *senderLabel;
#property (nonatomic) IBOutlet UILabel *dateLabel;
#property (nonatomic) IBOutlet UITextView *contentTextView;
#property (nonatomic, weak) IBOutlet UIButton *senderButton;
#end
And here is half the cellForRowAtIndexPath from my TableViewController (I do the same for the "message" before that) :
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellIdentifier = #"ThreadAnswerCell";
ThreadAnswerCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (cell == nil) {
cell = [[ThreadAnswerCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
}
Message *message = [threadArray objectAtIndex:indexPath.row];
cell.senderLabel.text = [NSString stringWithFormat:#"%#", message.sender];
cell.contentTextView.text = [NSString stringWithFormat:#"%#", message.content];
cell.dateLabel.text = [NSString stringWithFormat:#"%#", message.date];
//[cell.senderButton setBackgroundImage: [[UIImage alloc] initWithData: [NSData dataWithBase64EncodedString: message.userPic]]
// forState: UIControlStateNormal];
[cell.senderButton addTarget:self action:#selector(firstButtonSelected:) forControlEvents:UIControlEventTouchUpInside];
CGRect frame = cell.contentTextView.frame;
frame.size.height = cell.contentTextView.contentSize.height;
cell.contentTextView.frame = frame;
[cell.contentTextView sizeToFit];
return cell;
}
As you can see, I use my custom cell, then populate it with content (I use three useless stringWithFormat but I have my reasons, lol), and I try to add an event to my button. I also commented the part where I set the button's background image to "see" the button on my screen.
And here is the method I want the buttons to call :
- (void)firstButtonSelected: (id)sender
{
NSLog(#"hello");
}
But, the method is never called ! Any ideas on where I've gone wrong or any other working solution would be great ! Thanks.
Do you have
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
Implemented in your tableview delegate class?
I think, your cell catches the touch events. Try to delete the method. And
cell.selectionStyle = UITableViewCellSelectionStyleNone
Is it the topmost view? Make sure you add the senderButton view last to the cell view.
It's possible any other view gets touched.
I'd like to create custom UITableViewCell using an XIB, but I'm not sure how to recycle it using UITableViewController's queueing mechanism. How can I accomplish this?
Folks, this question was intended to be self answered as per the FAQ, although I love the awesome responses. Have some upvotes, treat yourself to a beer. I asked this because a friend asked me and I wanted to put it up on StackOverflow. If you have anything to contribute, by all means!
If you're using iOS 5 you can use
[self.tableView registerNib:[UINib nibWithNibName:#"nibname"
bundle:nil]
forCellReuseIdentifier:#"cellIdentifier"];
Then whenever you call:
cell = [tableView dequeueReusableCellWithIdentifier:#"cellIdentifier"];
the tableview will either load the nib and give you a cell, or dequeue a cell for you!
The nib need only be a nib with a single tableviewcell defined inside of it!
Create an empty nib and add the table cell as the first item. In the inspector, you can add the reuseIdentifier string in Interface Builder.
To use the cell in your code, do this:
- (UITableViewCell *)tableView:(UITableView *)_tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *reuseIdentifier = #"blah"; //should match what you've set in Interface Builder
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:reuseIdentifier];
if (cell == nil)
{
cell = [[[NSBundle mainBundle] loadNibNamed:#"YourTableCellNib" owner:nil options:nil] objectAtIndex:0];
}
//set up cell
return cell;
}
There is another method where you create an outlet for your cell and load the cell nib using the controller as the file's owner, but honestly this is much easier.
If you want to be able to access the subviews you've added to the cell in the nib, give them unique tags and access them with [cell viewWithTag:x];
If you want to be able to set custom properties on the cell, you'll need to create a custom UITableViewCell subclass, then just set that as the class of your nib in InterfaceBuilder and cast the UITableViewCell to your custom subclass when you dequeue it in the code above.
To set up a custom UITableViewCell using a XIB, you have to do several things:
Set up an IBOutlet in your header
Configure the table view cell in Interface Builder
Load the XIB inside of tableView:cellForRowAtIndexPath:
Configure it like any other cell
So... Let's set up an IBOutlet in the header file.
#property (nonatomic, retain) IBOutlet UITableViewCell *dvarTorahCell;
Don't forget to synthesize it inside the implementation file.
#synthesize dvarTorahCell;
Now, let's create and configure the cell. You want to pay attention to the Cell Identifier and the IBOutlet as shown below:
Now in code, you load up the XIB into your cell as shown here:
Notice that the Cell Identifier in Interface Builder matches the one shown in the code below.
Then you go ahead and configure your cell like any other.
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (cell == nil) {
[[NSBundle mainBundle] loadNibNamed:#"YUOnlineCell" owner:self options:nil];
cell = dvarTorahCell;
dvarTorahCell = nil;
}
//configure your cell here.
Just note that when accessing subviews, such as labels, you now need to refer to them by tag, instead of by property names, such as textLabel and detailTextLabel.
Here how you can do:
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
YourCustomeCell *cell = (YourCustomeCell *)[tableView dequeueReusableCellWithIdentifier:CellClassName];
if (!cell)
{
NSArray *topLevelItems = [cellLoader instantiateWithOwner:self options:nil];
cell = [topLevelItems objectAtIndex:0];
}
return cell;
}
Where cellLoader in .h is defined as follow:
UINib *cellLoader;
and in .m is istantiated as follows (for example during initialization):
cellLoader = [[UINib nibWithNibName:CellClassName bundle:[NSBundle mainBundle]] retain];
and CellClassName is the defined in .m as follows (is also the name for your xib).
static NSString *CellClassName = #"YourCustomeCell";
Do not forget to use the string CellClassName also in your xib created cell.
For further info I suggest you to read this fantastic tutorial creating-a-custom-uitableviewcell-in-ios-4.
Hope it helps.
P.S. I suggest you to use UINib because is an optimized method to load xib files.
I've been having a problem of adding a checkbox image to my uitableviewcell and toggle between checked and unchecked image.
My simple code is:
-(UITableViewCell *)tableView:(UITableView *)todoTable cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *SimpleTableIdentifier = #"SimpleTableIdentifier";
UITableViewCell *cell = [self.todoTable dequeueReusableCellWithIdentifier: SimpleTableIdentifier];
if (!cell) {
cell = [[[UITableViewCell alloc]
initWithStyle:UITableViewCellStyleDefault reuseIdentifier:SimpleTableIdentifier] autorelease];
}
cell.textLabel.text = [self.todoList objectAtIndex:indexPath.row];
return cell;
}
and i try to add in the solution in from here
But i don't know where shall i put the
interface ToggleImageControl : UIControl
I assume i put it in the same header file as my viewcontroller?
also i try to put in
ToggleImageControl *toggleControl = [[ToggleImageControl alloc] initWithFrame: <frame>];
toggleControl.tag = indexPath.row; // for reference in notifications.
[cell.contentView addSubview: toggleControl];
into my code above, i will get a ToggleImageControl reference error because of the implementation of the interface is not found, how can i fix this?
Thanks.
The easiest way to accomplish such modifications in look and behaviour is to write your own subclass of UITableViewCell. UIButton actually has toggling feature built in via selected property.
KakoSquid is correct. It looks like you might need to spend some time getting to know how classes and subclasses are built.
In your case it sounds like you have made a new interface section for your ToggleImageControl in the .h file but you need to put an #implementation section in your .m file.
In many situations you will make a new file set for a new class. (.h & .m files). In this case a toggle image is something you could reuse across other projects so it would make sense to break it out into its own file.
But, you can add the interface and implementation into any existing flies as you see fit. Just make sure to #import the correct files where you are using them.
I'm reading a custom table cell in tableView:cellForRowAtIndexPath: from a nib file. This works great for my purposes, except it's quite slow.
Now, I know the right thing to do in the long term is to create the cell entirely in code, and to use a single view, and so on. But this is a prototype, and I don't want to put that much effort into it.
For now, I'd be happy if I was reading the nib only once in the UIViewController subclass, then tableView:cellForRowAtIndexPath: made copies of it. My assumption here is that copying would be faster than reading the nib.
Here's what I use to load the nib, which I call from viewDidLoad: (and retain after)
-(id)loadFromNamed:(NSString*)name {
NSArray *objectsInNib = [[NSBundle mainBundle] loadNibNamed:name
owner:self
options:nil];
assert( objectsInNib.count == 1 );
return [objectsInNib objectAtIndex:0];
}
All is good so far. But the question is: How do I copy this over and over? Is it even possible?
I tried [_cachedObject copy] and [_cachedObject mutableCopy] but UITableViewCell doesn't support either copy protocol.
If I have to, I can just tell them to ignore the speed until I'm prepared to remove the nib entirely, but I'd rather get it going a little faster if there's a low-hanging fruit here.
Any ideas?
I think coping of table cell can be used together with dequeuing mechanism, which will allow to create cell one time (from nib or programmatically or getting it loaded automatically from other nib and linking as an outlet in IB) and then clone it or dequeue it when needed.
UITableViewCell doesn't conform to NSCopying protocol, but it supports keyed archiving/unarchiving mechanism, so it can be used for cloning.
Based on answer "
How to duplicate a UIButton in Objective C? " my data source delegate method looks like:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellID = #"CellIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellID];
if (!cell) {
NSData *archivedData = [NSKeyedArchiver archivedDataWithRootObject:self.tableViewCell];
cell = [NSKeyedUnarchiver unarchiveObjectWithData:archivedData];
}
// ... config ...
return cell;
}
And in my case self.tableViewCell is a cell that was loaded one time from view's nib file.
I don't tested what will be faster: "archive + unarchive" to clone or "load nib file + unarchive" which framework will do in case of -loadNibNamed:owner:options:, I used this method only with convenience considerations, but good chances that memory operation vs file operation will be faster.
EDIT: It appears not as easy as it seemed at first. As UIImage doesn't conforms to NSCoding, cells with configured UIImageViews can't be just copied without additional code. Yep, copying whole image is definitely not a good practice, cheers to Apple for pointing this.
Use the cell cloning built into the table view. Apple knew generating a lot of table cells was slow. Check out the docs for this method:
- (UITableViewCell *)dequeueReusableCellWithIdentifier:(NSString *)identifier
You create the cell once, then as new cells are requested, use that method to clone the existing cells. Then you just change what needs to be changed about the new cell and return the cell object.
Also check out the table view realted sample code provided by Apple that uses this method and show you the right way. The fact your cell was loaded from a nib shouldn't matter at all.
Minor clarification: I dont think the above method clone cells for you. Instead it takes cell object that have scrolled off the screen and simply moves them to a new spot. So it's literally reusing a cell. So be sure your custom table view can be set to all the new values it needs outside of the intialization.
Not proud of this solution, but it works with the maximum number of possible IB bindings:
Interface (AlbumTableViewCell is a subclass of UITableViewCell of which an instance is defined in AlbumViewController's XIB file):
#interface AlbumsViewController : UITableViewController {
IBOutlet AlbumTableViewCell *tableViewCellTrack;
}
#property (nonatomic, retain) AlbumTableViewCell *tableViewCellTrack;
Implementation (unarchive / archive makes a copy / clones the table view cell):
#implementation AlbumsViewController
#synthesize tableViewCellTrack;
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
AlbumTableViewCell *cell = (AlbumTableViewCell *)[tableView dequeueReusableCellWithIdentifier: #"AlbumCell"];
if (cell == nil) {
AlbumsViewController *albumsViewController = [[[AlbumsViewController alloc] init] autorelease];
[[NSBundle mainBundle] loadNibNamed: #"AlbumsViewController" owner: albumsViewController options: nil];
cell = albumsViewController.tableViewCellTrack;
}
cell.labelTitle.text = ...;
cell.labelArtist.text = ...;
return cell;
}
Well, I'm not sure why all the tutorials out there doesn't specify this step.
When using your own custom UITableViewCell from Nib, calling dequeueReusableCellWithIdentifier is not enough. You have to specify the "Identifier" in the IB, just for for it in the Table View Cell tab section.
Then make sure the identifier you put in IB is the same as the identifier you use for the dequeueReusableCellWithIdentifier.
Here it is in Swift
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell : UITableViewCell?
let cellId = String(format: "Cell%d", indexPath.row)
cell = alertTable!.dequeueReusableCellWithIdentifier(cellId) as! UITableViewCell?
if cell == nil {
let archivedData = NSKeyedArchiver.archivedDataWithRootObject(masterTableCell!)
cell = NSKeyedUnarchiver.unarchiveObjectWithData(archivedData) as! UITableViewCell?
}
// do some stuff
return cell!
}