Thursday, April 30, 2009

Alternative of NSPredicate on iPhone SDK

Use following classes:


The SearchPredicate.h

// SearchPredicate.h
//
// Created by Yasir Ibrahim on 24/12/08.

#import <UIKit/UIKit.h>

@interface SearchPredicate : NSObject {
NSString* searchTerm;
}


@property (nonatomic, retain) NSString* searchTerm;

- (
BOOL)evaluateWithObject:(id)object;

@end


And the SearchPredicate.m

// SearchPredicate.m
// Created by Yasir Ibrahim on 24/12/08.
#import "SearchPredicate.h"

@implementation SearchPredicate

@synthesize searchTerm;

- (
BOOL)evaluateWithObject:(id)object
{

searchTerm = [searchTerm stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];

if
([searchTerm compare:@"*"] == 0 || [searchTerm compare:@""] == 0)
{

return
TRUE;
}


NSString* name = [object objectForKey:@"Name"];

if
([name length] >= [searchTerm length])
{

NSRange r;
r.length = [searchTerm length];

r.location = 0;

if
([name rangeOfString:searchTerm options:NSCaseInsensitiveSearch range:r].location != NSNotFound)
{

return
TRUE;
}

else

{

return
FALSE;
}
}


else

{

return
FALSE;
}
}

@end




And here is how to use it with UISearchBar:


- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar
{
[
mySearchBar resignFirstResponder];

SearchPredicate* sp = [[SearchPredicate alloc] init];

sp.searchTerm = [mySearchBar text];
[
pickerItemFilteredList release];
//pickerItemFilteredList = [pickerItemList filteredArrayUsingPredicate:sp];
pickerItemFilteredList = [[NSMutableArray alloc] init];

for
(int i=0; i<[pickerItemList count]; i++)
{

if
([sp evaluateWithObject:[pickerItemList objectAtIndex:i]])
{
[
pickerItemFilteredList addObject:[pickerItemList objectAtIndex:i]];
}
}

[
pickerItemFilteredList retain];

[
myPickerView reloadComponent:0];

if
([pickerItemFilteredList count] == 1)
{
[
self showStoreOnMapAtRow:0];
}
}




Notice that I was using NSPredicate (sp) with filteredArrayUsingPredicate method of NSMuteableArray before. However as I have not implemented that method for our new class yet (you can do this, I guess using categories etc., and post back in comments) I have commented it and using a for loop and evaluateWithObject on each object of NSMuteableArray to have the same effect.

Tuesday, April 28, 2009

iPhone Related Business Blogs and Articles, I Just Read

Saturday, April 25, 2009

Strings in Objective C: Multiline String Literal, String Concatenation and String Formating

Here is an example of it:
NSString* sql = [NSString stringWithFormat:@"select ID, aText, aFloat, aFloat2, "
"aText2 as myText2, aText3, 'aStringConstant' as myStringConstant "
"from myTable where afloat<=%1.6f and afloat2>=%1.6f Order by aText",
floatVariable, floatVariable2];

return [self executeQuery:[sql UTF8String]];

During my search I found this interesting blog.

Following link(s) were also helpful:
Java Developer’s Guide to String Constants in Objective-C


Objective C and SQLite: Query Framework to Separate the Database Layer

After messing around with sqlite3_stmt*, sqlite3_prepare_v2, sqlite3_bind_double, sqlite3_step, sqlite3_column_int and sqlite3_column_text to run each query on sqlite (from my objectiveC code) and separate code for each time gathering the result in an NSMutableArray* and NSDictionary (for each row), I was thinking of a way to have a method that do it autometically by just giving the query, with no need to tell how to store the results.

