Is proper practice to use introspection in this example?
I'm in a UITableView datasource method tableView:cellForRowAtIndexPath:, and I'm checking for the type of the data source objects, in order to decide which UITableViewCell subclass to use.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
....
id object = [[self getDataSource:tableView] objectAtIndex:[indexPath section]];
if ( [object isKindOfClass:[NSString class]] ||
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
} else if ([object isMemberOfClass:[NSNumber class]]) {
cell = [[[CMAutocompleteTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier andAutocompleteTextField:object] autorelease];
} else if ([object isMemberOfClass:[NSDate class]]) {
cell = [[[CMDateTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier andTextView:object] autorelease];
} else {
cell = [[[CMAutocompleteTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier andAutocompleteTextField:field] autorelease];
}
return cell;
}
I can't see any reason not to check the classes of your data objects, but you should use isKindOfClass: rather than isMemberOfClass:.
The latter checks for an exact match with the class of the receiver, which you're not going to get with the data classes you've got; they're all class clusters. Any given NSString you have, e.g., is going to actually be an __NSCFString. Likewise for NSDate and NSNumber.
Also, you should use ARC.
It's technically ok (with the change to isMemberOfClass: as Josh pointed out).
It's debatable, if it's good style. Does the same tableview really show items of those different classes next to each other? If there are several tableviews, using separate methods/delegates might be more appropriate.
Also, I think you could improve on your method naming. getDataSource is discouraged - "get" has quite a special meaning and is rarely used (for example, in getBytes on NSData). Also you want to get rid of "and" which is just noise.
Finally, is there any reason not to switch to ARC today?
Related
I am seeing some weird things happening with viewDidLayoutSubviews and UITableViewCells.
I have 2 tables on a page, both of which get sized so they do not scroll (all elements are visible). I take care of the resizing in viewDidLayoutSubviews like this:
- (void)viewDidLayoutSubviews {
self.orderItemTableViewHeightConstraint.constant = self.orderItemTableView.contentSize.height - [self tableView:self.orderItemTableView heightForHeaderInSection:0];
self.shippingOptionTableViewHeightConstraint.constant = self.shippingMethodTableView.contentSize.height;
[self.view layoutIfNeeded];
self.scrollViewContainerViewHeightConstraint.constant = self.shippingMethodTableView.$bottom;
[self.view layoutIfNeeded];
}
This works as expected.
However, when shippingOptionTableView is built, it has 3 cells (for this example), each with a model that feeds the label in the cell and a selection marker like this:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
if (tableView == self.shippingMethodTableView) {
DLShippingOptionTableCell *cell = [tableView dequeueReusableCellWithIdentifier:SHIPPING_OPTION_CELL_IDENTIFIER];
if (cell == nil) {
NSString *nibName = (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) ? #"DLShippingOptionTableCell~iphone" : #"DLShippingOptionTableCell";
NSArray *topLevelObjects = [[NSBundle mainBundle] loadNibNamed:nibName owner:nil options:nil];
for (id currentObject in topLevelObjects) {
if ([currentObject isKindOfClass:[DLShippingOptionTableCell class]]) {
cell = (DLShippingOptionTableCell *)currentObject;
[cell setupCell];
break;
}
}
}
cell.model = [self.model.shippingOptionArray objectAtIndex:indexPath.row];
return cell;
}
else {
...
}
}
The model gets set in the table cell, which causes the visual state to be updated:
- (void)setModel:(DLShippingOption *)model {
_model = model;
NSNumberFormatter *currencyFormatter = [[NSNumberFormatter alloc] init];
[currencyFormatter setNumberStyle:NSNumberFormatterCurrencyStyle];
NSString *numberAsString = [currencyFormatter stringFromNumber:[NSNumber numberWithFloat:(model.priceInPennies / 100.0f)]];
self.shippingLabel.text = [NSString stringWithFormat:#"%# (%#)", model.optionName, numberAsString];
[self toggleSelectedState:model.isSelected];
}
- (void)toggleSelectedState:(BOOL)isSelected {
[self setSelected:isSelected];
self.radioButtonImageView.image = isSelected ? [UIImage imageNamed: #"radio_button_selected.jpg"] : [UIImage imageNamed: #"radio_button_unselected.jpg"];
}
Here is the problem... the table cell get sent setSelected:NO repeatedly during the layout process. So, even when my model is set to selected, and I update the radio-button graphic (which I would normally do in setModel:) it gets overridden and changed to false.
I had to put this hack in place to make it work (basically adding this bit of code to the cellForRowAtIndexPath in the ViewController, just after setting the cell's model:
if (cell.model.isSelected) {
[self.shippingMethodTableView selectRowAtIndexPath:indexPath animated:NO scrollPosition:UITableViewScrollPositionNone];
}
I this bites... So the question is, why are table cells getting sent setSelected:NO over and over and over during layout, and is there a better way to overcome that?
Try using prepareForReuse method and see if it helps. It will be also helpful to see the documentation here:
https://developer.apple.com/library/ios/documentation/uikit/reference/UITableViewCell_Class/Reference/Reference.html
If a UITableViewCell object is reusable—that is, it has a reuse identifier—this method is invoked just before the object is returned from the UITableView method dequeueReusableCellWithIdentifier:. For performance reasons, you should only reset attributes of the cell that are not related to content, for example, alpha, editing, and selection state. The table view's delegate in tableView:cellForRowAtIndexPath: should always reset all content when reusing a cell. If the cell object does not have an associated reuse identifier, this method is not called. If you override this method, you must be sure to invoke the superclass implementation.
Also note that you should only use this method for stuff which is not related to content as pointed out in the documentation. Content related stuff should always be done in tableView:cellForRowAtIndexPath: method
I am populating a tableview from data that is received from a server. The data is a list of user activities within a given timeframe. One such activity is "Login". I do not wish to populate my tableview with this string but I'm not sure how to skip it when populating my tableview.
Here is how I populate the cell
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{ static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
#try{
NSManagedObject *object = [self.fetchedResultsController objectAtIndexPath:indexPath];
NSString *action = [object valueForKey:#"theActionName"];
if ([action isEqualtoString:#"Login"]) {
return cell;
}
return cell;
}#catch (NSException *ex) {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
return cell;
}
}
As you can see I tried using return cell but as you probably know it gives me a blank cell when the table is displayed. I'm sure there is a simple line of code for this but I came up blank with the search terms I used. Could someone please enlighten me! Thanks!
P.S. you may be thinking I am not putting anything in any of the cells but I pulled out a bunch of code to keep this short.
UPDATE:
Thanks for the heads up on "isEqualtoString:" Everything worked fine with "isEqual" but I changed it given that I received so many suggestions to do so. But this is not what I am asking.
To be more clear if I had an array containing the terms: view, view, login, view. When my tableview was populated I would have 4 cells that said; view, view, login, view. I simply want to ignore the term login so that I would have 3 cells that all said view. Thanks!
There can be many way to do this.
I Belive that UITabelView should display what its datasource (here datasource is self.fetchedResultsController) contains.
What you can do is create another NSArray from self.fetchedResultsController which does not contain this object.
Try this:
NSMutableArray *newSource = [[NSMutableArray alloc] init];
for(int i = 0; i < self.fetchedResultsController.count ; i++)
{
NSManagedObject *object = [self.fetchedResultsController objectAtIndexPath:indexPath];
NSString *action = [object valueForKey:#"theActionName"];
if (![action isEqual:#"Login"])
{
[newSource addObject:action];
}
}
[tableView reloadData];
Now use newSource instead of self.fetchedResultsController
You might think that using one more array is not good. But believe it it is far easier than using the same array with condition. You don't have to worry about that condition when you perform some operation with your UITableView like remove object by using indexpath.
try using if ([action isEqualToString:#"Login"])
When you want to compare strings you need to use this isEqualToString .
Change this line
if ([action isEqualToString:#"Login"]) {
return cell;
}
You are using the wrong function to compare your input string and the given data variable.
They both are NSString objects so use :
if([action isEqualToString:#"Login"])
{
//enter your code here
}
#Ben : I am assuming that you have registered you cell through nib as you are using dequeueReusableCellWithIdentifier.
Make your tableview content as "Dynamic prototype" (You can see this in Attributes Inspector of table view) and change your table view cell style as custom (You can see this in Attributes Inspector of tableview cell).
This could be a silly question but I am new to IPhone developing, anyhow I have an NSMutableArray with information loaded from a server. I am trying to put this information on a table. I searched around for some ideas on how to do this and I ran into this code which a lot of people seem to use in order to do this:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [myArray count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"UITableViewCell"];
if (!cell) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"UITableViewCell"] autorelease];
}
cell.textLabel.text = [myArray objectAtIndex:[indexPath row]];
NSLog(#"Cell is %#", [myArray objectAtIndex:indexPath.row]);
return cell;
}
now my question is inside the if statement it gives me two errors with the autorelease saying: is unavailable and not available on reference counting mode, and arc forbids message send of autorelease, any thoughts?? thanks for the help
The code you have is older code and only works as-is with manual reference counting (MRC). Newer projects use automatic reference counting (ARC) by default. Just remove the call to autorelease and you will be fine.
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"UITableViewCell"];
You are using Automatic Reference Counting, which takes care of memory management for you (for the most part). Remove all references to manual memory management, such as autorelease (and retain, release, etc.), and the app will build. Use this here below:
if (!cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"UITableViewCell"];
}
This is also easily done using the freely available Sensible TableView framework. They have something called ArrayOfObjectsSection where you just pass the NSArray to and it will display it automatically, amongst many other stuff (including doning the server fetching for you).
This is a simple tutorial link for add a NSMutableArray into the tableView...Simple Tutorial link..Hope Its useful for you.
I have created a table view controller with a UITextField on it. How can I populate the tableview controller cell of the input text that came from the UITextField. I tried the following but it seems the array is empty or being emptied
- (IBAction)endingInput:(id)sender{
[sender resignFirstResponder];
//send the message to the table cell
NSMutableArray *history = [[NSMutableArray alloc] init];
[history addObject:[NSString stringWithFormat:#"%#",self.chatMessageField.text]];
[self.chatHistory addObject:history];
[myTableView reloadData];
//push the message to server
[self initiateMessageSending:self.chatMessageField.text];
chatMessageField.text = #"";
[history release];
}
On my cellForRowAtIndex method;
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
// Configure the cell...
if(cell == nil){
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
if(tableView == self.myTableView){
cell.textLabel.text = [chatHistory objectAtIndex:indexPath.row];
}
return cell;
I have set a break point but it doesn't pass there. I suspect my array is empty.
Likewise, where is the best place to call the reloadData method?
Also, chatHistory is private member, is there a way to initialize it like [[self.chatHistory alloc] init]; ?
I know its a common question but I've been haggling with this for sometime now.
I think you have forgotten to alloc and init your chatHistory
- (void)viewDidLoad
{
self.chatHistory = [[NSMutableArray alloc] init];
}
If you aren't initializing chatHistory it doesn't even exist...never mind being empty! (I'm not clear what "private member" means here.)
If you log the value of chatHistory, you'll see for sure whether it is nil or empty.
The other problem I see is that you're trying to load chatHistory with other (history) arrays but then use its content as a string. You should decide whether it's going to be an array of arrays or an array of strings and then stay consistent.
(Also [[self.chatHistory alloc] init]; isn't valid syntax for creating a new object. Look at an Objective-C language reference for the right way.)
I am creating a simple app to select video urls out of a UITable. I have hooked my data source and delegate to a UIViewController subclass and the table is filled correctly. Also, it recognizes selections and prints to the log.
My issue is that it gets a "EXC_BAD_ACCESS" and crashes when I select a cell. I am looking through the code and the error propagates to this method:
-(UITableViewCell*)tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString* cellIdentifier = #"SelectionIdentifier";
//Errors start happening this next line
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if(cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier] autorelease];
}
//NSString* str = [[videos objectAtIndex:[indexPath row]] lastPathComponent];
NSString* test = #"test";
[[cell textLabel] setText:test];
return cell;
}
-(void) tableView:(UITableView*)myTableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
// NSLog(#"Selected!");
}
-(NSInteger)tableView:(UITableView*)tableView numberOfRowsInSection:(NSInteger)section {
return [videos count];
}
I'm not sure why this error is getting thrown in this method. Any fixes? I double checked to make sure the videos array wasn't nill.
I did another app that used this same method and it doesn't cause these errors.
Any help or suggestions would be greatly appreciated!
Instead of testing if(cell == nil) try using if(!cell). Honestly I'm not sure this is the issue, but after reviewing this I do not think that the error is not actually inside this method (it may somehow be related which is why it brings you here).
If this is only after you select a cell though, why is this method being called?
I think you should also call this method.Because this is preliminary delegate method
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
After seeing your tableView i am not finding any problem at all.Try that may be it will be solve.