Is it possible to sort a UITableView section alphabetically?
I have an array of objects and in section 0 I have only one item from the array in this section. It could be any item in the array that I swap. section 1 will have the remainder of the objects in the array on display.
e.g.
A B C D
C A B D -- I want C to now be at section 0, the rest in alphabetical order in section 1
D A B C -- I want D to now be at section 0, the rest in alphabetical order in section 1
I've tried moving objects around, sorting and then moving but with no luck. Any suggestions would be great before I need to change it to a UIViewController with a Label and a UITableView below this label to simulate this.
You can sort the Object Array Alphabetically using sortUsingSelector (The array does need to be of type NSMutableArray rather than just a NSArray) and adding a method which returns an NSComparisonSort value in your Model Class. MyObject being the class of your Custom Object in this case. The sort is being done based on a letter property which is a NSString but could be something else.
// Just before wherever your Data Source is returned in your MyObject Class
[myArray sortUsingSelector:#selector(compareTo:)];
// Method which does the Comparison, also in your MyObject
- (NSComparisonResult)compareTo:(MyObject *)otherObject {
return [self.letter caseInsensitiveCompare:otherObject.letter];
}
Now to put the one object at Index 0, you could do the following. I assume you know the index of the object you want to take out...
MyObject *temp = [[myArray objectAtIndex:index] retain];
[myArray removeObject:temp];
[myArray insertObject:temp atIndex:0];
This preparation would be done before the Table View Delegate Methods are called. Possibly in your Table View Init or ViewWillLoad/ViewDidLoad
Update: Here is how to achieve the same Alphabetical sort using a SortDescriptor:
// This is setting up the Sort Descriptor for comparing the two objects based on the property 'letter'
NSSortDescriptor *sorter = [[[NSSortDescriptor alloc] initWithKey:#"letter" ascending:YES] autorelease];
NSArray *sortDescriptors = [NSArray arrayWithObject:sorter];
// This is the sorted array returned based on the property 'letter'
NSArray *sorted = [myArray sortedArrayUsingDescriptors:sortDescriptors];
Related
I have an NSMutableArray 'myArray' which contained some customObject 'A', Now I want to convert 'myArray' to tempArray which contained some object which subclass 'A' named as 'B'.
As a property just like follow:
NSMutableArray <B *> tempArray;
tempArray = [myArray mutableCopy];
But the object in tempArray always is kind of 'A'. I want convert 'A' to 'B', Any suggestion?
'B' has more property which i want to use.
How do you expect this to work? B is a child of A, it contains more information (the property you wish to use etc.). Where would the data for that property come from?
You need a way to construct a new instance of B using (the properties of) an instance of A and supplying the extra data a B needs. B might already have a suitable init method, or you may need to write your own code.
Once you have a way to produce a new B you can just iterate over your array building a new one, building a new B instance for each A instance.
HTH
If you have an immutable array (NSArray) that means you can't add or remove objects, or replace objects with other objects. [myArray mutableCopy] creates an NSMutableArray. It will contain exactly the same objects as the immutable array, but you are now free to add or remove or replace objects.
Your declaration NSMutableArray * tempArray does nothing but lie to the compiler. If myArray contained objects of type A*, then tempArray contains the same objects of type A*. You are just lying, so the compiler believes they are objects of type B*, but they are not.
There is no way on earth how a compiler could automatically convert an object of some class to an object of a subclass. If the subclass has additional members, for example, how is the compiler going to fill those members? This just cannot work.
If you want to create new objects of type B*, based on objects of type A*, you have to do that by hand, likely using an initialiser like
- (instancetype)initWithA:(A*)a;
in your B interface. Your code could be for example
NSMutableArray <B*> *tempArray = [myArray mutableCopy];
for (NSUInteger i = 0; i < tempArray.count; ++i) {
tempArray [i] = [[B alloc] initWithA:tempArray [i]];
}
or possibly
NSMutableArray <B*> *tempArray = [[NSMutableArray alloc] init];
for (A* a in myArray)
[tempArray addObject:[[B alloc] initWithA:a]];
I have an array - placeObjectsArray, that hold a lot of objects called place. Place is object of class PlaceHolder, in which i create different properties, filled with data:
self.place = [[PlaceHolder alloc]init];
// A lot of code here during parson XML with data
[self.placeObjectsArray addObject:self.place];
Header of this file look like this:
#interface PlaceHolder : NSObject
#property (strong, nonatomic) NSString *name;
#property (strong, nonatomic) NSString *description;
#property (strong, nonatomic) NSString *webPage;
It actually a container for an entity, each one hold data for name, description, image links etc. At now, i have array with place objects. What i want to, to manipulate with that objects inside an array. For example, how could i find all of data for specific "name"? (Name is one of properties in PlaceHolder class). How could i make an array that contain only names? How could i see in console 10 random "description"?
Any advice would be appreciated, thanks!
You're asking a bunch of separate questions.
First, how to select items in your array that match a particular name: Create an NSPredicate and use filteredArrayUsingPredicate. Google NSPredicate and you should find lots of examples.
Alternately you could use indexesOfObjectsPassingTest to get an index set of the items in the array that match your search criteria, and then use objectsAtIndexes: to turn the index set into a sub-array.
As for how to get all the names from the entries in your array, you can use a very cool trick in key value coding.
If you send an array the valueForKey message, it tries to fetch an item from each entry in the array using that key, and return them all in a new array. The code would look like this:
NSArray *names = [placeObjectsArray valueForKey #"name"];
Fetching 10 random descriptions is a little more complicated. You would need to write code that loops through the array, selecting 10 random items, and appends the description of each one into a new mutable array.
The trick there is to use arc4random_uniform to get a random index in your array:
NSUInteger random_index = arc4random_uniform(placeObjectsArray.count);
I leave the rest to you as a learning exercise.
If you want to fetch 10 random descriptions and make sure you never fetch the same description twice it's more complicated. You need to create a mutable copy of your array, then loop through the copy, fetching a random item, adding it's description to an array, and deleting the item from the array.
You can use NSPredicates:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"SELF.name LIKE[cd] %#", nameSearch];
NSArray *filtered = [self.placeObjectsArray filteredArrayUsingPredicate:predicate];
You could iterate over your array looking for the PlaceHolder with a given name, like:
PlaceHolder *namedPlaceholder = nil;
for (PlaceHolder *placeholder in theArray) {
if ([placeholder.name isEqualToString:"whateverName"]) {
namedPlaceholder = placeholder;
break;
}
}
If you want to find PlaceHolders by name efficiently you might consider using a dictionary instead of an array. With a dictionary you can map names to objects, like:
NSMutableDictionary *myDictionary = [[NSMutableDictionary alloc] init];
myDictionary[#"foo"] = somePlaceholder;
myDictionary[#"bar"] = someOtherPlaceholder;
and retrieve them like:
PlaceHolder *somePlaceholder = myDictionary[#"foo"];
To get random objects from an array, I recommend getting random indexes using arc4random_uniform. This gives pseudo-random numbers with a better uniform distribution than rand or random, and does not require you to explicitly seed the sequence with srand or srandom.
PlaceHolder *randomPlaceholder = theArray[arc4random_uniform(theArray.count)];
or
const NSUInteger arrayCount = theArray.count;
for (NSUInteger j = 0; j < 10; j++) {
PlaceHolder *randomPlaceholder = theArray[arc4random_uniform(arrayCount)];
// Do something with randomPlaceholder.
}
I have an NSFetchResultsController which returns ManagedObjects arranged by sections.
The sections are the NSString name of the Category objects which my Feed objects share in common:
Now, in some situations I want to obtain the Category itself from the section:
NSString *sortKey = #"category.name";
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:#"Feed"];
NSSortDescriptor *sortDescriptorCategory = [NSSortDescriptor sortDescriptorWithKey:sortKey ascending:asc];
At this moment, I do this:
Category *cat = ((Feed *)[[[self.fetchedResultsController sections][section] objects] objectAtIndex:0]).category;
Which is IMHO quite ugly. Especially as it forbids me to have empty sections which however might happen if I create new sections into which I want to move existing Feeds to.
So my question is : how do I access the Category object which defines the sections in my Feed list?
Also, how could I efficiently gather a list of all the sections in an NSSet?
Thanks in advance for your help.
Your method to retrieve the section object is OK, you should just include sanity checks.
NSArray *feeds = [self.fetchedResultsController.sections[section] objects];
Feed *aFeed = [feeds anyObject];
return aFeed ? aFeed.category : nil;
A list of all sections as a NSSet:
[NSSet setWithArray:self.fetchedResultsController.sections];
If you want to use the category entity extensively in your section headers, maybe it is better to change your setup: fetch the Categories rather than the feeds, and change the table view datasource methods to reflect this setup, e.g.
// number of rows for section
Category *category = self.fetchedResultsController.fetchedObjects[section];
return category.feeds.count;
You will need to introduce a sorting criterion to change the feeds set into an array for the rows of a section.
and thanks for looking...
I'm not even sure how to phrase this question, let alone search for the answer... I have tried, honestly, so all help needed!
It's probably pretty simple as I am sure this is a pattern that happens all the time:
I have an entity in my model (MainLevel*) that has a relationship to itself.
The entity is for levels of a law, and the only requirement is that each law has at least one (the top) level. Beyond that the number of sublevels is, technically, infinite (but in reality about 5 normally and probably no more than 8-10 at most). As might be expected each child level has only one parent (MainLevel.parentLevel) and any parent can have multiple (or zero) children (NSSet *childLevels).
What I would like to do is to get all the structure of this relationship to put in a UITableView or a collectionView.
I have a recursive function as follows:
- (NSDictionary *)getStructureOfLawForLevel:(MainLevel *)level
{
NSMutableDictionary *mutableDict = [[NSMutableDictionary alloc]initWithCapacity:50];
MainLevel *currentLevel = level;
[mutableDict setObject:currentLevel.shortName forKey:#"name"];
if (level.childLevels) {
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:#"order" ascending:YES];
NSArray *sortdescriptors = #[sortDescriptor];
NSArray *children = [currentLevel.childLevels sortedArrayUsingDescriptors:sortdescriptors];
for (MainLevel *childLevel in children)
{
NSDictionary *dict = [self getStructureOfLawForLevel:childLevel];
[mutableDict setObject:dict forKey:#"sublevels"];
}
}
return [mutableDict copy];
}
Then in viewWillAppear: I have this:
self.structure = [self getStructureOfLawForLevel:self.selectedLevel]
With this I hope I am on the right lines...(untested due to another issue I am sorting right now).
I still cant figure out how to configure a UITableView or a UICollectionView from this though. I mean I am sure I can do it by adding a counter or two and getting the number of lines, and sections, that way. It just seems way, way overcomplicated and I am certain there must be a more obvious method I am just not seeing...
The only criteria for the data is that it must be ordered by the .order attribute of the entity instance, and that is not unique. I mean, for example, each childLevel can have a childLevel with order number 1. It is the order in THAT parent level.
Sorry if this has been asked a thousand times. I have tried to search for an answer but nothing seems to fint the search terms I am using.
I am not doing anything with this data except putting on screen, no editing, adding, deleting... Not sure if that is relevant.
Edit for clarity...
I am not looking to do a drill-down type table view. I want a snapshot of the whole structure in one view, and then I may need to drill down from that (using relationships to other entities in the model).
EDIT FOR MY SOLUTION
Here's what I ended up doing...
- (NSArray *)getStructureAsArrayForLevel:(MainLevel *)child
{
NSMutableArray *thisChildAndChildren = [[NSMutableArray alloc]initWithCapacity:2];
[thisChildAndChildren addObject:child];
if (child.childLevels)
{
// children exist
// sort the children into order
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:#"order" ascending:YES];
NSArray *sortdescriptors = #[sortDescriptor];
NSArray *children = [child.childLevels sortedArrayUsingDescriptors:sortdescriptors];
// get an array for each child via recursion
for (MainLevel *child in children)
{
[thisChildAndChildren addObject:[self getStructureAsArrayForLevel:child]];
}
}
return [thisChildAndChildren copy];
}
Then I am using similar recursive function to convert the array to NSAttributedString and display in textView.
I really DO NOT like recursion. I don't know why but I find it SOOOOOOO hard to get my head around the logic, and when it's done it seems so obvious... Go figure!
Thanks to everyone for suggestions, help etc...
If you can use a 3rd-party controller, take a look at TLIndexPathTools. It handles tree structures. For example, try running the Outline example project.
Your view controller would look something like this (not much to it):
#import "TLTableViewController.h"
#interface TableViewController : TLTableViewController
#end
#import "TableViewController.h"
#import "TLIndexPathTreeItem.h"
#implementation TableViewController
- (void)viewDidLoad
{
[super viewDidLoad];
MainLevel *topLevel = nil;//get the top level object here
TLIndexPathTreeItem *topItem = [self treeItemForLevel:topLevel depth:0];
self.indexPathController.dataModel = [[TLTreeDataModel alloc] initWithTreeItems:#[topItem] collapsedNodeIdentifiers:nil];
}
- (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath
{
// customize cell configuration here
TLIndexPathTreeItem *item = [self.dataModel itemAtIndexPath:indexPath];
MainLevel *level = item.data;
cell.textLabel.text = [level description];
}
- (TLIndexPathTreeItem *)treeItemForLevel:(MainLevel *)level depth:(NSInteger)depth
{
NSMutableArray *childItems = [NSMutableArray arrayWithCapacity:50];
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:#"order" ascending:YES];
NSArray *sortdescriptors = #[sortDescriptor];
NSArray *children = [level.childLevels sortedArrayUsingDescriptors:sortdescriptors];
for (MainLevel *child in children) {
TLIndexPathTreeItem *childItem = [self treeItemForLevel:child depth:depth + 1];
[childItems addObject:childItem];
}
//set identifier to some unique identifier, if one exists. Otherwise, the item itself
//will be used as the identifier
id identifier = nil;
//set cell identifier based on depth. This can be set to something unique for each
//depth, or set to a constant value. If nil, the value "Cell" is assumed.
NSString *cellIdentifier = [NSString stringWithFormat:#"Cell%d", depth];
//pass "level" to the data argument. Or pass any other data, e.g. include the depth #[#(depth), level]
TLIndexPathTreeItem *item = [[TLIndexPathTreeItem alloc] initWithIdentifier:identifier sectionName:nil cellIdentifier:cellIdentifier data:level andChildItems:children];
return item;
}
#end
You can subclass TLTreeTableViewController instead of TLTableViewController if you want collapsable levels. Let me know if you need more help.
EDIT
Sorry, I missed the part that says you want to display it all at once. Basically, I think the easiest way to do this would be to basically have a recursive structure that gets the description of each object. This could be a string or even a UIView that you could then place inside your tableviewcell.
Lets stick with a dictionary for now. Each dictionary representation can have information about itself and its children. The template can be:
<LevelInfoDictionary>
<NSObject>someObjectThatRepresentsInfoAboutThisLevel
<NSArray>arrayOfInfoDictionariesThatRepresentChildren
</LevelInfoDictionary>
Then to implement your recursive method:
- (NSDictionary *)getLevelInfo
{
NSMutableArray *childInfo = [NSMutableArray array];
for(ClassName *child in self.children)
{
[childInfo addObject:[child getLevelInfo]];
}
return [NSDictionary dictionaryWithObjectsAndKeys:
<descriptionOfThisLevel>, #"CurrentLevel",
childInfo, #"children>, nil];
}
END EDIT
Basically, as some of these other guys have said, you should create your tableview that displays all of your top level objects. From there, after you select an object, you should be pushed to a new tableview that uses a different fetch request with the predicate like:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"parent = %#",
selectedParentObject];
Then you can use sort descriptors to sort the NSFetchRequest you are using.
Alternatively, you could just fetch the children by using the property on the parent object and store that in an array sorted by your sort descriptors.
One other thing that I should mention is that currently the sort descriptor does not accomplish anything. You may not notice this because there are other parts of the design that you should change, but since an NSDictionary does not have an order (it is a hash table), sorting objects before placing them in a dictionary does nothing.
From the ViewController for the TableView or CollectionView you should start by showing all of the top level objects (No parent). From there as a user selects an object that parent becomes the current level and the ViewController should refresh its data source to show all of the child elements at that level. You can then traverse back up to the parent via back button.
Let me know if you need any more detail.
Your loop doesn't make sense, because you want a list of the dictionaries for the structure of the children, but what you actually do it to overwrite it each time. You probably want something like:
NSMutableArray *subLevels = [NSMutableArray array];
for (MainLevel *childLevel in children)
{
[subLevels addObject:[self getStructureOfLawForLevel:childLevel]];
}
[mutableDict setObject:subLevels forKey:#"sublevels"];
I guess you want to show each level in a table view and drill to another table view for each subsequent level. That should be simple based on the dictionary which gives you a name to display and an optional array which defines whether drilling is possible and the data to pass to the next view controller.
In case it helps someone else, here's what I ended up doing...
- (NSArray *)getStructureAsArrayForLevel:(MainLevel *)child
{
NSMutableArray *thisChildAndChildren = [[NSMutableArray alloc]initWithCapacity:2];
[thisChildAndChildren addObject:child];
if (child.childLevels)
{
// children exist
// sort the children into order
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:#"order" ascending:YES];
NSArray *sortdescriptors = #[sortDescriptor];
NSArray *children = [child.childLevels sortedArrayUsingDescriptors:sortdescriptors];
// get an array for each child via recursion
for (MainLevel *child in children)
{
[thisChildAndChildren addObject:[self getStructureAsArrayForLevel:child]];
}
}
return [thisChildAndChildren copy];
}
Then I am using similar recursive function to convert the array to NSAttributedString and display in textView.
I really DO NOT like recursion. I don't know why but I find it SOOOOOOO hard to get my head around the logic, and when it's done it seems so obvious... Go figure!
Thanks to everyone for suggestions, help etc...
EDIT
It isn't exactly right, as the first layer has a slightly different structure to those that follow. at some point I need to change this to have the top level as a directory and use the very first level as the key for the directory, then add the complete array as the object for that key. But it works... my brain aches... and I can live with it until I get around to changing it.
All,
I have about 3000 words with definitions that I am loading into a TableView. Right now, it's just a sorted list of words, sans the sections because I haven't added them yet.
I need to add sections to my TableView data (A,B,C ...) and there seems to be several ways to do this so before I jump into this I am looking for some confirmation or correction if I am going down the wrong rabbit hole.
Currently the data that the TableView reads is stored as objects in an NSMutableArray per this code:
//AppDelegate.m
- (void)applicationDidFinishLaunching:(UIApplication *)application
{
//...
NSMutableArray *wordArray = [[NSMutableArray alloc] init];
//Loop through result set from DB and populate objects
while([rs next]){
[wordArray addObject:[Word wordWith:[rs stringForColumn:#"word"]
Definition:[rs stringForColumn:#"definition"]
SectionIndex:[rs stringForColumn:#"sectionIndex"]]];
}
MainViewController *mainViewController =
[[MainViewController alloc] initWithNibName:#"MainView" bundle:nil];
mainViewController.listContent = wordArray;
//...
}
Each object has a section index value ([A-Z0-9]) so I already know which section each word goes in, I know what the sections need to be and I can easily derive a count of objects for each section. All the words have been sorted via SQL before the NSMutableArray was populated so that's already handled.
Can I create multiple sections with the one NSMutableArray or do I need to do something different?
Thanks
You could store your words into arrays inside a NSDictionary holding keys for each letter.
Number of sections would return
[[dictionary allKeys] count];
Title for section
NSArray * keys = [dictionary allKeys];
[keys objectAtIndex:sectionIdx]
Number of rows in section would return
NSArray * keys = [dictionary allKeys];
[(NSArray *)[dictionary objectForKey:[keys objectAtIndex:sectionIdx]] count];
Each word would be
NSArray * keys = [dictionary allKeys];
[(NSArray *)[dictionary objectForKey:[keys objectAtIndex:sectionIdx]] objectAtIndex:indexPath.row];
I have found that you sometimes want to add sorting to your lists and then, another approach might be interesting. Put all your models (Word's in your example) in a dictionary with some unique value of the model as the key.
Implement a sorting method, that you run every time the underlying dictionary changes. The sorting method will use e.g. keysSortedByValueUsingComparator on the dictionary and supply a different blocks for different sort orders. Let the sorting method create section arrays and add keys in the arrays that corresponds to the keys in the dictionary.
You do not store anything twice and you get different sort orders by just providing different sort blocks (that can look at any properties of your model class).