Get hidden value from Cell in TableView - iphone

I want to populate a TableView with data from a database and, when tap a cell, in didSelectRowAtIndexPath, sending selected ID to a new View.
I would like to view, in cell text, the description of the record and to put ID in a hidden field/property of the cell, but I could not find a way to do it.
I know I can get an ID from the data source (something like [my_data_source objectAtIndex:indexPath.row]; ) but I don't really like this way, I would prefer to have an ID assigned to a single cell.
Is there a way to do it?
Thanks in advance and greetings.

I'm guessing you've come from web development? I also found it difficult to do it this way, but its the best way. IT probably is possible - but its better if you get used to doing it like this, it really is.
Basically define an NSArray in the .h file (so the whole script can use it).
then in the init function:
// set the array
myArray = [NSArray arrayWithObjects:#"One",#"Two",#"Threee",nil];
[myArray retain];
then the table view delegate methods:
// set numebr of rows
- (CGFloat)tableView:(UITableView *)tableView numberOfRowsForSection:(NSUInteger)section {
return [myArray count];
}
// set the cell titleLabel value
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// set the cell - I can't remember the exact code then do:
cell.textLabel.text = [myArray objectAtIndex:[indexPath row]];
}
// similarly
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSLog(#"%#",[myArray objectAtIndex:[indexPath row]];
}
(disclaimer: these are just off the top of my head, and I haven't checked the exact methods - they're probably wrong in some way - but the code inside the functions is what you want really.)
When you've started using this you'll see its so much better than "hiding" an id somewhere in a table. To get things from the database I would suggest adding it all to a dictionary, or an array or similar and doing it like that when you init the class, but if you really want to do it dynamically then pretend your "hidden" ids are just index's of an array. So id#1 is at index 1 in your array. :)

Allright here is a quick hack on a different approach. I always like to deal with objects that are self contained. Pack all the data that you want into a custom class (here called MyData) for the table view you initialise it with the minimal amout that you need there. id and text that you pulled from the database. You also implement a function that can load the rest of the data from the DB.
When the item gets selected you pass the instance of your object to the subview controller and fill its data from the database. You can trigger the filling in the main viewcontroller or the subviewcontroller, that does not matter.
The main point is to pack all the data that goes together into one object (basically a "model" you already have a view and controller) and then fill views by accessing that object. This keeps your interface the same all the way through your applications. And makes changes easier. For example if you find out that it is better to fill in all the data from the DB at the start of your program you can do that now without changing the other views.
#interface MyObject : NSObject
{
}
// Create a stump object that contains only the necessary info
+ (id) withName:(NSString)name id:(int)id;
// loads the rest of your data from the DB
- (void) fillFromDb;
#property (readwrite, retain) NSString name;
#property (readwrite, assign) int id;
// The data fields that you need
#end
// in tableview controller
#interface MyTableViewController ...
{
NSMutableArray _dbData;
}
#end
#implementation MyTableViewController
- (void) viewDidLoad {
// Load your data from DB
for (int i =0; i < dbCount; ++i)
{
MyObject* data = [MyObject withName:dbName[i] id:dbId[i];
[_dbData addObject:data];
}
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
cell.textLabel.text = [_dbData objectAtIndex:[indexPath row]];
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
//Create Subviewcontroller
// Optional call fillFromDb here and not in subviewcontroller
subviewcontroller.dbData = [_dbData objectAtIndex:[indexPath row]];
//activate subview
}
#interface SubViewController {
MyObject* _dbData;
}
#end
#implementation SubViewController
- (void) viewDidLoad {
[_dbData fillFromDb];
// Do View Initialisations with the newly fetched Data
}
The code is here just to demonstrate the architecture

Related

iOS 7 UITableView indexpath row property is nil

