Unable to Access UILabel in UITableViewCell - iphone

I am having trouble in accessing which is in the UITableViewCell, that i have placed in my main .xib file.
That Label is connected to IBOutlet servicesCell.
And the Label inside the table view cell is connected through IBOutlet serviceLabel.
At runtime i am not getting the text which i am assining to that label.
Following is my sample code for that.
static NSString *ServiceIdentifier = #"ServiceIdentifier";
UITableViewCell *cell1 = [tableView dequeueReusableCellWithIdentifier:ServiceIdentifier];
if(cell1 == nil) {
[[NSBundle mainBundle] loadNibNamed:#"servicesCell" owner:self options:nil];
cell1 = servicesCell;
}
// label access
serviceLabel = (UILabel *)[cell1 viewWithTag:1];
serviceLabel.numberOfLines = 3;
serviceLabel.lineBreakMode = UILineBreakModeWordWrap;
[serviceLabel setTextAlignment:UITextAlignmentLeft];
[serviceLabel setText:#"Testing String"];
Anyone have any idea then please help..
Thanks in advance..

You say that your Cell is in your main XIB file, but I take it the cell is actually in "servicesCell.xib", correct?
I assume you started with "Loading Custom Table-View Cells from Nib Files". This code is similar to Apple's example, so I assume you started there. If you just copied this code from somewhere without understanding it, go read that doc first.
You should walk through this code in the debugger and make sure that everything is set to what you think it should be set to. The most common cause of "nothing happens" is that you're sending messages to nil, and so I bet one of your variables here is actually nil. Looking at this code, the most likely culprits are failure to actually wire something in IB (this is the most common cause of nil objects when working with NIBs; double check that you really did it), or that you have not assigned your label the expected tag of 1.

Place the UITableViewCell in it's own nib.

Related

iPhone: smooth scroll through the UITableView

I have a UITableView, and custom cells on it. On cell I have a UILabel, but before I set text to UILabel I did really hard work on text...like find the text in another text, highlight some words on it, and only then I set it to label. So when I scroll my list, it has delay because of this hard work. Any idea how to improve performance ? Maybe to do all hard work in another thread ??
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:
(NSIndexPath *)indexPath {
static NSString *customCellIdentifier =
#"CellIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:
customCellIdentifier];
if (cell == nil) {
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"CustomTableRow"
owner:self options:nil];
if (nib.count > 0) {
cell = self.customTableRow;
}
}
self.myLabel.text = [self giveMeTheTextThatINeed];
return cell;
}
[self giveMeTheTextThatINeed] - did a hard work on text that takes some time.
Make a new thread for every cell. this thread calls [self giveMeTheTextThatINeed:indexPath], and resets the label(s) in the cell. I'm assuming you can't get your data any faster, so you want to maintain the scrolling in the table and spin the hard work out to the thread. When the thread is finished, update the cell. You see this a lot in cells with a thumbnail image where the thumbnail only gets uploaded after a while, and is blank or has a placeholder there first.
Any way for you to precompute the values you'll need? In other words, start doing your "hard work" (in another thread) when the app starts, and store it somewhere so that, if it's ready, you can just grab it when the table view asks for it. It's hard to answer without more detail about what the hard work is and how much data we're talking about.
I don't know about just doing the hard work on another thread as you suggested, since you still have to give something to tableView:cellForRowAtIndexPath:. I suppose you could return some kind of template cell at first, and then update it with reloadRowsAtIndexPaths:withRowAnimation: when the hard work is done.
I believe the method giveMeTheTextThatINeed needs to take the current cell as one of the parameters (or another parameter dependent on the cell content), e.g.: [self giveMeTheTextThatINeed:indexPath]. Otherwise you could store the text as an instance variable and set it in all cells from that variable.
So, with that in mind, the easiest way is to store the result of the computation in an additional dictionary, where indexPath (or the other parameter) would be the key:
self.myLabel.text = [self->myDictionary objectForKey:indexPath];
Now, you could either pre-populate that dictionary before the cells are drawn (e.g. in viewWillAppear), or cache them once they are calculated so that they are not recalculated when the cells are scrolled, e.g.:
NSString* calculatedText = [self->myDictionary objectForKey:indexPath];
if(calculatedText == nil)
{
calculatedText = [self giveMeTheTextThatINeed:indexPath];
[self->myDicationary setValue:calculatedText forKey:indexPath];
}
self.myLabel.text = calculatedText;

How to make tableview cell "detailLabel" editable?

