Tagged: UITableView Toggle Comment Threads | Keyboard Shortcuts

  • Jay Versluis 11:39 pm on April 20, 2013 Permalink | Reply
    Tags: , UITableView   

    Categories: iOS ( 222 )

    How to add a Search Display Controller to a UITableView (in code) 

    We’ve recently discussed how to deal with a Search Bar and Search Display Controller using Interface Builder. You can however do this in code too. This can be useful if you don’t want to make all the relevant connections in every Storyboard.

    This example assumes you have a Table View (self.tableView) to which you’d like to add a Search Bar and Search Display Controller at the top. We do this by utilising the Table View’s tableHeaderView property:

    // create a new Search Bar and add it to the table view
    UISearchBar *searchBar = [[UISearchBar alloc]initWithFrame:CGRectMake(0.0f, 0.0f, 320.0f, 44.0f)];
    self.tableView.tableHeaderView = searchBar;
            
    // we need to be the delegate so the cancel button works
    searchBar.delegate = self;
            
    // create the Search Display Controller with the above Search Bar
    self.controller = [[UISearchDisplayController alloc]initWithSearchBar:self.searchBar contentsController:self];
    self.controller.searchResultsDataSource = self;
    self.controller.searchResultsDelegate = self;
    

    Your View Controller also needs to conform to the UISearchBarDelegate Protocol for this to work properly.





     
  • Jay Versluis 12:27 am on March 7, 2013 Permalink | Reply
    Tags: , UITableView   

    Categories: iOS ( 222 )

    How to create an NSIndexPath and specify its components 

    On occasion we need to create an NSIndexPath manually, with components we specify (such as a row or a section). There’s a method for that: indexPath:forItem:inSection.

    Here’s how you create an indexPath for row 0, section 0:

    NSIndexPath *indexPath = [NSIndexPath indexPathForItem:0 inSection:0];
    

    The method takes two integers. You can even take an existing indexPath, then add or subtract values to the new indexPath, like so:

    // current indexPath
    NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
    
    // make a new indexPath and add 1 to the row of the previous one
    NSIndexPath *indexPath2 = [NSIndexPath indexPathForItem:(indexPath.row + 1) inSection:indexPath.section];
    




     
    • Bob Easterday 11:31 am on February 24, 2015 Permalink | Reply

      You are effectively allocating indexPath twice. The convenience method will return an allocated object. the above can be written simply as;

      NSIndexPath *indexPath = [NSIndexPath indexPathForItem:0 inSection:0];

      • Jay Versluis 12:08 pm on February 24, 2015 Permalink | Reply

        Thanks Bob, very good point! I’ll change the article.

  • Jay Versluis 1:22 pm on March 3, 2013 Permalink | Reply
    Tags: UITableView,   

    Categories: iOS ( 222 )

    How to dequeue UITableViewCells 

    Sometimes Xcode does a bit of magic behind the scenes which I don’t really understand.

    Consider this standard table view method to display cells:

    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
    {
        UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell"];
    
        // populate your cell
        
        return cell;
    }
    

    Where do we actually ever create a UITableViewCell? All we’re asking here is “do we have reusable ones available” – but if the answer is “no”, then somehow these get created without an error message. Other times (such as when using an overlay table view courtesy of the search display controller) we get an error message.

    This brings me to the point that perhaps Xcode and iOS create as well as dequeue cells automatically – but when they don’t, here’s how we can do this manually.

    Let’s amend the above code snippet:

    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
    {
        UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell"];
        
        if (cell == nil) {
            cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"Cell"];
        }
    
        // populate your cell
        
        return cell;
    }
    

    Now we properly create a cell should a dequeueable one not be available.

    I haven’t had to use this, and the if statement never seems to get called if inserted into the Master/Detail template, yet I find this may be something to keep in mind for future projects.





     
  • Jay Versluis 2:23 pm on March 2, 2013 Permalink | Reply
    Tags: , UITableView   

    Categories: iOS ( 222 )

    How to hide (and show) the UISearchBar in a UITableView 

    If you add a UISearchBar to your table view (see previous article) it’s just sitting there by default. Chances are you’d like to hide it when your table view first loads.

    You can do this by adding the following code to your viewWillAppear method:

        - (void)viewWillAppear:(BOOL)animated {
    
        // scroll the search bar off-screen
        CGRect newBounds = self.tableView.bounds;
        newBounds.origin.y = newBounds.origin.y + self.searchBar.bounds.size.height;
        self.tableView.bounds = newBounds;
    
    }

     

    Now it’s hidden, users make a search, hit cancel… and then it comes back into view. Wouldn’t it be nice if we could hide it again after users are finished with their search? And sure we can! We’re already conforming to the UISearchBarDelegate protocol, so all we need to do is to implement the following method and react to the cancel button:

     

    - (void)searchBarCancelButtonClicked:(UISearchBar *)searchBar {
    
        [self viewWillAppear:YES];
    
    }

    We could implement the same code as above, or just call the viewWillAppear method again.

    Note that you have to set the search bar’s delegate to your class for this to work. Searches are working fine when it’s not set, but to react to the cancel button it needs to be set (either in code or via the Connections Inspector in Interface Builder).

    You can find a full demo project on GitHub: Table Search 2013 (updated for iOS 7)





     
  • Jay Versluis 12:02 am on March 1, 2013 Permalink | Reply
    Tags: search, UISeachDisplayController, , UITableView   

    Categories: iOS ( 222 )

    How to create a searchable UITableView 

    The ingredients for a search function in a UITableView are more involved than just displaying a simple search field. First we need the standard UITableView. Next we need a UISearchBar which can be added to the top of the table view. But displaying the actual search results is something called the UISearchDisplayController.

    This process isn’t very well documented from what I could find, so here’s how I did it successfully. Works fine in iOS 6.

    For iOS 5 compatibility please see my comment at the end.

    This new controller “switches out” the standard table view and overlays a new one (the search display). Since the data for the search view is more or less the same as what was already powering the original table view (only “filtered”), the search view often uses the same data source as the original table view.

    Speaking of filtering the original data: this is done with something called a predicate (NSPredicate). First time I’ve heard of this was in regards to Core Data. A predicate is a rather powerful yet mysterious thing and rest assured you don’t need to understand it in order to use it.

    Let’s go through this step by step: from creating a standard table view with some dummy data, then adding a search function and slowly making it work.

     

    Creating the Table View

    I’ll start with a Single View Application project (with Storyboards and ARC selected), removing the single View Controller from the Storyboard. Delete both ViewController h and m files too.

    Next we’ll drag a UITableViewController into the Storyboard. Select the cell and set its reuse identifier to “Cell”. Next we’ll create a new Cocoa Touch Objective-C Class called TableViewController, naturally being a sub class of UITableViewController. Before we do anything else, let’s set this custom class in our Storyboard.

    We’ll also create two properties in our h file:

    • an NSArray called allData, holding all our data
    • an NSMutableArray called searchResults which will hold our filtered results

    Before we forget, let’s initialize the latter in our viewDidLoad method:

    self.searchResults = [[NSMutableArray alloc]init];

    Creating some Dummy Data

    Here’s a small method that initializes our data: 24 written out numbers. This could be anything really:

    - (void)createData {
    
        self.allData = [[NSArray alloc]initWithObjects:@"One", @"Two", @"Three", @"Four", @"Five", @"Six",
                        @"Seven", @"Eight", @"Nine", @"Ten", @"Eleven", @"Twelve",
                        @"Thirteen", @"Fourteen", @"Fifteen", @"Sixteen", @"Seventeen", @"Eighteen",
                        @"Ninteteen", @"Twenty", @"Twentyone", @"Twentytwo", @"Twentythree", @"Twentyfour",
                        nil];
    }

    Let’s call this method in our viewDidLoad so it’s ready for use as soon as the app starts:

    [self createData];

    Populating the Table View

    Let’s make our table view show those items: take out the two #warnings and all commented code to make our class a bit more readable. Next set the tableView:numberOfSections to 1 as we wont be dealing with sections here.

    The tableView:numberOfRowsInSection needs to be the amount of our allData array, so we’ll make it look like this:

    - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
    {
        // Return the number of rows in the section.
            return self.allData.count;
    }

    And the final method we need to amend is the tableview:cellForRowAtIndexPath method so that text gets displayed in each cell:

    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
    {
        static NSString *CellIdentifier = @"Cell";
        UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    
            // Configure the cell...
            cell.textLabel.text = [self.allData objectAtIndex:indexPath.row];
    
        return cell;
    }

    Note that since Xcode 4.6 the template for a new Table View class has changed to incorporate an extremely annoying addition: right after the line with dequeueReusableCellWithIdentifier, we find “forIndexPath:indexPath”. This makes your app crash in iOS 5 and sometimes in iOS 6. Remove it to reflect my code above – you’ll avoid a lot of hair pulling later!

    Run the app to see all our values listed as expected.

    We’re not dealing with selecting them, but we’ll see next how we can make the view change into search mode.

    Bring on the big Search Bar

    This is where things get interesting: back in the storyboard, drag a UISearchDisplayController to the very top of your table view, so that it sits above the dummy cell. Switch to Assistant Editor mode and control drag a reference into your h file so we can address this thing.

    Notice that when you do this you will only be able to create an outlet to a UISearchBar – that’s fine, it’s part of the UISearchDisplayController. Call it searchBar.

    Setting up our Predicate

    Back in the TableViewController.m file we need to add a couple of helper methods. The first one is a method called filterData. We will call this repeatedly so a small method will work best for us:

    - (void)filterData {
    
        [self.searchResults removeAllObjects];
        NSPredicate *predicate = [NSPredicate predicateWithFormat:@"self CONTAINS [cd] %@", self.searchBar.text];
        self.searchResults = [[self.allData filteredArrayUsingPredicate:predicate] mutableCopy];
    
    }

    This method will take what’s in our search bar and compare it to our allData array. Matching items are placed into our searchResults array (which we clear out at the beginning of this method). The [cd] statement means we don’t want to know about caSe sEnSitiVity or “diacritics” which are things like the Umlaut Dots above a letter and such.

    The great thing for us is that after calling this method, we know how many matches we have and hence how many cells we need to display. We want to call this method every time someone adds a single letter to our search bar. And in order to know when that is, we better conform to the

    UISearchBar Delegate Protocol

    If we’re the Search Bar’s delegate, then we can implement a method that tells us when our search text changed. This is what we want! Head over to your TableViewController.h file and add the protocol declaration to the top:

    @interface TableViewController : UITableViewController <UISearchBarDelegate>

    Back in the m file add this method, which in turn will call our filterData method:

    - (void) searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText {
    
        [self filterData];
    }

    There’s no need to setup the delegate; since we’ve implemented a Search Display Controller, this has been done for us already.

    Dealing with the Search Display Controller

    This controller is rather clever: rather than bringing up a modal view or anything visually disruptive, the Search Display Controller “overlays” his own table view above out existing one, dimming the latter out. It’s slick alright – but therefore also a tad confusing. Because out of a sudden, we have TWO table views to deal with.

    So when we tell the usual data source methods how many sections, rows and cells we need to display, we need to do so via an if-then statement so see which table view is requesting the data.

    We’ll start with the tableView:numberOfRowsInSection method:

    - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
    {
        // Return the number of rows in the section.
        if (tableView == self.tableView) {
            return self.allData.count;
        }
        [self filterData];
        return self.searchResults.count;
    }

    Here we ask: if it’s the “normal” table view, show the number of our allData array. If that’s not the case, we’ll be asked by the Search Display Controller. In which case, call the filterData method, then see how many items are, and then count those.

    We need to do the same for our cellForRowAtIndexPath method:

    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
    {
        static NSString *CellIdentifier = @"Cell";
        UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    
        if (!cell) {
            cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"Cell"];
        }
    
        if (tableView == self.tableView) {
            // Configure the cell...
            cell.textLabel.text = [self.allData objectAtIndex:indexPath.row];
        } else {  
            cell.textLabel.text = [self.searchResults objectAtIndex:indexPath.row];
        }    
        return cell;
    }

    Again if the “standard” table view is asking, display an object from the allData. If it’s the other guy, display it from searchResults (by which time we would have just called the filterData method so we’re not calling it again).

    One other point of note here is the if(!cell) statement:

    Leaving this out makes the table view crash as soon as you hit a letter. For some reason cells don’t automatically create themselves as needed, which works fine in a standard table view. So if there aren’t any, we just create as many as needed.

    Full Project Code

    You can get the entire project on my repository on GitHub: Table Search 2013

     

    Further Reading





     
  • Jay Versluis 7:15 am on January 18, 2013 Permalink | Reply
    Tags: UITableView,   

    Categories: iOS ( 222 )

    iOS 6 Table View crashes when deployed to iOS 5 

    Apple have changed the UITableViewController template in iOS 6 a bit. Specifically, when you create a new UITableViewController class, it’s created using something like this (in the cellForRowAtIndexPath method):

    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
    {
        static NSString *CellIdentifier = @"Cell";
        UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
        
        // Configure the cell...
        
        return cell;
    }
    

    When run in iOS 5 the app crashes. The culprit seems to be the addition of forIndexPath:indexPath in this declaration which is only available in iOS 6. To make it work in either iOS version, simply take it out, like so:

        UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier;
    

    The full code from an iOS 5 template is:

    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
    {
        static NSString *CellIdentifier = @"Cell";
        UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier;
        
        // Configure the cell...
        
        return cell;
    }
    




     
  • Jay Versluis 2:06 am on January 11, 2013 Permalink | Reply
    Tags: UITableView,   

    Categories: iOS ( 222 )

    How to change a cell’s selection colour 

    By default, when you touch a cell in a UITableView, it lights up bright blue. You can use this property to change the cell’s selection style in the cellForRowAtIndexPath method:

    cell.selectionStyle = UITableViewCellSelectionStyleGray;
    

    Possible values are

    • UITableViewCellSelectionStyleGray
    • UITableViewCellSelectionStyleBlue (the default)
    • UITableViewCellSelectionStyleNone

    Alternatively you can select the cell in Interface Builder and pick these values from the drop down menu:

    Screen Shot 2013-01-11 at 01.05.19





     
  • Jay Versluis 11:48 pm on December 27, 2012 Permalink | Reply
    Tags: UITableView,   

    Categories: iOS ( 222 )

    How to disable cell interaciton in UITableView 

    If you don’t want your cell to be highlighted you can de-select the option “User Interaction Enabled” in the storyboard. This is ticked by default. Untick it and nobody can select your cell anymore.

    UserInteraction





     
  • Jay Versluis 5:48 pm on December 27, 2012 Permalink | Reply
    Tags: UITableView,   

    Categories: iOS ( 222 )

    How to disable cell interaciton in UITableView 

    If you don’t want your cell to be highlighted you can de-select the option “User Interaction Enabled” in the storyboard. This is ticked by default. Untick it and nobody can select your cell anymore.

    UserInteraction





     
  • Jay Versluis 4:44 pm on December 27, 2012 Permalink | Reply
    Tags: cell, UITableView,   

    Categories: iOS ( 222 )

    How to create a transparent cell in a UITableView 

    First we make the cell’s background transparent. Next we create a custom view which we can show behind the cell, like so:

    cell.backgroundColor = [UIColor clearColor];
    UIView *backView = [[UIView alloc]initWithFrame:CGRectZero];
    backView.backgroundColor = [UIColor clearColor];
    cell.backgroundView = backView;
    




     
c
compose new post
j
next post/next comment
k
previous post/previous comment
r
reply
e
edit
o
show/hide comments
t
go to top
l
go to login
h
show/hide help
shift + esc
cancel