I have a UITableView and it contains a custom UITableViewCell. To test, I have an array that has three strings in it. The UITableView delegate methods are called as expected, however, the tableView:cellForRowAtIndexPath delegate is always passed an NSIndexPath instance whose row property is always == nil:
tableView:cellForRowAtIndexPath is called 3 times (once for each object in my array). I added the tableView from within the designer (storyboard) and created an outlet for it. The UITableViewCell instances appear to be correctly instantiated which each call to this delegate. I just can't wrap my head around why the [indexPath row] value is always nil.
Interface(s):
In the implmentation file:
#interface FirstViewController ()
#property(nonatomic, strong)AppDelegate *sharedDelegate;
#property(nonatomic, strong)NSArray *userList;
#end
In the header:
#interface FirstViewController : UITableViewController <FacebookDelegate>
#property (strong, nonatomic) IBOutlet UITableView *tableView;
#end
Init the custom cell:
-(void)viewDidLoad
{
[self.tableView registerClass: [ListCategoryCell class]forCellReuseIdentifier:#"ListCategoryCell"];
self.userList = #[#"d", #"g", #"f"]; // make some test data
}
And the delegate this is driving me mad:
//NSIndexPath.row is nil ?!
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *MyIdentifier = #"ListCategoryCell";
ListCategoryCell *cell = [tableView dequeueReusableCellWithIdentifier:MyIdentifier forIndexPath:indexPath];
if (cell == nil) {
cell = (ListCategoryCell *)[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:MyIdentifier];
}
cell.titleLabel.text = [self.userList objectAtIndex:[indexPath row]];
cell.detailLabel.text = #"Detail";
return cell;
}
Am I missing something? Thanks!
Working Now
I left out some context (and I should not have) that I believe was very relevant to my problem. I created a UIViewController originally and then added a UITableView to this as the view. In the UITableView I created a custom prototype cell. I did all the house work:
UIViewController implemented the UITableViewDelegate & UITableViewDatasource.
Created and outlet for the UITableView.
Hooked up all the outlets
Everything seemed to work except for the fact that indextPath.row property was always nil. Some resources I found suggested that custom cells were not visible before the uitableview delegates were called.
In the end I made my class a subclass of UITableViewController. Things started working. I am still curious why my original attempt was failing.
Thanks for everyone's time. Some great comments helped me investigate some topics that are "good to know".
You need to provide at least two methods in view controller if you want it to manage your table view. They are:
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
-(UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
You've already provided the second, so your table view actually can produce cells but it doesn't know how many.The default value the first method returns is nil.That is the reason why you don't even have an index path.
Optionally:
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
The default value is one,so you don't need to override this in case you have only one section
Make sure your view controller also follows delegate and datasource protocols.

UITableView in Storyboard appears empty

It's my first time using Storyboard to build an app. I'm trying to create a UITableView with a custom cell. I've created the cell in IB and created a custom tableViewCell class for it (added some labels, created appropriate outlets, connected them in IB and assigned the custom class to the custom cell in IB).
In the ViewController responsible for the TableView I create the data source (a dict with some arrays) and fill out all the obligatory methods (I've tried both with UITableViewControler and with a UIViewController that is set as a delegate and data source for the tableview)
And when I run the app - the tableview is empty. And after doing some NSLogging I've noticed that the data source methods are never executed. And I have NO IDEA why.
I'm going crazy about this for a few hours now. Please help me out :)
Let me know if you need to see the code or Storyboard, or whatever else.
UPDATE: Alright, after some digging, I've decided to test out the same code but using an NSMutableArray as data source instead of a NSMutableDictionary. And it works!
Now, could someone explain to me why it didn't work with a dict?
Here's what did. I had a dict with 5 arrays, and each array had 5 strings.
In the numberOfRowsForSection method, I returned [dict count]
And in the cellForRowAtIndexPath method, I've used this code
NSArray * routeArray = [dealsDict objectForKey:#"route"];
cell.routeName.text = [routeArray objectAtIndex:indexPath.row];
NSArray * companyArray = [dealsDict objectForKey:#"company"];
cell.companyName.text = [companyArray objectAtIndex:indexPath.row];
NSArray * priceArray = [dealsDict objectForKey:#"price"];
cell.priceLabel.text = [priceArray objectAtIndex:indexPath.row];
NSArray * dateArray = [dealsDict objectForKey:#"date"];
cell.dateLabel.text = [dateArray objectAtIndex:indexPath.row];
NSArray * monthArray = [dealsDict objectForKey:#"month"];
cell.monthLabel.text = [monthArray objectAtIndex:indexPath.row];
NSLog(#"I'm in here");
return cell;
why didn't it want to show anything?
In the IB, select your table view, and make sure that the delegate and dataSource outlets are set for your controller
UITableView need some collection dataset as data source. Its delegate method "cellForRowAtIndexPath" get called equals to number of elements of collection (ARRAY). like numberOfRowsInSection delegate tells the table view that it need to call cellForRowAtIndexPath for every element in collection. and it also need some iterator to read each time the next element, in case of dictionary it always stick to same data element at each call of cellForRowAtIndexPath. I hope it wil give you the idea of its working, Please let me know if you need anything else to know.
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [self.data count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
MessagesCell *cell = [tableView dequeueReusableCellWithIdentifier:#"messageCellIdentifier"];
Message *msg = [self.data objectAtIndex:indexPath.row];
cell.Name.text = msg.Name;
cell.Message.text = msg.MessageText;
cell.Time.text = msg.Time;
return cell;
}

Multiple TableViews in a single screen

I have a UIViewController that I plan to have two TableViews and some other items in.
Both TableViews I am using on other screens, so I want to make them as independent and reusable as possible. One of those TableViews is called messageList (A UITableView) which shows my ChatHistory.
I am trying to understand if my approach is. [Edited 9/2 with correct code to make this approach work]
One approach would be to use a single table with 2 different sections, then in the delegate methods use a conditional statement to see which section is which and act accordingly.
The problem with this approach is usability. I want to easily reuse my TableViews in other views where one or the other TableView may or may not exist. Additionally, I want the DataSource to exist throughout the lifecycle of the app regardless of what Controller is instantiated or active.
My approach is to separate the view controller that manages the table view's from the table UITableViewDataSource and UITableViewDelegate implementations. But I am having a problem making this work.
Focusing on one of the TableViews, my ChatTableView.
In my AppDelegate has a property for chatHistory of type ChatHistory which implements UITableViewDelegate & UITableViewDataSource.
// AppDelegate.h
ChatHistory *chatHistory;
...
#property (nonatomic, retain) ChatHistory *chatHistory;
// ChatHistory.h
#import <Foundation/Foundation.h>
#interface ChatHistory : NSObject <UITableViewDelegate, UITableViewDataSource> {
UITableViewCell *nibLoadedCell;
NSMutableArray *messages;
}
#property (nonatomic, retain) UITableViewCell *nibLoadedCell;
#property (nonatomic, retain) NSMutableArray *messages;
#end
// ChatHistory.m - Note this code, including the custom cell was working correctly when it was a part of the controller so I believe it should be correct
#import "ChatHistory.h"
#include "ChatMessage.h"
#implementation ChatHistory
#synthesize nibLoadedCell; // custom cell design
#synthesize messages;
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView
numberOfRowsInSection:(NSInteger)section {
return [messages count];
}
- (NSString *)tableView:(UITableView *)tableView
titleForHeaderInSection:(NSInteger)section {
return [NSString stringWithFormat:#"Discussion"];
}
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
[[NSBundle mainBundle] loadNibNamed:#"ChatTableCell" owner:self options:nil];
cell = nibLoadedCell;
}
// custom tag order - username; message; future - Avatar; like; dislike
ChatMessage *obj = [messages objectAtIndex:indexPath.row];
UILabel *messageLabel = (UILabel *) [cell viewWithTag:1];
messageLabel.text = obj.message;
UILabel *usernameLabel = (UILabel *)[cell viewWithTag:2];
usernameLabel.text = obj.sender;
return cell;
}
- (void)dealloc {
if (messages) [messages release];
[super dealloc];
}
#end
// MyViewController.m
- (void)viewDidLoad { // MAKE SURE TO INITIALIZE viewDidLoad not InitWithNib
if (!appDelegate.chatHistory)
appDelegate.chatHistory = [[ChatHistory alloc] init];
messageList = [[UITableView alloc] initWithFrame:CGRectMake(0, 54, 320, 100) style:UITableViewStylePlain];
messageList.dataSource = appDelegate.chatHistory;
messageList.delegate = appDelegate.chatHistory;
[self.view addSubview:messageList];
...
You do not need to make a view controller your tableview DataSource or delegate; any object can be set. You can use a singleton as Felix suggests, or any other class structure you want. Since you mention that you want the chat history to be available from anywhere in the app, it makes sense to provide a UITableViewDataSource protocol to that chat history.
As for the UITableViewDelegate, you can simply create a new class as a subclass of NSObject and implement the delegate there. Make sure it's created and retained properly, and set in (upon load) as the delegate for your table views.
If you want one central data store, you could create a Singleton class with the data.
Then set it as the data source for the table view or fetch the array (or whatever you got) from the data store in your UIViewController / UITableViewController.
If you initialize the data store in your AppDelegate, you can access it from every class you want (note that all data you load, will remain in memory until your application gets terminated by iOS)
How to create a Singleton class in Objective-C
I would do it this way: make the two UITableViews subviews of one view controller.
In each of the datasource methods you simply distinguish between the two tables, similar to the code Apple provides in the UISearchDisplayController examples. For instance:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
if (tableView == myTableView1) {
// return appropriate number of rows
}
// return appropriate number of rows for the other table view
}
I know this does not separate the two table view classes very neatly. You would have to have a view controller controlling two other view controllers, one for each tableview.
Perhaps one has to consider the tradeoff between reusability and the complications of a more convoluted architecture (which typically also leads to classes that do not perform all that well in terms of reusability). That's why I would recommend the approach from the Apple sample projects.

UITableView refreshing problem

I am trying to refresh an UITableView every time I navigate the the view that contains this Table.
I Have a ViewController and a custom UITableViewController that manages to set the Table Correctly when the application starts, using an NSMutableArray contained inside the controller.
When I navigate to the page containing the table, the ViewController calls a function that gets the data from a server with an HTTP request and parse it in an NSMutableArray.
Now here is my problem. I manage to send this array to my UITableViewController, but when I want to refresh my tableView, nothing happens.
I tried to use [myTable reloadData], but it doesn't calls the numberOfRowsInSection, or cellForRowAtIndexPath functions. I saw that people with the same problem solved it using [self.myTable ReloadData], but I get an error :
accessing unknown getter/setter method
I am pretty new to objective-C, and this error is still a bit mysterious to me as I get it a bit randomly.
Anyway, there is a high probability that I made a mess with the declaration of the UITableViewController (where am I supposed to declare it?) and with the Interface Builder links, so this can be a clue to find the solution.
Any one have an idea?
Thank you very much!
EDIT : Here is my tableview controller class:
#import "MyCell.h"
#class Mycell;
#interface MyTableController : UITableViewController {
IBOutlet MyCell * myCell;
IBOutlet UITableView * myTable;
NSMutableArray *data;
}
#property (nonatomic, retain) IBOutlet UITableView * myTable;
- (void) EditTable : (NSMutableArray*) param;
#end
And now the .m:
#implementation MyTableController
#synthesize myTable;
- (void) viewDidLoad {
[super viewDidLoad];
myTable = [[UITableView alloc] init];
data = [[NSMutableArray alloc] init];
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [data count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"MyCell";
MyCell *cell = (MyCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier]; >
if (cell == nil) {
NSArray *topLevelObjects = [[NSBundle mainBundle] loadNibNamed:#"MyCell" owner:self options:nil];
for (id currentObject in topLevelObjects){
if ([currentObject isKindOfClass:[UITableViewCell class]]){
cell = (MyCell *) currentObject;
}
}
}
NSString *datastring = [listenom objectAtIndex:indexPath.row];
[cell setCell: datastring ];
return cell;
}
- (void) EditTable : (NSMutableArray*) param{
//This function is called by the ViewController when the user goes to the page containing the view
data = param; //The param array contains the data from the HTTP request
[self.tableView reloadData];
[self.myTable reloadData]; //I tried both, but only the first one actually calls the previous functions
}
You have a number of problems in this code sample. I'll point out a few of them here but I highly recommend reading the relevant Apple documentation at:
http://developer.apple.com/library/ios/#featuredarticles/ViewControllerPGforiPhoneOS/Introduction/Introduction.html
and
http://developer.apple.com/library/ios/#documentation/userexperience/conceptual/TableView_iPhone/AboutTableViewsiPhone/AboutTableViewsiPhone.html
Some issues in your code:
Since the class MyTableController is a subclass of UITableViewController you don't need the attribute and property for myTableView. The tableView property is defined and initialized as part of UITableViewController's implementation with its dataSource and delegate set to the UITableViewController instance. This is why [self.tableView reloadData] is calling your delegate and dataSource protocol methods.
You are also using interface builder so if you did want to create your own subviews you should either do that within IB and set the outlet there or do it in your code which means creating the subview(s) in viewDidLoad and then adding them to your view with [view addSubview:mySubView].
A better way to set the data for your table would be to create a property for your data attribute and call setData from the view controller that has initialized the MyTableController instance. You would use the setData: method to do this. You can call [self.tableView reloadData] in setData. You don't need to explicitly reload the table when the view is loaded as this is done automatically. A more minor point, if you stay with EditTable I would rename it to be more descriptive and to use camel case (e.g. setDataForTable`) to be consistent with iOS conventions.
You don't show any init/alloc for the listenom attribute referenced in tableView:cellForRowAtIndexPath:. Did you mean to use data instead?
Is your MyTableController.m file the complete version? If so, you are missing viewDidUnload and dealloc methods. Both of which are required. viewDidUnload should release any objects allocated in viewDidLoad and dealloc should release anything retained by the controller (including objects released in viewDidUnload.
As you are using tableViewController you should be able to use self.tableView instead to reload the data like this
[self.tableView reloadData];
you need to synthesize first then you can use self.myTable
do on the top
#synthesize myTable
and then
[self.myTable reloadData];

Memory issue in iPhone OS (shorter version)

I'm facing some sort of memory related issue I cannot figure out.
I have one summary view that lists some settings and a detail view where you can edit each setting.
In the summary view I have a tableview populated by an array (settingArray) of settings which in turn is loaded from a core data repository.
// SettingsViewController.h
#interface SettingsViewController : UIViewController <UITableViewDelegate, UITableViewDataSource> {
NSMutableArray *settingArray;
}
#property (nonatomic, retain) NSMutableArray *settingArray;
// SettingsViewController.m
- (void)viewWillAppear:(BOOL)animated {
[self setSettingArray:[DataHelper searchObjectsInContext:#"Sections" :nil :#"Id" :YES :managedObjectContext]];
}
This array of NSManaged objects is used to assign values to a custom UITableViewCell which has two labels: title and description.
// SettingsViewController.m
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
[…]
[cell.titleLabel setText:[[settingArray objectAtIndex:indexPath.row] valueForKey:#"Title"]];
[cell.descriptionLabel setText:[[settingArray objectAtIndex:indexPath.row] valueForKey:#"Description"]];
[…]
}
When the user clicks on a row, this loads a detail view where you can edit the values and save them to the database. The summary view passes the detail view an NSManagedObject reference (settingObject) in order to know what record in the core data database was selected and must be updated.
// SettingsViewController.m
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
if (settingsDetailViewController == nil) {
settingsDetailViewController = [[SettingsDetailViewController alloc] initWithNibName:#"SettingsDetailView" bundle:nil];
}
// Pass corresponding settingArray object to detail view in the settingObject variable
settingsDetailViewController.settingObject = [settingArray objectAtIndex:indexPath.row];
[…]
}
In the detail view, the user can modify some values and then save the modified core data object in the database.
// SettingsDetailViewController.m
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
// Update current setting in database
[self.settingObject setValue:[tableView cellForRowAtIndexPath:indexPath].textLabel.text forKey:#"Description"];
}
The problem is that once the value is updated in the detail view in this NSManagedObject, when the tableview information in the summary view is reloaded, I get a EXC_BAD_ACCESS exactly in the point where the label information is read from the settingArray.
// SettingsViewController.m
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
[cell.titleLabel setText:[[settingArray objectAtIndex:indexPath.row] valueForKey:#"Title"]];
// HERE IS WHERE THE ERROR OCCURS IN THE SECOND PASS
[cell.descriptionLabel setText:[[settingArray objectAtIndex:indexPath.row] valueForKey:#"Description"]];
[…]
}
I guess it's related to the release of the settingArray or settingObject being used, but I tried different approaches and no solution yet. All variables are declared in the corresponding .h, properties are added using nontoxic and retain, the accessors are synthesized in the .m and the objects are released in the dealloc function. According to the Cocoa memory management guidelines it should work.
The funny thing is that other parts of my code use identical arrays and pass identical objects with no problems whatsoever.
Any hints please?
Clang static analysis finds many reference counting errors, so you might want to give it a try.
You can either run it manually, using Run -> Build and Analyze, or you can enable it for all builds via the "Run Static Analyzer" build setting.
Note that this tool presently only supports C and Objective-C, but not C++ / Objective-C++.
It's hard to pinpoint the problem without the complete source.
Some items you can try:
Make sure the "settingObject" property in SettingsDetailViewController.h is declared as a "retain". Maybe you forgot the "retain", but released this instance in the dealloc method?
Try not setting the settingObject property in the SettingsDetailViewController (and not using it in any method - just comment out those sections). If that makes a difference, it must have to do with SettingsDetailViewController's retain/release of this settingObject
Trace the "retainCount" of the settingObject object in SettingsDetailViewController (debugger or NSLog statements) where you use it and after you release it in dealloc. That might give you some clues