This website uses cookies to ensure you get the best possible experience. See our Cookies Policy.

PMG Digital Made for Humans

3 Different Ways to Sort a UITableView

9 MINUTE READ | April 17, 2012

3 Different Ways to Sort a UITableView

Have you ever wanted to be able to sort the data in your UITableView_s? Well lucky for you, that’s a nifty piece of_UITableView functionality that I recently learned how to achieve myself, and I thought I’d share that newly acquired knowledge with you. For the sake of this tutorial, let’s say you have a UITableView that you’ve populated with an NSArray of contacts.

You’ve created your own Contact data object, that can hold each contact’s full name, date of birth, and occupation, like so:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

// …in your Contact.h file…

@interface Contact : NSObject

@property (nonatomic, retain) NSString *firstName;

@property (nonatomic, retain) NSString *lastName;

@property (nonatomic, retain) NSDate *birthDate;

@property (nonatomic, retain) NSString *occupation;

@end

// …in your Contact.m file…

@implementation Contact

@synthesize firstName, lastName, birthDate, occupation;

@end

And you’ve filled up an NSArray with multiple Contact objects that you’re then using to populate your UITableView, displaying each contact’s full name, as well as their occupation and age:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

// …in your UITableViewController’s implementation file…

– (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section

{

    return [contacts count];

}

– (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath

{

    static NSString *CellIdentifier = @”Cell”;

    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];

    if (cell == nil) {

        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];

    }

    Contact *contact = [contacts objectAtIndex:indexPath.row];

    [cell.textLabel setText:[NSString stringWithFormat:@”%@ %@”, contact.firstName, contact.lastName]];

    NSString *age = [NSString stringWithFormat:@”%i”,[[[NSCalendar currentCalendar] components:NSYearCalendarUnit fromDate:[contact birthDate] toDate:[NSDate date] options:0] year]];

    [cell.detailTextLabel setText:[NSString stringWithFormat:@”%@ – %@”, contact.occupation, age]];

    return cell;

}

And let’s say you want to be able to sort the rows of your UITableView by either first name, last name, occupation, or age.

To begin, let’s create two UIBarButtonItem_s to handle the sort type and order. The first _UIBarButtonItem will contain a segmented control with two segments, “Asc” and “Desc”. The second one will be a standard button that will call up a UIActionSheet with the four different options for the type of sort.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

NSArray *sortItems = [NSArray arrayWithObjects:@”Asc”, @”Desc”, nil];

UISegmentedControl *sortOrder

sortOrder = [[UISegmentedControl alloc] initWithItems:];

[sortOrder setSelectedSegmentIndex:0];

[sortOrder setSegmentedControlStyle:UISegmentedControlStyleBar];

[sortOrder addTarget:self

              action:@selector(didPressSortOrder:)

    forControlEvents:UIControlEventValueChanged];

UIBarButtonItem *rightItem

rightItem = [[UIBarButtonItem alloc] initWithCustomView:sortOrder];

UIBarButtonItem *leftItem

leftItem = [[UIBarButtonItem alloc] initWithTitle:@”Sort”

                                            style:UIBarButtonItemStyleBordered

                                           target:self

                                           action:@selector(didPressSortType:)];

[self.navigationItem setLeftBarButtonItem:rightItem animated:NO];

[self.navigationItem setRightBarButtonItem:leftItem animated:NO];

[rightItem release];

[leftItem release];

That should give you a couple of nice UIBarButtonItems in your UINavigationBar, and should look something like this:

Now, we need to write out the implementation of the selector methods for each UIBarButtonItem. First, we’ll create an enum called SortType that we’ll use to determine which property of the contacts to sort by.

1

2

3

4

5

6

typedef enum {

    SortTypeFirstName = 0,

    SortTypeLastName = 1,

    SortTypeAge = 2,

    SortTypeRelationship = 3

} SortType;

Note: You don’t have to specify what each enum‘s actual integer value is, you could leave out the assignment entirely and their values would all be the same as what I set them to above.

We also need to create two variables, sortDescending, a BOOL we’ll use to determine which direction to sort in, and a variable of the enum type that we just created, SortType, that we can just call sortType. We’ll also make the declarations of the two methods that will be called by our _UIBarButtonItem_s.

1

2

3

4

5

6

7

8

9

10

11

12

@interface ViewController : UITableViewController

{

    BOOL sortDescending;

    SortType sortType;

    NSArray *contacts;

}