I am trying to implement a suggestion offered by already answered question, but I am stuck on getting it to work, so looking for some help.
Here is the post I am trying to implement: http://stackoverflow.com/a/3067579/589310
I am trying to use the solution offered by "ridale" on how to make a "detailLabel" editable as part of a TableView. I hope it will allow me to directly edit a cell and enter a number. It doesn't seem too common as a UI, but "SmartRecord" does it and I want to emulate it.
Here is the only line that gives me an error:
UITextField *tmpView = [self detailLabel:indexPath];
I get this error:
Instance method -detailLabel: not found (return type defaults to 'id')
I assume it is because my self is different than the original poster.
I added a TableView directly to my existing controller. It is not a TableViewController directly:
#interface EditViewController : UIViewController
{
IBOutlet UITableView *tableSettings;
}
I can fill the table and interact with it, so I know it works (at some level anyway).
I have tried changing self to my table control or the cell directly:
UITextField *tmpView = [tableSettings detailLabel:indexPath];
I can't find anything that responds to the "detailLabel" method.
I am also not sure if the proposed solution is complete or uses more code not shown.
This is the only error I get, so I am hopeful once I solve it, it will work ;-)
-(UITextField*)detailLabel:(NSIndexPath*)indexPath is not present in UITableView or any other classes provided by Apple.
The frame position is just for sample. Modify that such that it would come in place of your detailLabel.
You have to write your own method which returns a UITextField, something as following
-(UITextField*)detailLabel:(NSIndexPath*)indexPath
{
UITextField *textField = [[UITextField alloc] initWithFrame:CGRectMake(0, 0, 100, 40)];
textField.delegate = self;
//assuming myList is array of NSString
NSString* str = [myList objectAtIndex:indexPath.row];
textField.text = str;
return [textField autorelease];
}

UISwitch and UITableViewCell iOS4

