I searched a bit here and on google, but I couldn't really find unearthing that I could clearly understand. Basically what I'd like to do is display a list of names, then when clicking on the table add the ID of those names to another array, along with their ID. The solution I'm working on at the moment is based on nested NSMutableArrays but I don't know if that's the best thing to do. I think it would be ideal to have another "field" to write on in the cell (by field I mean something like cell.text) so that I could store my hidden value.
Is there a way to do so?!
You need to adjust your thinking. Cocoa strongly follows the MVC (Model View Controller) design pattern, and you should learn to think that way.
In MVC, the model is the part of the program that stores your data. The View is what displays information to the user and interacts with the user.
The controller is the brains that serves as the control logic and feeds data back and forth between the model and the view.
In UIKit, objects with View in their name are view objects, and objects with controller in their name are controller objects. As a general rule the model object is up to you, as its application specific.
A table view is a view object. It doesn't store information, it presents it to the user and/or collects it from the user.
An array usually is the best choice for storing the data that gets presented in a table view. If you have a table view with multiple sections and multiple rows in each section than an array for each section, containing an array with data for each row is a good choice. Unless you have a sectioned table view forget about that however. It'll just confuse you.
For a "flat" table view with only one section, a single array is usually what you want.
How you store the data that you display in a cell is a design question. One easy answer is to use a dictionary for the data in each cell. So you wind up with an array of dictionaries.
If you're comfortable creating custom objects the a custom data storage object makes your code a little cleaner. However, you will probably want to implement the NSCoding protocol in your data objects so you can save them to disk easily.
Using an array to store the data for a cell is NOT a very good choice, because arrays use numeric indexes for their data. You might have a name that you want to display in your cell, and an image, and an employee ID, and a salary, and a few other things. What numeric indexes should you use for those values? Not clear. Now, if you use a dictionary, you can use meaningful keys:
cellData[#"name"]
cellData[#"imageName"]
or better yet, define constants for your keys:
#define kNameKey #"name"
#define kImageNameKey #"imageName"
then
cellData[kNameKey]
cellData[kImageNameKey]
That way you don't risk mis-typing the key string somewhere and introducing a bug that you have to go figure out.
If you're a beginner you might want to start out with an array of dictionaries. After you've used that approach for a while you might decide to migrate to custom data objects.
Let's assume we're using a dictionary to store the data for each cell from now on.
Now, to your question. It's perfectly ok to store more information in the dictionary for each cell than you display at any one time. The data isn't really hidden, it's just not (yet) displayed. Then, if the user takes an action that means you want to expose additional data, tell the table view to reload that cell. You might want to keep an array of info about the status of each cell (e.g. a bool that tells if the table view should display the salary info for each entry in your data array.) When the user does something that changes the info you want to display, change the appropriate setting in that index in the display status array and tell the table view to reload that cell. In your cellForRowAtIndexPath method, you would check the display status array for what to display for that cell, then fetch the appropriate data and install it into the cell (and clear out fields that should not be shown, since they might be left over from the last time the cell was used.)
If you have an array of dictionaries, then your cellForRowAtIndexPath method might look up the attributes of an entry like this:
- (UITableViewCell *)cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSInteger row = indexPath.row;
NSDictionary *rowData = dataArray[row]
NSString *name = rowData[kNameKey];
NSString *imageName = rowData[kImageNameKey];
}
Think more object-oriented, it'll make things like this a lot easier.
Make a class called Person with two properties: name and ID. Subclass UITableViewCell to take a list of Person, for which each name will be displayed. When the cell is clicked, add each Person to the appropriate location.
Here is a link for a tutorial on how to subclass youor own custom table view cell:
http://www.pumpmybicep.com/2014/07/21/designing-a-custom-uitableviewcell-in-interface-builder/
As for the Person class, create a new class of type NSObject called Person. In Person.h, add the following:
#property (nonatomic) NSString *name;
#property (nonatomic) int *ID;
- (id)initWithName:(NSString *)name ID:(int)ID;
And in Person.m, add the following:
- (id)initWithName:(NSString *)name ID:(int)ID {
if (self = [super init]) {
self.name = name;
self.ID = ID;
}
return self;
}
Now you can transfer data using Person.
Initialize:
Person *myPerson = [[Person alloc] initWithName:myName ID:1];
Get:
customCell.text = myPerson.name;
Transfer ID:
[clickedIDs addObject:[NSNumber numberWithInt:myPerson.ID]];
You could subclass a UITableViewCell and add an extra property
#property (nonatomic, strong) NSString *hidden;
for example. Then:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
CustomUITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell" forIndexPath:indexPath];
cell.text = #"Name";
cell.hidden = #"ID";
return cell;
}
That's how to do what you've asked, however it seems an odd request so you may want to consider if that's really the best way of achieving what you're trying to do. I'm not clear from your question what you've tried though.
Related
I have two UICollectionViews C1 & C2 in a master detail layout on an iPad app. C1 is master collection view and displays primary data. When one taps on a cell in C1 then C2 will be updated and shows all objects information including the primary object information already being shown on C1.
Is it possible to have a single data source for both the collection views?
I thought out below given design for the data source
#protocol MyDataSourceDelegate <NSObject>
#optional
//Below method is called to notify collection view that a new object is added to the data source and so it must update its UI
- (void)didInsertItemAtIndexPath:(NSIndexPath*)indexPath;
//Below method is called to notify collection view that an existing object is removed from the data source and so it must update its UI
- (void)didRemoveItemAtIndexPath:(NSIndexPath*)indexPath;
#end
#interface MyDataSource : NSObject
#property(nonatomic,strong)NSArray *fetchedObjects;//main objects array
#property(nonatomic,strong)NSArray *masterObjects;//array of objects displayed in master collectionview
#property(nonatomic,strong)NSArray *detailObjects;//array of objects displayed in detail collection view in multiple sections.
//called from within collection view when a cell is tapped
- (void)selectItemAtIndexPath:(NSIndexPath*)indexPath isMasterData:(BOOL)isMaster;
//call when a collectionview cell needs information for a particular cell's contents
- (id)itemAtIndexPath:(NSIndexPath*)indexPath isMasterData:(BOOL)isMaster;
- (id)itemAtSection:(NSInteger)section isMasterData:(BOOL)isMaster;
#end
The problem with above implementation is that I want to keep data manipulation related work completely out of view controller hosting above two collection views so as to avoid bloating and same data source for both collection views. In above scenario how delegation will work between collectionviews and MyDataSourceDelegate protocol? As per my understanding delegation is a 1-1 relationships so how a single data source can be managed between two collection view?
A data source is a very handy way to pass information around your application and can handle pretty much every class you choose to implement within your project. I use a data source mainly to handle a core data model, but I also use it for other things (passing around custom methods,etc).
I recently was using a data source for two tableview controllers, and this did cause some problems. However, if you keep your data separate in your data source for each collection view you should be okay. It WILL get harry when you start passing around properties (especially your arrays) between the controllers and the data source. You can run the risk of mutating your data.
A teacher of mine once said that the best programmers write the least amount of code. But if you find your data getting mutated, or you aren't getting the results back that you need, you may just need to implement it within your controller temporarily. Get it working and then decide the best way to move it to your data source. You might also consider creating similar properties for your arrays in each controller to store the information from your data source. You might be okay with immutable arrays, but if you're going to be manipulating the data you'll want to store it in a new property.
Consider the scenario below. I'm going to use the *fetchedObject array for my example.
DataSource.h
#interface DataSource : NSObject
+(instancetype) sharedInstance;
#property (nonatomic, strong) NSArray *fetchedObjectsC1;
#property (nonatomic, strong) NSArray *fetchedObjectsC2;
#property(nonatomic,strong)NSArray *masterObjects;
#property(nonatomic,strong)NSArray *detailObjects;
Consider creating separate instances that are designated for each collection view.
DataSource.m
+(instancetype) sharedInstance{
static dispatch_once_t once;
static id sharedInstance;
dispatch_once(&once, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
- (void) fetchCoreData{
//set up fetch
self.fetchedObjectsC1 = [NSArray arrayWithArray:[self.fetchedResultsController fetchedObjects]];
self.fetchedObjectsC2 = [NSArray arrayWithArray:[self.fetchedResultsController fetchedObjects]];
}
Now you have two separate instances populated with the same data. One for C1 and one for C2. This is how you would call them in a different class.
[DataSource sharedInstance].fetchedObjectsC1;
[DataSource sharedInstance].fetchedObjectsC2;
Consider a scenario where you're trying to populate two separate tableviews/collection views with the same instance of NSArray from your data source. When you start to implement canEditRowAtIndexPath: and other methods that manipulate your table view's data, you're also going to effect the functionality of the other collection view. Especially if you're saving/fetching from core data throughout this process. This will often result in a crash.
Based on the info that I have this is the best I could do, let me know if this doesn't work for you, and we'll work something else out. Don't forget to add your singleton to the data source and make sure its public.
I need "Two different heights for cells in a table inheriting from the same UITableViewCell".
A bit more on this. I am using iOS 7 and storyboard. I created on the story board two different UITableViewCell prototype for a UITableView with custom cells.
I then created a class, MyUITableViewCell which defines the beheaviour of the cell as well as a protocol method that is then implemented by the delegate (which is in my case is the UITableViewController class where the UITableView containing the cells is).
Now.. I would like to dynamically set the row of the cells according to whether the cells is of type 1 or type 2.
I have found this method:
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
// if(indexPath) refers to cell of type 1 then
// return 240;
// else return 120;
// I created a NSDictionary with data that includes the type of cell information, however I am not sure how to link indexPath to the dictionary. The keys of the dictionary are ids and not array indexes.. so I am a bit lost in here..
}
As said in the comment:
"I created a NSDictionary with data that includes the type of cell information, however I am not sure how to link indexPath to the dictionary. The keys of the dictionary are ids and not array indexes.. so I am a bit lost in here.."
I would like to find a solution to this but not sure if it is possible using only NSDictionary or if I need some other hack...
Sure you can do that. The cells in your table view are data backed, you must have access to the data that defines the table in the table view delegate.
If your table is simple with only one section then you might only need to look at indexPath.row. There must be some way to relate this value to your data, if there is not you will not be able to populate the cell in cellForRowAtIndexPath either.
Often the indexPath.row value can be used as an index into an array containing data, such as [tableDataArray objectAtIndex:indexPath.row]; In your case, if the data is a dictionary, what are the keys? Maybe you can make a key directly from the row ([NSNumber numberWithInteger:indexPath.row], since a key must be an object) or maybe you need to use an array to translate the NSNumber produced from the index paths into the keys that are used in your dictionary. If your data is pre-existing and it would be a lot of work to change it this might be the best way, otherwise think about organising your data for easy access.
An alternative would be to use a UICollectionView, where with a custom UICollectionViewLayout you can define the frame of every cell individually at the time you report attributes for the cell. However you still need a way to relate index path to the underlying data. It is more versatile than a UITableView and is possibly a more useful skill to develop with the state of iOS development today.
So I'm using the required protocol methods from UITableViewDataSource to display rows on my application. It's working fine and everything but the book I'm learning from doesn't show me exactly how these rows are being updated.
In the method below there is no for loop so I'm wondering if the updating of the rows is done in a for loop in the background or something?
If not is it just updating all rows at once? So let's say in the other required method tableView:numberOfRowsInSection: I'm returning an int with the value of 5. Doe's the method below just take that info and say ok you have 5 rows I'll set the textLabel text for each of them in one go?
I'd like to get a deeper understanding of this.
Code:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// Create an instance of UITableViewCell, with the default appearance/style and name
// a reuseIdentify, used to identify cells with the same content
UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"UITableViewCell"];
// Use the index path row number to grab the BNRItem out of the allItems an
BNRItem *p = [[[BNRItemStore sharedStore] allItems] objectAtIndex:[indexPath row]];
// Set text label of cell using the BNRItem stored in "P".
[[cell textLabel] setText:[p description]];
return cell;
}
Kind regards
The method is called by the table view whenever it feels it needs to update a cell. So, if you have five rows on screen, it will call it for those five rows (and maybe a couple of extra). Then, as you scroll, it will call it for rows that are about to appear on the screen. Cells that have scrolled off screen will be added to the reuse pool, limiting the total number of sub views that need to be created fresh and added to the table.
This is a common pattern in Cocoa; to adapt an existing control (like a table), you wouldn't subclass it, you'd configure a separate object (in this case, the datasource) which implements various methods. When the control needs to know something, it calls the relevant method on your specially configured object. It's basically the delegate pattern, except a table view already has a delegate, and the data source does a slightly different job.
By the look of your sample code you're using the Big Nerd Ranch book - their Mac OS X book had one of the best descriptions of subclassing versus delegation that I've read. Something like:
Robocop is a subclassed human. Every part has been replaced with a custom one. Michael Knight uses a powerful delegate object (KITT) instead.
Which do you think is the more lightweight and flexible design?
jrturton covered the answer pretty well.
I would like to add some thoughts.
You are thinking procedurally: Write a for-loop that fills your UI with content from an array.
iOS is an event-driven OS, and Objective C is an object-oriented language. Events happen in the OS, and in response messages get sent to objets.
A table view is an autonomous object that does things on it's own. When a table view is added to the current screen's view hierarchy, it wakes up and figures out what content to display by asking the data source how many sections of data it has, and how many rows per section. It also asks how tall each cell should be. Once it has that information, it decides which items it should display, and asks the data source for cells to display.
If the user scrolls the table view it will recycle cells as they go off-screen and ask the data source to configure new cells for newly exposed data.
I have a question regarding a design convention.. See I have this tableview filled with editable information. Editable as in changing the text in the right detail of the cell, not as in deleting or moving a cell. I wonder how to design/model this, the only Apple product that I know has this feature is the contacts app. The solution there is to make lots and lots of groups, but this does not fit my problem at all, partly since I already have groups. The simplest way would just be to have the right detail be a text field and enable it in edit mode, but that would of course be a stupid solution since no visual feedback is given..
Any ideas on how to design/model this, or how Apple would like to have it?
EDIT:
To be more clear in what I mean, this is a screenshot explaining what I have said. Once again, my problem is how to show the table cells when they are in edit mode. All values are changeable, and none of them have any kind of presets to choose from, they are all based on text written by the user. This part of the app is basically a CRM system, where you can edit all the information about yourself.
What you are trying to do is pretty standard for Dynamic Prototype cells (as opposed to the Static Cells you probably used to make that screenshot). You're best bet is probably going to be to just watch a couple of these tutorials.
To give a quick summary, you're going to put all the strings you want to show in the Value part of your screenshot into an NSArray. There are UITableViewDelegate and UITableViewDataSource methods that will automatically handle putting the ith item in the array into the ith cell (so the 3 item in the array will go into the 3rd cell, etc.). To allow the user to edit things, you're going to want to make a whole new second screen (this is where you're UITextfields will be). When the user goes back from the second screen to your table screen, you replace items in the array with whatever the user entered in the UITextfield, then tell the UITableViewDelegate methods to reload the table with the new Values.
Most of the tutorials I linked to probably aren't going to have anything about having multiple Groups, so I'll add a bit on that here (what follows will probably only make sense if you watch the tutorials first, so I'd suggest following along with the tutorials, then coming back here afterward and making the changes I'm about to suggest). The NSIndexPath that is sent to the tableView:cellForRowAtIndexPath method contains information on the cell row and section. Each "Group" is it's own section (I'm really not sure why Apple decided to use two different names for the same thing, but so it goes). The easiest way to do this is to have different arrays for each section (so you'll have lines for NSMutableArray *firstSectionArray = [[NSMutableArray alloc] init];, NSMutableArray *secondSectionArray = [[NSMutableArray alloc] init];, and so on). Then, at the very top of your tableView:cellForRowAtIndexPath method, you put some if statements in to see what section of the table you are "building", and use values from the correct array accordingly. Something like:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
if (indexPath.section == 0)
{
// bunch of stuff from the tutorials here to create "cell"
[cell.detailTextLabel.text = firstSectionArray objectAtIndex:indexPath.row];
// bunch more stuff
}
else if (indexPath.section == 1)
{
// bunch of stuff from the tutorials here to create "cell"
[cell.detailTextLabel.text = secondSectionArray objectAtIndex:indexPath.row];
// bunch more stuff
}
// else if (keep going for however many sections you have)
return cell;
}
I have a UiSearchBar implemented in my TableView, and I also have two NSArrays, one for title and one for description. When I search through the array of the titles, it returns the right search, but when I click on a row that the search came with, I get "row 0" if I click on the first row. My question is how to make a connection between the two arrays so that when the search rearranges the titles based on the user search, the description array corresponds to the same row the title is at.
Simply do not use two NSArrays, but just one with custom NSObject objects:
#interface SomeObject : NSObject {
NSString *_title;
NSString *_description;
}
- (BOOL)matchesKeywords:(NSString *)keywords;
#end
Then you have all your information stored in one class, the way Obj-C is meant to be. You can easily perform the search because the objects itself sort of knows whether it matches a given keyword, so when you'd like to change SomeObject you can easily manage those changes in the class itself.
I did merge the two arrays into one, but that made the tableviewcell load alot slower because the cell is holding both, the title and description
I had this problem once So for the quick fix I did this:
In the header:
BOOL usingFilterArray;
Where you switch between the complete dictionary and the filtered one simply set the above BOOL to NO and YES respectively.
then in didSelectRowAtIndexPath use "if" statement to check the sate of the usingFilterArray.
Rest should be pretty easy. (Let me know if you still need help)
Just one thing when you perform the search after the filter Dictionary is hydrated if you cancel the search you need to make sure to run this or your app is going to crash as the hydrated dictionary will not have any object in it. (I assumed you cleaned the filtered Dictionary)
- (void) searchDisplayControllerDidEndSearch:(UISearchDisplayController *)controller {
[self.tableView reloadData];
}
Mate this is just a quick fix.