I have a NSMutableArray when it is added to, removed from, or changes the notifications from the array update my UITableView such that those changes are reflected in the UITableView automatically using KVO observing. This works great.
We now a have a new requirement that we want to filter out certain items from the array. This just shows what kind of filtering I would like to do:
NSIndexSet *indexes = [array indexesOfObjectsPassingTest:
^BOOL(MyObject *obj, NSUInteger idx, BOOL *stop)
{
return !obj.isHidden;
}];
NSMutableArray *newArray = [[array objectsAtIndexes:indexes] mutableCopy];
However, doing the above does not work, I can't create a new array because it is being monitored by the UITableView, and I want to preserve the fine grained notifications that we get from the insertions and removals from the model array rather than reloading the entire table when a change occurs. So what I need is a way to only get notifications from newly inserted or removed items from the model that also meet the criteria.
So what, I think, I really want is two arrays, one that is the model, and another is the filtered presentation array. The presentation array will register for KVO notifications with the model array. The UITableView will register for notifications on the filtered presentation array. So if the insertion occurs in the model array it will send notification to the presentation array, then I need to check if it meets the criteria before inserting it in the presentation array or if it does not, then ignore the insertion. The problem I having is getting this to work correctly, the order of the items in the model is important and must be preserved. Any help or suggestions for alternative approaches would be greatly appreciated.
you have to maintain the model array and combinate the notification with the new array. So for example:
#property (nonatomic, strong) NSArray *modelArray;
//This array is also initialized obviously
#property (nonatomic, strong) NSArray *filteredArray;
//On this array you put the array filtered and connect this with the notification.
//So you can start each time from the model array and set the filtered array (and so
//change automatically the tableView).
Related
I have a NSmutableArray like this.
[obj1,obj2,obj3,obj5,......];
this has assigned into a UITableView
lets say user clicked on the 3rd cell. then I need to create another NSMutableArray like this.
[obj3,obj4,obj5,------]; need to remove first few objects from the array upto the current object. How can I do this.
please help me.
Thanks
you can use this...for remove specific object from any mutable array...
[array removeObjectAtIndex:0];
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.
I want to perform actions on selected cells starting from the top of the table, going down.
Does UITableView indexPathsForSelectedRows return its rows in order that the user has selected them, or if it is an unordered set? I didn't see this in the official documentation and need to know if I need to sort the returned array in some way before performing operations where the order is important.
Since it's not documented to return a sorted array, you should not rely on it being sorted even if it happens to be sorted in the current implementation. The order might change in future versions of iOS.
Since NSIndexPath implements compare:, sorting the array is trivial:
NSArray *sortedIndexPaths = [[tableView indexPathsforSelectedRows]
sortedArrayUsingSelector:#selector(compare:)];
I intend to have a UITableView filled with entries from an NSMutableDictionary. What I would like to do is when checking a cell, it creates an array which contains the key's objects.
The table view's cell.textLabel.text is equal to the key of each element of the dictionary, while the object associated with the key is an integer.
Let's say I tap one of the cells, and the object associated with it is 5722. I want to add that to the array, and check the cell as well. Also, tapping the cell again should remove the object associated with it and uncheck the cell. How do I go about doing this?
As of now, when scrolling, my checkmarks go everywhere.
I think that you are better of having your datasource as an NSArray instance. The list of keys provided by the allKeys method of the NSDictionary instance is not ordered and will change at any time. If you use an array, you can store the selected cells using an NSMutableIndexSet, and use this instance to get the records that have been selected from the array using the NSArray method objectsAtIndexes
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.