Tuesday, February 24, 2009

iPhone Development: Adding Search Bar in Table View

In this post I will be creating a Search screen, which will have a table view with a search bar. Table should display all the records if search field is empty other wise it should show all the records that have matching strings with the search field.

At the end, it should look like this:


So to start create a new ViewBased Project and name it as MySearchScreenApp. You can start with any template you want but you can find in my previous posts why I always start with the most basic template.

The Search screen that you can see in the above images are implemented in MySearchScreenAppViewController class. So open the header file MySearchScreenAppViewController.h and add the following code:

@interface MySearchScreenAppViewController : UIViewController {

UITableView *myTableView;

NSMutableArray *dataSource; //will be storing all the data

NSMutableArray *tableData;//will be storing data that will be displayed in table

NSMutableArray *searchedData;//will be storing data matching with the search string

UISearchBar *sBar;//search bar


}

@property(nonatomic,retain)NSMutableArray *dataSource;


@end


Here we have only defined the UIElements and some data sources. There are three mutable arrays. dataSource will be containing all the data and will be initialized before loading the searchScreen. searchedData will be holding a subset of dataSource based on search string. It will be contents of tableData which you will be viewing in the table below searchBar.

To implement all these elements, open MySearchScreenAppViewController.m file and add the following code in loadView:

- (void)loadView {

[super loadView];

sBar = [[UISearchBar alloc]initWithFrame:CGRectMake(0,0,320,30)];

sBar.delegate = self;

[self.view addSubview:sBar];

myTableView = [[UITableView alloc]initWithFrame:CGRectMake(0, 31, 300, 400)];

myTableView.delegate = self;

myTableView.dataSource = self;

[self.view addSubview:myTableView];

//initialize the two arrays; dataSource will be initialized and populated by appDelegate

searchedData = [[NSMutableArray alloc]init];

tableData = [[NSMutableArray alloc]init];

[tableData addObjectsFromArray:dataSource];//on launch it should display all the records

}



The only code worth explanation here is the last line [tableData addObjectsFromArray:dataSource]
As already discussed, dataSource will be holding all of data and tableData will be holding data to be shown in table. This code copies all elements of dataSource into tableData so that when the search screen is visible first time, all the data is displayed.

Now we implement all the tableViewDelegate methods: In the same file add the following code:

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {

return 1;

}



- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {

//NSLog(@"contacts error in num of row");

return [tableData count];

}



- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {

static NSString *MyIdentifier = @"MyIdentifier";

UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:MyIdentifier];

if (cell == nil) {

cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:MyIdentifier] autorelease];

}

cell.text = [tableData objectAtIndex:indexPath.row];

return cell;

}


If you do not understand the code above, you should refer my earlier posts. These methods will be displaying data in tableData array into our table view.
Now we have to implement search bar delegate methods, which will be controlling the contents of tableData. In the same file add the following code:

#pragma mark UISearchBarDelegate


- (void)searchBarTextDidBeginEditing:(UISearchBar *)searchBar

{

// only show the status bar's cancel button while in edit mode

sBar.showsCancelButton = YES;

sBar.autocorrectionType = UITextAutocorrectionTypeNo;

// flush the previous search content

[tableData removeAllObjects];

}


- (void)searchBarTextDidEndEditing:(UISearchBar *)searchBar

{

sBar.showsCancelButton = NO;

}


In the first method, we are only changing the appearance of search bar when a user taps on search bar. Refer the first two images to find the difference. The main code will be written now:

- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText

{

[tableData removeAllObjects];// remove all data that belongs to previous search

if([searchText isEqualToString:@""]||searchText==nil){

[myTableView reloadData];

return;

}

NSInteger counter = 0;

for(NSString *name in dataSource)

{

NSAutoreleasePool *pool = [[NSAutoreleasePool alloc]init];

NSRange r = [name rangeOfString:searchText];

if(r.location != NSNotFound)

{

if(r.location== 0)//that is we are checking only the start of the names.

{

[tableData addObject:name];

}

}

counter++;

[pool release];

}

[myTableView reloadData];

}


