Skip to main content

TableSearch (Filtering)


TableSearch
===========

This sample demonstrates how to use the UISearchDisplayController object in conjunction with a UISearchBar, effectively filtering in and out the contents of that table. If an iOS application has large amounts of table data, this sample shows how to filter it down to a manageable amount so that users to scroll through less content in a table.

It shows how you can:
- Create a UISearchDisplayController.
- Use scopes on UISearchBar with a search display controller.
- Manage the interaction between the search display controller and a containing UINavigationController
    (there is no code for this -- the navigation bar is moved around as necessary).
- Return different results for the main table view and the search display controller's table view.
- Handle the destruction and re-creation of a search display controller when receiving a memory warning.

Using the Sample

Tap the search field and as you enter case insensitive text the list shinks/expands based on the filter text. An empty string will show the entire contents.  To get back the entire contents once you have filtered the content, touch the search bar again, tap the clear ('x') button and then tap cancel.


Main Classes
----------

APLViewController
Manages a table view to display a list of products, and manages a search bar to filter the product list.


APLProduct
A simple model file to represent a product with a name and type.


=======================================================




#import "APLViewController.h"
#import "APLProduct.h"


@interface APLViewController ()

/*
 The searchResults array contains the content filtered as a result of a search.
 */
@property (nonatomic) NSMutableArray *searchResults;

@end


@implementation APLViewController

#pragma mark - Lifecycle methods

- (void)viewDidLoad
{
    /*
     Create a mutable array to contain products for the search results table.
     */
    self.searchResults = [NSMutableArray arrayWithCapacity:[self.products count]];

    /*
     Set up the search scope buttons with titles using products' localized display names.
     */
    NSMutableArray *scopeButtonTitles = [[NSMutableArray alloc] init];
    [scopeButtonTitles addObject:NSLocalizedString(@"All", @"Title for the All button in the search display controller.")];

    for (NSString *deviceType in [APLProduct deviceTypeNames])
    {
        NSString *displayName = [APLProduct displayNameForType:deviceType];
        [scopeButtonTitles addObject:displayName];
    }

    self.searchDisplayController.searchBar.scopeButtonTitles = scopeButtonTitles;
}


-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    if ([segue.identifier isEqualToString:@"pushDetailView"])
    {
        // Sender is the table view cell.
        NSArray *sourceArray;
        NSIndexPath *indexPath = [self.searchDisplayController.searchResultsTableView indexPathForCell:(UITableViewCell *)sender];
        if (indexPath != nil)
        {
            sourceArray = self.searchResults;
        }
        else
        {
            indexPath = [self.tableView indexPathForCell:(UITableViewCell *)sender];
            sourceArray = self.products;
        }

        UIViewController *destinationController = segue.destinationViewController;
        APLProduct *product = sourceArray[indexPath.row];
        destinationController.title = product.name;
    }
}


#pragma mark - UITableView data source and delegate methods

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
      /*
       If the requesting table view is the search display controller's table view, return the count of
     the filtered list, otherwise return the count of the main list.
       */
      if (tableView == self.searchDisplayController.searchResultsTableView)
      {
        return [self.searchResults count];
    }
      else
      {
        return [self.products count];
    }
}


- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
      static NSString *kCellID = @"CellIdentifier";

    // Dequeue a cell from self's table view.
      UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:kCellID];

      /*
       If the requesting table view is the search display controller's table view, configure the cell using the search results array, otherwise use the product array.
       */
      APLProduct *product;
   
      if (tableView == self.searchDisplayController.searchResultsTableView)
      {
        product = [self.searchResults objectAtIndex:indexPath.row];
    }
      else
      {
        product = [self.products objectAtIndex:indexPath.row];
    }

      cell.textLabel.text = product.name;
      return cell;
}


#pragma mark - Content Filtering

- (void)updateFilteredContentForProductName:(NSString *)productName type:(NSString *)typeName
{
      /*
       Update the filtered array based on the search text and scope.
       */
    if ((productName == nil) || [productName length] == 0)
    {
        // If there is no search string and the scope is "All".
        if (typeName == nil)
        {
            self.searchResults = [self.products mutableCopy];
        }
        else
        {
            // If there is no search string and the scope is chosen.
            NSMutableArray *searchResults = [[NSMutableArray alloc] init];
            for (APLProduct *product in self.products)
            {
                if ([product.type isEqualToString:typeName])
                {
                    [searchResults addObject:product];
                }
            }
            self.searchResults = searchResults;
        }
        return;
    }

   
    [self.searchResults removeAllObjects]; // First clear the filtered array.
      /*
       Search the main list for products whose type matches the scope (if selected) and whose name matches searchText; add items that match to the filtered array.
       */
    for (APLProduct *product in self.products)
      {
            if ((typeName == nil) || [product.type isEqualToString:typeName])
            {
            NSUInteger searchOptions = NSCaseInsensitiveSearch | NSDiacriticInsensitiveSearch;
            NSRange productNameRange = NSMakeRange(0, product.name.length);
            NSRange foundRange = [product.name rangeOfString:productName options:searchOptions range:productNameRange];
            if (foundRange.length > 0)
                  {
                        [self.searchResults addObject:product];
            }
            }
      }
}