– (void)didPressSortOrder:(id)sender;

– (void)didPressSortType:(id)sender;

@end

Then we can write out the actual methods. The didPressSortOrder: method will be called whenever the selected segment of the UISegmentedControl changes. If the first index is selected we set the sortDescending BOOL tofalse, since we know that the first segment is set to “Asc”.

1

2

3

4

5

6

7

8

9

10

– (void)didPressSortOrder:(id)sender

{

    if ([(UISegmentedControl *)sender selectedSegmentIndex] == 0) {

        sortDescending = FALSE;

    } else {

        sortDescending = TRUE;

    }

    // do the logic to sort the UITableView

}

In the didPressSortType: method, we call up a basic UIActionSheet, with a title, the four different options (in the same order as our SortType enum values), and a cancel button. We also set our UITableViewController as thedelegate of the UIActionSheet, so that we can implement the actionSheet:didDismissWithButtonIndex: method. When the delegate method is called, we first check to make sure that buttonIndex is not equal to the index of the cancel button (in our case, 4), then we set the sortType to be equal to buttonIndex.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

– (void)didPressSortType:(id)sender

{

    UIActionSheet *actionSheet;

    actionSheet = [[UIActionSheet alloc] initWithTitle:@”Sort Contacts by”

                                              delegate:self

                                     cancelButtonTitle:@”Cancel”

                                destructiveButtonTitle:nil

                                     otherButtonTitles:@”First Name”,

                                                       @”Last Name”,

                                                       @”Age”,

                                                       @”Occupation”, nil];

    [actionSheet showInView:self.view];

    [actionSheet release];

}

– (void)actionSheet:(UIActionSheet *)actionSheet didDismissWithButtonIndex:(NSInteger)buttonIndex

{

    if (buttonIndex != 4) {

        sortType = buttonIndex;

        // do the logic to sort the UITableView

    }

}

Note: setting our enum sortType to the NSInteger buttonIndex only works properly in this instance, because we specifically set the order of our enum_s to be in the same order as the buttons in the _UIActionSheet;SortTypeFirstName is equal to 0, and the “First Name” button is at the buttonIndex of 0.

Now, if you run the app and tap on the “Sort” button, you should get a nice little UIActionSheet, like this:

Now, to do some actual sorting!

First off, I should explain that you can’t actually do any sorting on the UITableView itself. The sort has to happen on the NSArray that is being used to populate the table. So, first we’re going to go into our header file and add another NSArray for holding the sorted items, this is really just so that we never lose our original data array.

1

2

3

4

5

6

7

8

@interface ViewController : UITableViewController

{

    BOOL sortDescending;

    SortType sortType;

    NSArray *contacts;

    NSArray *sortedContacts;

}

Now, the first way I’m going to show you to sort your NSArray is by using the sortedArrayUsingSelector: method that can be called on any NSArray. To use it, the first thing we have to do, is implement a compare: method in our data object. So, let’s go to our Contact class.

1

2

3

4

5

6

7

8

// …in your Contact.h file…

– (NSComparisonResult)compareFirstName:(Contact *)otherObject;

// …in your Contact.m file…

– (NSComparisonResult)compareFirstName:(Contact *)otherObject

{

    return [self.firstName compare:otherObject.firstName];

}

Pretty simple, right? The compare: method that is being called will return an NSComparisonResult ofNSOrderedAscendingNSOrderedDescending, or NSOrderedSame. This will then be used by thesortedArrayUsingSelector: method to determine how to sort the objects in the NSArray. We’ll then call that method, like this:

1

2

sortedContacts = [[contacts sortedArrayUsingSelector:@selector(compareFirstName:)] retain];

[self.tableView reloadData];

We should then go through our UITableViewDataSource methods and change them to use the sortedContacts_array instead of the original _contacts array. Now, it should all work perfectly! You can now go through and write the other three methods for Last Name, Age, and Occupation.

Also, I created a separate method called sortTableView that I call whenever I want to sort the UITableView and use the enum sortType to determine which method to use when sorting.

Wait, selecting a different sort direction isn’t changing anything! Don’t worry, unfortunately that is just one of the drawbacks of using the sortedArrayUsingSelector: method. There’s no way to actually set the direction in which it should sort, it will always sort Ascending.