Caveat: I have looked for the answer to my question and several come close, but I'm still missing something. Here is the scenario:
I want a way to create UITableViewCells that contain UISwitches dynamically at run time, based on the data in a table (which I can do). The problem becomes connecting the switches such that I can get their value when that view is changed (navigated away, closed, etc). I have tried to use the events UIControlEventValueChanged to be notified, but have failed to specify it correctly, because it dumps when that switch is tapped. Also, there doesn't seem to be any way to uniquely identify the switch so that if all the events are handled by a single routine (ideal), I can't tell them apart.
So...
If I have a UITableView:
#interface RootViewController : UITableViewController
{
UISwitch * autoLockSwitch;
}
#property (nonatomic, retain) UISwitch * autoLockSwitch;
-(void) switchFlipState: (id) sender;
#end
// the .m file:
#implementation RootViewController
// ...
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString * CellIdentifier = #"Cell";
int row = 0;
NSString * label = nil;
TableCellDef_t * cell_def = nil;
row = indexPath.row;
cell_def = &mainMenuTableCellsDef[ row ];
UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil)
{
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
}
label = (NSString *) mainMenuTableCellsDef[indexPath.row].text;
[cell.textLabel setText:(NSString *) mainMenuItemStrings[ indexPath.row ]];
if (cell_def->isSpecial) // call special func/method to add switch et al to cell.
{
(*cell_def->isSpecial)(cell ); // add switch, button, etc.
}
else
{
[cell setAccessoryType:UITableViewCellAccessoryDisclosureIndicator];
}
}
and this is the 'special' function:
-(void) autoLockSpecialItem :(UITableViewCell *) cell
{
autoLockSwitch = [[[UISwitch alloc] initWithFrame:CGRectZero] autorelease];
[autoLockSwitch addTarget:self action:#selector(switchFlipState:) forControlEvents:UIControlEventValueChanged ];
[cell addSubview:autoLockSwitch];
cell.accessoryView = autoLockSwitch;
}
and finally:
-(void) switchFlipState: (id) sender
{
NSLog(#"FLIPPED");
}
==============================================================
Questions:
Why would it crash (bad selector) when the switch was tapped? I believe that my code follows all the example code that I have seen, but obviously something is wrong.
I cannot put a instance method into a table as a function pointer; and it doesn't seem to like a class method either. If I make it a 'C/C++' function, how do I get access to the class/instance member variables? That is, if I want to put a call to autoLockSpecialItem into a static table (or reasonable facsimile) such that I can get autoLockSwitch member variable? If I make it a class method and the autoLockSwitch var a static, will that be valid?
More simply: how do I connect the UIControlEventValueChanged to my view (I have tried and failed) and can I differentiate at runtime within the event handler which switch has changed?
Is there a better way? I cannot believe that I am the first person to have to solve this type of problem.
Apologies for the length, appreciation for attention and grateful for any and all help.
:bp:
Don't know about why your method isn't connected, but a simple way to "differentiate at runtime within the event handler which switch has changed" is to take the (id)sender given to your event handler, walk your tableview, and compare the sender to any switches, if present, in each table item. If that's too slow, a hash table connecting senders to table cells, or something like that, is a possible optimization.
If you want to use C function pointers, you need to pass the object to the function to use it to call the object's property accessor methods within the function. (Or you could assign the object to a global variable if it's clearly a singleton, but that's a very politically incorrect answer.)
First, and easy way to define your different switches would be defining their tag based on the row number. When one of the switches is tapped you can access sender.tag to get the row number this way.
Also, you should probably be adding the switch the the cells content view, not the actual cell, [cell.contentView addSubview:autoLockSwitch]. Also the frame does need to be set (note CGRectZero, cocoa will ignore the width and height but uses the x,y coords to define where you want the switch in the cell.

objc_msgsend when setting cell.imageView.image

this code works fine until I start scrolling:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier] autorelease];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
}
Route *r = [data routeForDay:day index:indexPath.row];
cell.textLabel.text = r.name;
cell.imageView.image = r.image;
return cell;
}
It works perfectly for every row until I scroll down, when it crashes with objc_msgsend on cell.imageView.image = r.image; I've confirmed that nothing is nil, I've even checked the retainCount for everything involved. I'm completely at a loss, any ideas? Thanks.
Edit:
I solved the problem, but do not understand how the change in code makes the bug go away, so I'd appreciate a hint if anyone knows.
This is how the image was initially created, in the Route init method. image became deallocated when the table was scrolled, in my tableview controller.
NSString *imagePath = [[NSBundle mainBundle] pathForResource:[dict valueForKey:#"image"] ofType:#"png"];
image = [UIImage imageWithContentsOfFile:imagePath];
When I changed the second line to
image = [[UIImage alloc] initWithContentsOfFile:imagePath];
it worked fine.
I'm just a little confused and unhappy.
Your bug is most definitely a retain/release problem. Try to turn on zombies. If you don’t know how to do that, see below and read this tech note.
I assume one of the objects pointed to by data, r, or r.image is not retained properly.
One more thing: don’t look at retainCount before understanding memory management (and especially autorelease pools) in depth. Otherwise you’ll only be confused by the returned value.
How to enable zombies:
Choose Project > Edit Active Executable to open the executable Info window.
Click Arguments.
Click the add (+) button in the “Variables to be set in the environment” section.
Enter NSZombieEnabled in the Name column and YES in the Value column.
Make sure that the checkmark for the NSZombieEnabled entry is selected.
Edit:
Congratulations on finding the bug. The only pice of the puzzle you’re still missing is understanding Cocoa’s memory management. I recommend that you read the official documentation, as it is concise and easy to read.
In short: imageWithContentsOfFile: returns an autoreleased object, while initWithContentsOfFile: returns a retained object. But again: read the docs, or you’ll continue having memory errors.

How can I use NIB loaded UIView in tableView viewForHeaderInSection without having a performance hit

I'm implementing tableView viewForHeaderInSection where I return a nib loaded UITableViewCell.
I've simplified it for the purpose of this question.
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
UIView* header = [CellFactoryController newSectionHeader];
header.text = "Some Text Depending on the section"
return header;
}
Unfortunatly this is very slow because I sometimes have 50+ headers which all get loaded from nib all at once when the table draws , even though those headers are not in view.
Is there any realistic way of me being able to clone UIViews and thus clone the header in this example? Or is the only way for me to just create the headers UIView hard coded?
Thanks
I was reading the doco on the UINib class the other night and it was talking about how it caches the nibs internal structure so repeated requests execute faster. That might be a suitable option for you.
Well, I don’t know if you still need an answer to this question, but… what the heck. Let’s do this. I’m going to make some assumptions, but if you’re having performance problems the only way to really solve this is going to be to test it, and without more knowledge of your views’ complexity, we can’t really get reliable metrics. So I’m going to assume that you’ve tried a few things and throw something out there that you maybe haven’t tried.
When you load a view out of a nib, it unarchives it from a file. If that loading is happening 50 times, that could go a long way towards your performance problems. So what else can we do? Well, in your table view controller class, make a new instance variable of type NSData. Then, in your -init method (or other designated initializer), load a view in from a property list:
- (id)init
{
self = [super init];
if (self) {
// Other initialization code.
NSString *filePath = [[NSBundle mainBundle] pathForResource:#"MyView"
ofType:#"plist"];
myData = [[NSData alloc] initWithContentsOfFile:filePath];
}
return self;
}
OK, so you have this property list loaded into an NSData. Now, the rest should be clear:
- (UIView *)tableView:(UITableView *)tableView
viewForHeaderInSection:(NSInteger)section
{
UIView *view = [NSKeyedUnarchiver unarchiveObjectWithData:myData];
return view;
}
My goal here was to keep the fully-instantiated view in memory the whole time. But then I thought, “how is he going to create that plist?” So I modified it a bit. Instead of loading the data from a file, create your view in code:
- (UIView *)tableView:(UITableView *)tableView
viewForHeaderInSection:(NSInteger)section
{
UIView *headerView;
if (myData == nil) {
headerView = [[UIView alloc] init];
// Configure your view, but only that part that isn’t customized.
myData = [[NSKeyedArchiver archivedDataWithRootObject:headerView] copy];
} else {
headerView = [NSKeyedArchiver unarchiveObjectWithData:myData];
}
// Now customize the view for your particular section.
return headerView;
}
That should give you the flexibility of creating the view in code, but hopefully also keep it in memory and cache it for you. Try it out!
You could, potentially, break up your views into separate .xib files to speed loading, but (as you said) your best bet is probably going to be to create those views procedurally. Outside of quickie sample code, .xib files are generally reserved for very high-level layout of containers needed for an entire app or a full-screen view. The layout and drawing detail-oriented elements— such as the cells, headers, and footers in table views— is typically better handled procedurallly.