This method will be called whenever text in the search bar is changed. We should update tableData array accordingly. So the first line[tableData removeAllObjects]; removes all the searched records from previous search string.
Then we check if the search string is null so that we should return immediately with and empty tableData.
If the searchString is not null then we will go through each object in dataSource and select those objects which have the occurrence of search string in beginning. You can modify the code to have any kind of search though.
The last line[myTableView reloadData]; refreshes the table view. What ever be the content of tableData will be shown now.

We should also show all the records if the search string is cleared or cancelled. For that add the following code:

- (void)searchBarCancelButtonClicked:(UISearchBar *)searchBar

{

// if a valid search was entered but the user wanted to cancel, bring back the main list content

[tableData removeAllObjects];

[tableData addObjectsFromArray:dataSource];

@try{

[myTableView reloadData];

}

@catch(NSException *e){

}

[sBar resignFirstResponder];

sBar.text = @"";

}


// called when Search (in our case "Done") button pressed

- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar

{

[searchBar resignFirstResponder];

}


In the first function, we have flushed all the searched records and simply copy all the records from dataaSource. In the second function we are telling the text pad to resign. You can skip this method to see what happens.


Thats it!! We are done with the search screen. But we have to initialize it from AppDelegate. So open MySearchScreenAppAppDelegate.m and add the following code:

- (void)applicationDidFinishLaunching:(UIApplication *)application {

// Override point for customization after app launch

viewController.dataSource = [[NSMutableArray alloc]init];

for(char c = 'A';c<='Z';c++)

[viewController.dataSource addObject:[NSString stringWithFormat:@"%cTestString",c]];

UINavigationController *nvc = [[UINavigationController alloc]initWithRootViewController:viewController];

viewController.title = @"Search";

[window addSubview:nvc.view];

[window makeKeyAndVisible];


Here we have only added dummy data into dataSource to test. You can always store data from database or anywhere .

Now build and run the application. Try typing characters in SearchField, you will find that the search is case sensitive. You will also notice that the search works only if you type the beginning characters only. If you want that the searched records should have search string occurance at any place, here is an alternative implementation of (void)searchBarTextDidEndEditing:(UISearchBar *)searchBar

- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText

{

[tableData removeAllObjects];// remove all data that belongs to previous search

if([searchText isEqualToString:@""]||searchText==nil){

[myTableView reloadData];

return;

}

NSInteger counter = 0;

for(NSString *name in dataSource)

{

NSAutoreleasePool *pool = [[NSAutoreleasePool alloc]init];

NSRange r = [name rangeOfString:searchText];

if(r.location != NSNotFound)

[tableData addObject:name];

counter++;

[pool release];

}

[myTableView reloadData];

}


I hope this post was useful. I will suggest you shou should read Predicates to optimize the search if you have very large data source to be searched.

Comments will be appreciated.



Wednesday, February 4, 2009

iPhone Development: Setup Wizard

In this post I am going to access all the preferences from the code. Also I have made a setup wizard which allows you to change the preferences from the application it self.
Following are some of the things covered:
  1. Reading Preferences
  2. Editing Preferences
  3. Custom UITableView
  4. Wizard
The Wizard will finally look like this:
`
`
Before we start, lets have a look at NSUserDefaults. This is the class which will be used to access  our preferences. All you need to do is get an instance of this class, after that it is much like a dictionary. For example:
NSUserDefaults *user = [NSUserDefaults standardUserDefault];
[user boolForKey:]
[user stringForKey:]
[user setBool:(BOOL) forKey:


So lets start by adding a new UIViewController subclass in the classes folder. Right click on classes and add new UIViewController subclass, name it as SetupWizard.m.
Before accessing preferences, we need to setup the view for the wizard. So open SetupWizard.h and add the following code:
`

`
Here we have created some UI elements which are similar to the ones we have in our application preferences if you remember.
Since we will be using a table view , we have included the tableview delegate and datasource in interface declaration, which also includes UITextFieldDelegate . We will see later why we have used it.
Also we have made 2 cells, here we are diverting from the conventional coding practices, but there is a reason for that which will be clear as we proceed.

For now open SetupWizard.m and add the following code inside loadView method:
`

`
This code is simple enough, just a few UI elements being added on the view. If you are not clear what we did in this code then you need to refer my previous posts. 
Important things to note in this code are: we need to have a method named "save" which we have added as a target to the button. 

This is the method where we will be saving the preferences from our application into the Settings so that next time user launches the application, he has the same preferences he had last time. We will understand that in a while.

Before implementing save method, we should not forget we have a tableView added into the view so we have to implement it's protocol methods. Add the following code in SetupWizard.m:
`

`
Here we need some description. Why do we need to have 2 cells declared in .h? See the table view in the Wizard image at the beginning of this post. You will notice that this is similar to the one in preferences we made last time. See the image below, it is the multivalue preference we made last time.
`

`
If you tried using it then you must have noticed that when you select a cell, it gets checkmarked and only one cell at a time gets checkmarked. To make this happen in our table view, we had to have the access to both the cells all the time. You will understand this after you see what we are going to write in didSelect method of tableViewDelegate. So go on and write the following code:

`
Did that help? If we went on with the conventional coding style, how do we get access to the cell in didSelectMethod to change the accessory types? You will say that I will make a call to cellForRowAtIndexPath and receive the cell where ever I want. That's correct but you only get the cell corresponding to the indexpath you have, if you want to get other cells by this method, you need to create your own index path. Since we had only 2 cells, we can make our lives simple by avoiding  it.

Now we are going to implement the most awaited "Save" method where we will be accessing preferences. Add the following code in SetupWizard.m
`

I think the code is self explanatory. If you now build the code and run it, you should be expecting to see what you made just now, right? Wrong! you will see the same hello world screen again and the tableview if you pressed the button. What we are going to do now is push this SetupWizard view from an appropriate place. For now, we will be doing it from MyTableView class we created earlier. So open MyTableView.m and add the following code:
`

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {

SetupWizard *wizard = [[SetupWizard alloc]init];

[self.navigationController pushViewController:wizard animated:YES];

}
`
Now if you run build and run the project, you can see the wizard by clicking on any row in the table.
You can download the whole code till now from here 

Now, we still have one thing to do. When the user sees the wizard, he should be shown all the previous configurations he chose. For that we will be reading all the preferences and reflecting it in our view. Just add the following code in SetupWizard.m :
`

-(void)loadPreferences

{

NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];

[textField setText: [userDefaults stringForKey:@"url"]];

if([userDefaults integerForKey:@"whichNumber"] == 0)

cell1.accessoryType = UITableViewCellAccessoryCheckmark;

else

cell2.accessoryType = UITableViewCellAccessoryCheckmark;

[sw setOn:[userDefaults boolForKey:@"switch1"]];

[slider setValue:[userDefaults doubleForKey:@"slider_preference"]];

}

`
and call this method from viewDidLoad like this:
`

- (void)viewDidLoad {

[self loadPreferences];

}

`
Now if you build and run the application, every time you will see the previous configurations showing up in the wizard.

Let me know if I have been missing anything.

`
`


Tuesday, February 3, 2009

iPhone Development: Application Preferences Part 2

In this post I will be using PSChildPaneSpecifier in the application preferences.
You should read part 1 of this post if you are not familiar with application preferences. You can find it here .

What does PSChildPaneSpecifier do? It adds another page of configurations into your settings. We do that when we have a large number of configurations, so we divide them into categories and make a child pane of each category. 

A child pane is nothing but another plist file. So to start with, open the same project "MyTestProject", right click on Settings.bundle and select "add new". From the "Other" tab select plist file as shown below:



Name the file as "Child.plist". You will notice that child.plist file is not added in the Settings.bundle. You need to add it inside Settings.bundle which will be done from the terminal.

To do so, open Terminal and navigate to your projects directory. Execute "ls" on shell to cross verify that you are in the correct directory. Following should appear on the Terminal:



Now copy child.plist file into the Settings.bundle by following command: "cp child.plist Settings.bundle". Cross verify by navigating into Settings.bundle directory and executing "ls". child.plist file should show up. 

Close the terminal now and come to xCode again. You will now find child.plist inside Settings.bundle. Double click Child.plist and add items to make it look like this:



We have created the new child plist file, now we need to add a reference to it in the root.plist file. Open root.plist and add 2 new items to make it look like this.



Build and run the application , go to the settings and select your project MyTestProject. It shold look similar to the following:



In next post I am going to access these preferences from the MyTestProject and also allow the user to change them from the View itself.

`
`
`
`
`
`


iPhone Development: Application Preferences

Here I am going to discuss Application Preferences. For those who are unaware of it, Application preferences refers to the configurations that shows up in the Settings. So if you want you application name to be listed inside Settings, you need to follow the procedure.

Lets first discuss what are the available options. Since we know in iPhone all applications work in a sandbox mode, we can not write a program to alter the behavior of another program. What it means here is, there are fixed UI Elements that can be used in Application preferences, their behavior is also fixed and cannot be changed. For example a you cannot provide a date picker in the settings, you cannot pop up a message saying the URL you entered is not correct. All you can do is provide a field where user can enter his preferences.

Following is a list of options we have for UI elements that we can provide:

  1. PSTitleValueSpecifier: Used to write titles, similar to UILabel
  2. PSTextFieldSpecifier: Similar to UITextField
  3. PSToggleSwitchSpecifier: A toggle switch which can be ON or OFF
  4. PSSliderSpecifier: A slider which can be used to provide a value bw MAX and MIN
  5. PSMultiValueSpecifier: Shows multiple values from which user can select one.
  6. PSGroupSpecifier: Very important for layout (discussed later).
  7. PSChildPaneSpecifier: When you need to have another page of configurations.
All these elements can be used in the settings and you can read as well as manipulate them from your code.

Lets start the procedure. Open the same project "MyTestProject" we have been using last time. right click on the resources folder in left pane and select addNew. In the new window select Settings.bundle as shown in the figure.



Name it as Settings.bundle and click OK. This will create a Settings.bundle in your resources folder. Expand Settings.Bundle and double click on  Root.plist.

By default it should look like this: 

Right click on Preference Specifier and select "cut". I did this because in all my tutorials I do not rely on any convenience/ default things provided. It helps later in customizing things the way you want. Any ways now click on the  "+" button on the right side of the table and in place of "new Item" type PreferenceSpecifiers. In the type field corresponding to PreferenceSpecifiers, select "Array".

Now we will add one by one all the elements we listed earlier. Click on the Arrow symbol to the left of PreferenceSpecifiers to expand it. You will notice that the "+" image on right has now changed and it shows 3 lines. Click on it to add a child which will automatically be named as "Item 1". In the Type select "Dictionary". 
Now expand "Item 1" and add a child element to it, name it as "Title". Type should be "String" and in the Value write "URL".
Add more childs and make the plist file look like this:


Now Click on the arrow against "Item 1" to hide its child elements, click on "Item 1" (not on the arrow) and click on "+" button on right. This should show "Item 2" below "Item 1". Follow the last step correctly else you will end up wondering why I am not able to add a child or a new item at the place I want.

Add more items to make it look like this: 





Now build and run your project, go to Settings and find you applications' name there, clicking on it should show a screen like this:



I have only used three types in the preferences. In next post I will be using PSChildPaneSpecifier and add remaining of the elements i.e. PSTitleViewSpecifier, PSSliderSpecifier and PSGroupSpecifier.

Do not forget to mention name of your project "MyTestProject" against "Title", the second field from top in the pList file. 

An important thing to remember: "+" button in plist file adds a sibbling and the button with 3 lines adds a child.

If you faced any problem in creating child elements or new Items at the specific place in the plist file, don't worry, you just need to expand/hide elements properly. If you still face any problems, lemme know and I will be more than happy to help.

Topics of next post:
  1. Using PSChildPaneSpecifier and adding rest of the types.
  2. Accessing application preferences from inside your code
  3. Changing preferences from your code.
Also in next post we will be creating a custom TableView and custom Cells to display the application preferences in the form of a SetUp Wizard (all by writing code and not using Interface Builder).

 


There was an error in this gadget