#pragma mark - UISearchDisplayController Delegate Methods

- (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString
{
    NSString *scope;

    NSInteger selectedScopeButtonIndex = [self.searchDisplayController.searchBar selectedScopeButtonIndex];
    if (selectedScopeButtonIndex > 0)
    {
        scope = [[APLProduct deviceTypeNames] objectAtIndex:(selectedScopeButtonIndex - 1)];
    }

    [self updateFilteredContentForProductName:searchString type:scope];

    // Return YES to cause the search result table view to be reloaded.
    return YES;
}


- (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchScope:(NSInteger)searchOption
{
    NSString *searchString = [self.searchDisplayController.searchBar text];
    NSString *scope;

    if (searchOption > 0)
    {
        scope = [[APLProduct deviceTypeNames] objectAtIndex:(searchOption - 1)];
    }

    [self updateFilteredContentForProductName:searchString type:scope];

    // Return YES to cause the search result table view to be reloaded.
    return YES;
}


#pragma mark - State restoration

static NSString *SearchDisplayControllerIsActiveKey = @"SearchDisplayControllerIsActiveKey";
static NSString *SearchBarScopeIndexKey = @"SearchBarScopeIndexKey";
static NSString *SearchBarTextKey = @"SearchBarTextKey";
static NSString *SearchBarIsFirstResponderKey = @"SearchBarIsFirstResponderKey";

static NSString *SearchDisplayControllerSelectedRowKey = @"SearchDisplayControllerSelectedRowKey";
static NSString *TableViewSelectedRowKey = @"TableViewSelectedRowKey";


- (void)encodeRestorableStateWithCoder:(NSCoder *)coder
{
    [super encodeRestorableStateWithCoder:coder];

    UISearchDisplayController *searchDisplayController = self.searchDisplayController;
   
    BOOL searchDisplayControllerIsActive = [searchDisplayController isActive];
    [coder encodeBool:searchDisplayControllerIsActive forKey:SearchDisplayControllerIsActiveKey];
   
    if (searchDisplayControllerIsActive)
    {
        [coder encodeObject:[searchDisplayController.searchBar text] forKey:SearchBarTextKey];
        [coder encodeInteger:[searchDisplayController.searchBar selectedScopeButtonIndex] forKey:SearchBarScopeIndexKey];

        NSIndexPath *selectedIndexPath = [searchDisplayController.searchResultsTableView indexPathForSelectedRow];
        if (selectedIndexPath != nil)
        {
            [coder encodeObject:selectedIndexPath forKey:SearchDisplayControllerSelectedRowKey];
        }

        BOOL searchFieldIsFirstResponder = [searchDisplayController.searchBar isFirstResponder];
        [coder encodeBool:searchFieldIsFirstResponder forKey:SearchBarIsFirstResponderKey];
    }

    NSIndexPath *selectedIndexPath = [self.tableView indexPathForSelectedRow];
    if (selectedIndexPath != nil)
    {
        [coder encodeObject:selectedIndexPath forKey:TableViewSelectedRowKey];
    }
}


- (void)decodeRestorableStateWithCoder:(NSCoder *)coder
{
    [super decodeRestorableStateWithCoder:coder];

    BOOL searchDisplayControllerIsActive = [coder decodeBoolForKey:SearchDisplayControllerIsActiveKey];

    if (searchDisplayControllerIsActive)
    {
        [self.searchDisplayController setActive:YES];

        /*
         Order is important here. Setting the search bar text causes searchDisplayController:shouldReloadTableForSearchString: to be invoked.
         */
        NSInteger searchBarScopeIndex = [coder decodeIntegerForKey:SearchBarScopeIndexKey];
        [self.searchDisplayController.searchBar setSelectedScopeButtonIndex:searchBarScopeIndex];

        NSString *searchBarText = [coder decodeObjectForKey:SearchBarTextKey];
        if (searchBarText != nil)
        {
            [self.searchDisplayController.searchBar setText:searchBarText];
        }

        NSIndexPath *selectedIndexPath = [coder decodeObjectForKey:SearchDisplayControllerSelectedRowKey];
        if (selectedIndexPath != nil)
        {
            [self.searchDisplayController.searchResultsTableView selectRowAtIndexPath:selectedIndexPath animated:NO scrollPosition:UITableViewScrollPositionTop];
        }

        BOOL searchFieldIsFirstResponder = [coder decodeBoolForKey:SearchBarIsFirstResponderKey];
        if (searchFieldIsFirstResponder)
        {
            [self.searchDisplayController.searchBar becomeFirstResponder];
        }

    }
    NSIndexPath *selectedIndexPath = [coder decodeObjectForKey:TableViewSelectedRowKey];
    if (selectedIndexPath != nil)
    {
        [self.tableView selectRowAtIndexPath:selectedIndexPath animated:NO scrollPosition:UITableViewScrollPositionTop];
    }
}


@end

===================================================================
#import <UIKit/UIKit.h>

@interface APLViewController : UITableViewController <UISearchDisplayDelegate, UISearchBarDelegate>

@property (nonatomic) NSArray *products; // The master content.

@end
======================================================================


#import "APLProduct.h"


NSString *ProductTypeDevice = @"Device";
NSString *ProductTypeDesktop = @"Desktop";
NSString *ProductTypePortable = @"Portable";


@implementation APLProduct

+ (instancetype)productWithType:(NSString *)type name:(NSString *)name
{
      APLProduct *newProduct = [[self alloc] init];
      newProduct.type = type;
      newProduct.name = name;
      return newProduct;
}


+ (NSArray *)deviceTypeNames
{
    static NSArray *deviceTypeNames = nil;
    static dispatch_once_t once;

    dispatch_once(&once, ^{
        deviceTypeNames = @[ProductTypeDevice, ProductTypePortable, ProductTypeDesktop];
    });

    return deviceTypeNames;
}


+ (NSString *)displayNameForType:(NSString *)type
{
    static NSMutableDictionary *deviceTypeDisplayNamesDictionary = nil;
    static dispatch_once_t once;

    dispatch_once(&once, ^{
        deviceTypeDisplayNamesDictionary = [[NSMutableDictionary alloc] init];
        for (NSString *deviceType in self.deviceTypeNames)
        {
            NSString *displayName = NSLocalizedString(deviceType, @"dynamic");
            deviceTypeDisplayNamesDictionary[deviceType] = displayName;
        }
    });

    return deviceTypeDisplayNamesDictionary[type];
}


static NSString *NameKey = @"NameKey";
static NSString *TypeKey = @"TypeKey";

-(id)initWithCoder:(NSCoder *)aDecoder
{
    self = [super init];
    if (self) {
        _name = [aDecoder decodeObjectForKey:NameKey];
        _type = [aDecoder decodeObjectForKey:TypeKey];
    }
    return self;
}

-(void)encodeWithCoder:(NSCoder *)aCoder
{
    [aCoder encodeObject:self.name forKey:NameKey];
    [aCoder encodeObject:self.type forKey:TypeKey];
}

@end
 
=======================================================================
extern NSString *ProductTypeDevice;
extern NSString *ProductTypeDesktop;
extern NSString *ProductTypePortable;


@interface APLProduct : NSObject <NSCoding>

@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *type;

+ (instancetype)productWithType:(NSString *)type name:(NSString *)name;

+ (NSArray *)deviceTypeNames;
+ (NSString *)displayNameForType:(NSString *)type;

@end
=======================================================================


#import "APLDetailViewController.h"

@implementation APLDetailViewController

static NSString *ProductTitleKey = @"ProductTitleKey";


- (void) encodeRestorableStateWithCoder:(NSCoder *)coder
{
    [super encodeRestorableStateWithCoder:coder];
    [coder encodeObject:self.title forKey:ProductTitleKey];
}


- (void) decodeRestorableStateWithCoder:(NSCoder *)coder
{
    [super decodeRestorableStateWithCoder:coder];
    self.title = [coder decodeObjectForKey:ProductTitleKey];
}


@end
  ================================================================
#import <UIKit/UIKit.h>

@interface APLDetailViewController : UIViewController

@end
=================================================================

Comments

Popular posts from this blog

Bitmap scalling and cropping from center

How to Bitmap scalling and cropping from center? public class ScalingUtilities {     /**      * Utility function for decoding an image resource. The decoded bitmap will      * be optimized for further scaling to the requested destination dimensions      * and scaling logic.      *      * @param res      *            The resources object containing the image data      * @param resId      *            The resource id of the image data      * @param dstWidth      *            Width of destination area      * @param dstHeight      *     ...

Custom camera using SurfaceView android with autofocus & auto lights & more

Custom camera using SurfaceView android with autofocus & auto lights & much more /**  * @author Tatyabhau Chavan  *  */ public class Preview extends SurfaceView implements SurfaceHolder.Callback {     private SurfaceHolder mHolder;     private Camera mCamera;     public Camera.Parameters mParameters;     private byte[] mBuffer;     private Activity mActivity;     // this constructor used when requested as an XML resource     public Preview(Context context, AttributeSet attrs) {         super(context, attrs);         init();     }     public Preview(Context context) {         super(context);         init();     }     public Camera getCamera() {        ...

Get Android phone call history/log programmatically

Get Android phone call history/log programmatically To get call history programmatically first add read conact permission in Manifest file : <uses-permission android:name="android.permission.READ_CONTACTS" /> Create xml file. Add the below code in xml file : <Linearlayout android:layout_height="fill_parent"  android:layout_width="fill_parent" android:orientation="vertical"> <Textview android:id="@+id/call" android:layout_height="fill_parent" android:layout_width="fill_parent"> </Textview> </Linearlayout> Now call the getCallDetails() method in java class : private void getCallDetails() { StringBuffer sb = new StringBuffer(); Cursor managedCursor = managedQuery( CallLog.Calls.CONTENT_URI,null, null,null, null); int number = managedCursor.getColumnIndex( CallLog.Calls.NUMBER ); int type = managedCursor.getColumnIndex( CallLog.Calls.TYPE ); int date = managedCur...