After some research in sqlite documentation, I found sqlite3_get_table method (http://www.sqlite.org/c3ref/free_table.html), which I used in the following way:


+ (NSMutableArray*) executeQuery: (const char *) sql
{
MyAppDelegate *appDelegate = (MyAppDelegate *)[[UIApplication sharedApplication] delegate];
char** azResult = NULL;
int nRows = 0;
int nColumns = 0;
char* errorMsg; //= malloc(255); // this is not required as sqlite do it itself

sqlite3_get_table(
[appDelegate getdb], /* An open database */
sql, /* SQL to be evaluated */
&azResult, /* Results of the query */
&nRows, /* Number of result rows written here */
&nColumns, /* Number of result columns written here */
&errorMsg /* Error msg written here */
);
if(azResult != NULL)
{
NSMutableArray* dataTable = [[[NSMutableArray alloc] init] autorelease];
nRows++; //because the header row is not account for in nRows

for (int i = 1; i < nRows; i++)
{
NSMutableDictionary* row = [NSMutableDictionary dictionaryWithCapacity:nColumns];
for(int j = 0; j < nColumns; j++)
{
NSString* key = [NSString stringWithUTF8String:azResult[j]];
NSString* value = [NSString stringWithUTF8String:azResult[(i*nColumns)+j]];

[row setValue:value forKey:key] ;
}
[dataTable addObject:row];
}

//free(errorMsg); // no need to free, sqlite somehow doing it
sqlite3_free_table(azResult);
return dataTable;
}
else
{
NSAssert1(0,@"Failed to execute query with message '%s'.",errorMsg);
return nil;
}
}

Above code is capable of accepting any 'select' query and generate results in a standard data structure consist of NSMutableArray and NSMutableDictionary. Each element in the array represent a row, while each key-value pair in dictionary represent a column name and its value. Column name would be same as given in the query or as altered by 'as' keyword.

Using this method one can totally separate DB layer and application layer, and even in DB layer the method would be very short. Here is an example:



NSString* sql = [NSString stringWithFormat:@"select ID, aText, aFloat, aFloat2, "
"aText2 as myText2, aText3, 'aStringConstant' as myStringConstant "
"from myTable where afloat<=%1.6f and afloat2>=%1.6f Order by aText",
floatVariable, floatVariable2];

return [self executeQuery:[sql UTF8String]];


Related Links
SQLite Version 3 C/C++ API Reference
Convenience Routines For Running Queries

Friday, April 17, 2009

ASP.NET: Accessing the GridView Row And Controls In It

There are some buttons in one of our ASP.NET project that are being directly generated from the sql query by spitting out html code. In grid view to show these buttons some fields are bound to the expression generating these buttons. The problem with these button was that we cannot set server side properties for them. Our developers had to set causevalidation = false for some of these button but they were unable to do so.

I thought of solving this issue by using a select command button changed to a templated field. However by changing a select button to a templated field it stops doing it function of selecting a grid row. To do that I planned to use the click event of this button and find this button in the grid by iterating over the rows, once found use that row index to select the row, which would trigger the selection change event. I implemented this idea as follows:


protected void Button1_Click1(object sender, EventArgs e)
{

for (int i = 0; i < GridViewBiz.Rows.Count; i++)
{
if (GridViewBiz.Rows[i].FindControl("Button1").Equals(sender))
{
GridViewBiz.Rows[i].RowState = DataControlRowState.Selected;
break;
}
}
}



For some unknown reason this code was unable to find the control, as the control that is sending the event is something different then all the buttons in all the rows of grid.

Frustrated by this I tried to find this kind of topic on the internet. I found an answer on http://forums.asp.net/t/1137287.aspx.

Using that I reimplemented the method in following way:


protected void Button1_Click1(object sender, EventArgs e)
{
Button btnMoreDetails = (Button)sender;
GridViewRow row = (GridViewRow)btnMoreDetails.NamingContainer;
row.RowState = DataControlRowState.Selected;
bringMoreDetails();
}


The key in above code is using the NamingContainer property which is ideal to find the row in a grid situation. We will now use this method for finding different IDs (primary keys) when a templated button (will be implemented) for an action would be clicked.

The method bringMoreDetails() is being called as changing the selection is not triggering GridView_SelectedIndexChanged event.

Thursday, April 16, 2009

Microsoft Access: How to Insert a Subdatasheet

I had two tables in my Access database, like category and subcategory such that the categoryId was being used as foriegn key in the subcategory table, however when I was opening the category table datasheet view it was not show a + sign to collapse and browse the subcategories. I remembered seeing this kind of view earlier and I remember that it happen when you make the relationships between the tables, so I went ahead and did that but it was now show that '+' sign in the datasheet view. I tried to find the older tables in which it was showing the '+' sign and found out that the type of relationship in them was "one-to-many" while some how in my case it was "intermediate", recreating the relationship automatically made it "one-to-many" and the problem solved.

During this investigation I also found that this can be done without actually setting up relationships. This is by going to insert menu selecting Subdatasheet and then selecting whatever table or query you want as subdatasheet and which column will actually link the tables.