So, in order to allow for an actual sort direction, we’re going to switch to use a different method calledsortedArrayUsingDescriptors:. The sortedArrayUsingDescriptors: method is slightly similar to thesortedArrayUsingSelector: method in that it is called on the original NSArray and returns a sorted NSArray that we will then assign to a new NSArray. But, unlike the sortedArrayUsingSelector:, which used a @selector, which I’m sure you’re at least fairly used to seeing and working with in iOS, sortedArrayUsingDescriptors: uses something a bit more foreign, called an NSSortDescriptorNSSortDescriptor_s are fairly straight forward, they can hold an_NSString key, which is used to access a property inside of the objects it is going to sort. They also hold a BOOL_for determining the sort direction, as well as the ability to call a @selector on the sortable object, just like_sortedArrayUsingSelector: can.

I’ll just assume you wrote out a sortTableView method that your calling when you want to sort the table, and update the implementation of that method, like so:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

– (void)sortTableView

{

    if (sortedContacts) {

        [sortedContacts release];

    }

    NSString *key;

    switch (sortType) {

        case SortTypeFirstName:

            key = @”firstName”;

            break;

        case SortTypeLastName:

            key = @”lastName”;

            break;

        case SortTypeAge:

            key = @”birthDate”;

            break;

        case SortTypeRelationship:

            key = @”occupation”;

            break;

    }

    NSSortDescriptor *sortDescriptor;

    sortDescriptor = [[NSSortDescriptor alloc] initWithKey:key

                                                 ascending:!sortDescending];

    NSArray *sortDescriptors = [NSArray arrayWithObject:sortDescriptor];

    sortedContacts = [[contacts sortedArrayUsingDescriptors:sortDescriptors] retain];

    [sortDescriptor release];

    [self.tableView reloadData];

}

So, let’s break this code down a bit. First, I’m releasing the sortedContacts array if it’s been set, just for good memory management. Then, I create an NSString using a switch/case on the sortType enum to set its value to be the name of which ever property I want to sort by in our Contact object. Using the NSString and oursortDescending BOOL I create an NSSortDescriptor and then wrap it in an NSArray. Finally, I set oursortedContacts array to the result of the sortedArrayUsingDescriptors: method, which I call on the originalcontacts array. Lastly, I tell the UITableView to reload its data.

Voila! Now, you can sort your UITableView in any direction and by any element of your data object you want to!

Lastly, I’d like to show you one final way that you can perform a sort on an NSArray using a block.

Using a block for your sort actually works in almost the same way that the first method I showed you,sortedArrayUsingSelector:, does, except that thanks to blocks you don’t have to create specific methods on the data object itself, you can just inline them into the call to the sort method, which in this case issortedArrayUsingComparator:.

1

2

3

4

5

sortedContacts = [[contacts sortedArrayUsingComparator:^(id a, id b) {

    NSString *first = [(Contact *)a firstName];

    NSString *second = [(Contact *)b firstName];

    return [first compare:second];

}] retain];

That’s simple enough, right? Unfortunately, as with sortedArrayUsingSelector:, you can’t control the sort direction at all.

So, in most cases, I would suggest using the _NSSortDescriptor_s, it just gives you more customizability in your sort options. But, it’s always good to know different ways of accomplishing something, that way you can always know that your using the best possible solution for your specific problem.

Stay in touch

Bringing news to you

Subscribe to our newsletter

Interested in working with us? See our open engineering roles here.


Posted by Christopher Davis

Related Content

thumbnail image

Get Informed

PMG Innovation Challenge Inspires New Alli Technology Solutions

4 MINUTES READ | November 2, 2021

Get Informed

Applying Function Options to Domain Entities in Go

11 MINUTES READ | October 21, 2019

thumbnail image

Get Informed

My Experience Teaching Through Jupyter Notebooks

4 MINUTES READ | September 21, 2019

Get Informed

Trading Symfony’s Form Component for Data Transfer Objects

8 MINUTES READ | September 3, 2019

Get Inspired

Working with an Automation Mindset

5 MINUTES READ | August 22, 2019

Get Informed

Parsing Redshift Logs to Understand Data Usage

7 MINUTES READ | May 6, 2019

Get Inspired

3 Tips for Showing Value in the Tech You Build

5 MINUTES READ | April 24, 2019

thumbnail image

Get Informed

Testing React

13 MINUTES READ | March 12, 2019

Get Inspired

Tips for Designing & Testing Software Without a UX Specialist

4 MINUTES READ | March 6, 2019

Get Informed

A Beginner’s Experience with Terraform

4 MINUTES READ | December 20, 2018

ALL POSTS