Let's Do iOS Programming For Beginners
Wednesday, April 9, 2014
Blocks From one Class to Another
One popular scenario in using blocks is when you need to update your view after a web service call is finished.
Thursday, April 3, 2014
Blocks in Web Service Functions
Hi Everyone! It has been a very long while since I last posted in this blog. This time, I will be making a tutorial in using blocks when we are accessing our API or web services.
Disclaimer: I am also not that 'expert' when it comes to blocks. But I have learned this method throughout my career as an iOS Developer. This is a combination of what I learned from my senior teammates.
By the way, I am using the AFNetworking Framework.
1. Create a singleton-patterned APIClient class which is a subclass of AFHTTPClient.
2. Our APIClient interface file should look like this...
3. And in our implementation file:
@end
4. Next, I will setup my RequestManager class which is an NSObject subclass. So in my RequestManager.h file:
//RequestManager.m file
5. The last thing that we are going to prepare is our ResultSet. What I mean by ResultSet is that what do we usually RECEIVE from the API? There are results that totally look the same, so we will just have to create an object of what the API usually returns.
So you have to decide what your API usually returns and just modify the list. You can always add or remove variables on the list -- make it as flexible as possible. A flexible and easy to reuse codes are GOOD codes ;-)
Currently, in my part, our API usually returns the following so this is how my ResultSet class currently look like:
You may opt to just create a Request class for all the API request functions that we will be making but I suggest we create request classes according to our modules for easier editing in the future.
6. Let's create a FeedRequest class which is a subclass of RequestManager (the one that we did above) for our Feeds module.
This is how my FeedRequest interface file looks like:
Disclaimer: I am also not that 'expert' when it comes to blocks. But I have learned this method throughout my career as an iOS Developer. This is a combination of what I learned from my senior teammates.
By the way, I am using the AFNetworking Framework.
Set up...
1. Create a singleton-patterned APIClient class which is a subclass of AFHTTPClient.
2. Our APIClient interface file should look like this...
#import <Foundation/Foundation.h>
#import "AFHTTPClient.h"
@interface APIClient : AFHTTPClient
+ (APIClient *)sharedClient;
@end
3. And in our implementation file:
#import "APIClient.h"
#import "Constants.h"
@implementation GCAPIClient
/**
We usually have our Development and Production API links, right? So to cater the changes in our Development and Production URLs, let's do this #if #else #endif preprocessor directives.
DevelopmentAPIURL and ProductionAPIURL values are defined in my Constants.h file
Example in Constants.h file:
#define DevelopmentAPIURL @"https://jescdev.sample.com/api/"
#define ProductionAPIURL @"https://jesc.sample.com/api/"
*/
+ (APIClient *)sharedClient
{
static APIClient *_sharedClient = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
#if STAGING
_sharedClient = [[APIClient alloc] initWithBaseURL:[NSURL URLWithString:DevelopmentAPIURL]];
#else
_sharedClient = [[APIClient alloc] initWithBaseURL:[NSURL URLWithString:ProductionAPIURL]];
#endif
});
return _sharedClient;
}
- (id)initWithBaseURL:(NSURL *)url {
self = [super initWithBaseURL:url];
if (!self) {
return nil;
}
return self;
}
- (NSMutableURLRequest *)requestWithMethod:(NSString *)method path:(NSString *)path parameters:(NSDictionary *)parameters {
NSMutableURLRequest *request = [super requestWithMethod:method path:path parameters:parameters];
[request setTimeoutInterval:120];
return request;
}
@end
4. Next, I will setup my RequestManager class which is an NSObject subclass. So in my RequestManager.h file:
#import <Foundation/Foundation.h>
#import "AFJSONRequestOperation.h"
typedef void(^ResultBlock)(id results);
typedef void(^FailedBlock)(NSError *error);
@interface RequestManager : NSObject {
AFJSONRequestOperation *operation;
}
//We'll get to know more of these later... These methods basically reports a success or fail back to the calling function right after the request has finished executing.
- (void)reportSuccess:(id)results;
- (void)reportFailure:(NSError *)error;
//We'll also get to know more of these later. These methods will just set a completionBlock if the request reported a Success or failureBlock if the request reported a Failure.
- (void)setCompletionBlock:(ResultBlock)aCompletionBlock;
- (void)setFailedBlock:(FailedBlock)aFailedBlock;
- (void)start; //To Start the request
- (void)cancel; // To Cancel the request. This is important so we can stop the request whenever we want to.
- (BOOL)isExecuting; //To know if the request is being executed or is currently executing
- (BOOL)isFinished; //To know if the request has finished executing
//Optional
- (void)showSuccessAlertViewWithSuccessMessage:(NSString *)successMessage;
- (void)showFailedAlertViewWithErrorMessage:(NSString *)errorMessage;
@end
//RequestManager.m file
#import "RequestManager.h"
#import "APIClient.h"
@implementation RequestManager {
ResultBlock completionBlock;
FailedBlock failureBlock;
}
#pragma mark - Methods
- (void)reportSuccess:(id)results
{
if (completionBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
completionBlock(results);
});
}
}
- (void)reportFailure:(NSError *)error
{
if (failureBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
failureBlock(error);
});
}
}
#pragma mark - Block Setters
- (void)setCompletionBlock:(ResultBlock)aCompletionBlock
{
completionBlock = [aCompletionBlock copy];
}
- (void)setFailedBlock:(FailedBlock)aFailedBlock
{
failureBlock = [aFailedBlock copy];
}
#pragma mark - Public Methods
//Starts the operation.
- (void)start
{
[[APIClient sharedClient] enqueueHTTPRequestOperation:operation];
}
//The operation has been canceled.
- (void)cancel
{
[operation cancel];
}
//The operation is currently executing.
- (BOOL)isExecuting
{
return [operation isExecuting];
}
// The operation has finished executing.
- (BOOL)isFinished
{
return [operation isFinished];
}
#pragma mark - AlertViews
/**
Shows a Successful Message Alert
@param successMessage, a string with Success-related message
*/
- (void)showSuccessAlertViewWithSuccessMessage:(NSString *)successMessage
{
UIAlertView *alertError = [[UIAlertView alloc] initWithTitle:@"Success!" message:successMessage delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil];
[alertError show];
}
/**
Shows a Failed Message Alert
@param errorMessage, a string with error message of what went wrong why the request failed
*/
- (void)showFailedAlertViewWithErrorMessage:(NSString *)errorMessage
{
UIAlertView *alertError = [[UIAlertView alloc] initWithTitle:@"Failed!" message:errorMessage delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil];
[alertError show];
}
@end
5. The last thing that we are going to prepare is our ResultSet. What I mean by ResultSet is that what do we usually RECEIVE from the API? There are results that totally look the same, so we will just have to create an object of what the API usually returns.
So you have to decide what your API usually returns and just modify the list. You can always add or remove variables on the list -- make it as flexible as possible. A flexible and easy to reuse codes are GOOD codes ;-)
Currently, in my part, our API usually returns the following so this is how my ResultSet class currently look like:
#import <Foundation/Foundation.h>
@interface EventResultSet : NSObject
@property NSInteger currentPage;
@property NSInteger resultPageCount;
@property NSInteger resultCount;
@property (nonatomic, strong) NSString *noResultFoundText;
@property (nonatomic, strong) NSMutableArray *results;
@end
#import "EventResultSet.h"
@implementation EventResultSet
//Just in case you still didn't know, in the new versions of Xcode, there was no need for us to synthesize the properties anymore. Ain't this nice?
@end
Let the fun of calling API services using blocks begin! :D
Can we first imagine this scenario in our app? Most social applications will need to fetch FEEDS from their server. "Feeds" are the status messages/updates of our "friends" or the people we "follow" and even ourselves. So imagine we have to fetch Feeds from our server...You may opt to just create a Request class for all the API request functions that we will be making but I suggest we create request classes according to our modules for easier editing in the future.
6. Let's create a FeedRequest class which is a subclass of RequestManager (the one that we did above) for our Feeds module.
This is how my FeedRequest interface file looks like:
#import <Foundation/Foundation.h>
#import "RequestManager.h"
/**
A customized request class for fetching feeds/feed-related fetches.
*/
@interface FeedRequest : RequestManager
//Methods
- (void)feedsWithParameters:(NSMutableDictionary *)parameters; //We will know later what the parameters will be.
@end
7. And in my FeedRequest implementation file, I will implement the feedsWithParameters: method.
/**
Fetch list of events from the server. Increment currentPage in order to fetch more events.
@param parameters, a dictionary of parameters that may include currentPage or page, and pageSize or limit of results.
*/
- (void)feedsWithParameters:(NSMutableDictionary *)parameters
{
ResultSet *resultSet = [[ResultSet alloc] init];
if (! resultSet.results) {
resultSet.results = [[NSMutableArray alloc] init];
}
//Path is the string after our APIURL. From the API URL given above, if we get our list of feeds using the URL : https://jescdev.sample.com/api/feeds then our path value will be "feeds"
NSMutableURLRequest *request = [[GCAPIClient sharedClient] requestWithMethod:@"GET" path:@"feeds" parameters:parameters];
operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
//Assuming that the returned JSON looks like this. Do not include the texts on maroon to your code, this is just a sample response.
JSON: {
currentPage = 1;
list = (
{
status = Test
userId = 1234;
userName = JennCurativo;
...
},
{
status = Hello
userId = 1002;
userName = Polka;
...
},
...
{
status = Test 3
userId = 5688;
userName = Demitria;
...
}
);
pageCount = 1;
resultCount = 6;
}
//This is how we save the results:
resultSet.currentPage = [JSON[@"currentPage"] integerValue];
resultSet.resultCount = [JSON[@"resultCount"] integerValue];
resultSet.resultPageCount = [JSON[@"pageCount"] integerValue];
NSArray *feeds = [JSON[@"list"] allObjects];
for (id result in feeds) {
Feed *feed = [[Feed alloc] init];
//example on how to set feed values
//feed.status = result[@"status"];
[resultSet.results addObject:feed];
}
if ([feeds count] == 0) {
resultSet.noResultFoundText = JSON[@"message"];
}
//Report success back to the calling function
[self reportSuccess: resultSet];
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
//Report failure back to the calling function
[self reportFailure:error];
//Optional
//[self showFailedAlertViewWithErrorMessage:error.localizedDescription];
}];
}
Let's call this API method that we just created from our ViewController.
8. Hey, I'm assuming you have your view controllers ready. So what I'm gonna do is just simply make a function in calling our API method. :D
Inside our view controller .m file, we make a function:
- (void)fetchFeedsFromServer
{
NSMutableDictionary *parameters = [[NSMutableDictionary alloc] init];
parameters[@"pageSize"] = [NSNumber numberWithInt:20];
parameters[@"page"] = [NSNumber numberWithInteger:++self.currentPage];
//Add any other parameters needed for your API request
FeedRequest *request = [[FeedRequest alloc] init];
[request feedsWithParameters:parameters];
[request setCompletionBlock:^(id results) {
//Remember the [self reportSuccess: resultSet]; code above (#7)? Upon reportingSuccess, it will go inside this block. resultSet is received by results
//Processing of fetched data.
ResultSet *resultSet = (ResultSet *)results;
self.resultPageCount = resultSet.resultPageCount;
self.currentPage = resultSet.currentPage;
for (int i = 0; i < [resultSet.results count]; i++) {
[self.feedsArray addObject:[resultSet.results objectAtIndex:i]];
}
//Update our feeds tableview
[self.tableView reloadData];
}];
[request setFailedBlock:^(NSError *error) {
//If the request fails, the [self reportFailure:error]; will go inside this block.
}];
[request start];
}
There may be other BETTER way of doing this so if you have a better suggestion, feel free to comment below. I hope this somehow helps beginners in fetching data from the API.
Another blocks tutorial from one class to another - UP NEXT!
Monday, May 20, 2013
UISplitViewController Landscape
Tutorial Request from Mazrizan Kamisan
We'll have the portrait view in another tutorial which will be a continuation of this landscape tutorial of UISplitViewController.
/*Note: This works in OS versions greater than or equal to iOS 6.0
OS versions less than iOS 6.0 has a different look in its UIPopoverController and doesn't support Landscape Orientation.*/
I studied UISplitViewController by creating a Master Detail Application Template. For sure, you will also be able to understand by looking at its code. For those who want to do it themselves (not just merely creating a Master Detail Application), just follow this tutorial.
1. Create an Empty Application Template.
Select iPad for our Device Family.
2. Add a New File, subclass of UITableViewController, check "With XIB for user interface." This is the view on the left area of our application.
3. Create another File, subclass UIViewController. This is for the bigger area (right panel) of our application. I named it "Detail" view because it acts and shows the detailed view of the cell selected in our tableview (left area).
4. Create a UISplitViewController property in our AppDelegate.h file.
We'll have the portrait view in another tutorial which will be a continuation of this landscape tutorial of UISplitViewController.
/*Note: This works in OS versions greater than or equal to iOS 6.0
OS versions less than iOS 6.0 has a different look in its UIPopoverController and doesn't support Landscape Orientation.*/
I studied UISplitViewController by creating a Master Detail Application Template. For sure, you will also be able to understand by looking at its code. For those who want to do it themselves (not just merely creating a Master Detail Application), just follow this tutorial.
1. Create an Empty Application Template.
Select iPad for our Device Family.
2. Add a New File, subclass of UITableViewController, check "With XIB for user interface." This is the view on the left area of our application.
3. Create another File, subclass UIViewController. This is for the bigger area (right panel) of our application. I named it "Detail" view because it acts and shows the detailed view of the cell selected in our tableview (left area).
4. Create a UISplitViewController property in our AppDelegate.h file.
Synthesize the splitViewController property and import the two new files we created from steps 2 and 3 in AppDelegate.m file.
5. Go to AppDelegate.m file and update the didFinishLaunchingWithOptions method such that:
- Create an instance of the two new classes we created.
- Allocate and initialize the splitViewController.
- Set the view controllers of our splitViewController.
- @[menuVC, detailVC] is just the same as saying [[NSArray alloc] initWithObjects: menuVC, detailVC, nil];
- Set the rootViewController of our window to splitViewController.
Hit Run button and you should see something like this.
6. Open PopMenuViewController.h and create a PopMenuDetailViewController property. This will be our "link" to the two view controllers.
Open PopMenuViewController.m file and synthesize the PopMenuDetailViewController property. Also, create an array for our the list that we will be showing in our tableView.
7. Add objects in our array inside our ViewDidLoad method in PopMenuViewController.m file.
self.menu = [[NSArray alloc] initWithObjects: @"Menu 1", @"Menu 2", @"Menu 3"];
Update the Table view Data Source methods such that:
- numberOfSections = 1
- numberOfRows = array.count
- cellForRowAtIndexPath >> cell Label will be from our array
8. Open PopMenuDetailViewController.xib file and add a UILabel at the middle of the view. Create an IBOutlet property of the UILabel and connect them.
@property (nonatomic, strong) IBOutlet UILabel *passedObjLabel; // Interface Section
@synthesize passedObjLabel; //Implementation Section
9. Make a function that will update the contents in our Detailed view depending on what was selected from our tableview. For now, let's have a very simple content such as NSString.
- (void)updateMenuLabel:(NSString *)passedObject {
if (![self.passedObjLabel.text isEqualToString:passedObject]) {
self.passedObjLabel.text = passedObject;
}
}
Call this method in our viewDidLoad. You may choose whatever string you want to pass as a default value.
10. In our PopMenuViewController.m file, update table view's delegate method didSelectRowAtIndexPath. This will update the detailed view.
[self.detailViewController updateMenuLabel: [menu objectAtIndex:indexPath.row]];
Hit Run! Make sure you have your simulator in landscape. (If it's in portrait, press CMD + Left key)
But wait! There's more!
How come there's no Navigation Bar on top? I want one!
That's simple! Just edit didFinishLaunchingWithOptions method in our AppDelegate.m. And add a self.title = @"My Title" in the initialization function in our Menu and Detailed view controllers. You may also change the tint color of our Navigation Bar according to your preference, add navigation buttons, etc.
Hit Run!
Download sample code here.
Follow me on my new Twitter Account: @iosmadesimple
UISplitViewController Portrait
/*Note: This works in OS versions greater than or equal to iOS 6.0
OS versions less than iOS 6.0 has a different look in its UIPopoverController and doesn't support Landscape Orientation.*/
Please do continue from where we left off in our UISplitViewController Landscape Tutorial...
1. Add <UISplitViewControllerDelegate> in PopMenuDetailViewController interface header.
2. Add UIPopoverController property in PopMenuDetailViewController interface Section.
@property (strong, nonatomic) UIPopoverController *masterPopoverController;
Synthesize this property:
@synthesize masterPopoverController;
By the way, UIPopoverController is only available in iPad. If you want to make something like this in your iPhone application, you may use third party libraries like WEPopoverController. Download it from github.
3. Copy these SplitView Delegate methods in our PopMenuDetailViewController.m file.
4. In AppDelegate.m file, add this one-line code in didFinishLaunchingWithOptions method after initializing splitViewController.
splitViewController.delegate = detailVC;
5. Hit Run!
Download Sample Project here.
Thursday, May 9, 2013
3.5 and 4-Inch Screen Compatible
Now that iPhone 5 is in the market, we have to make sure that our apps are compatible to both 3.5-inch screens and 4-inch screens.
Here's how my simple app looks like. No functions, just a simple user interface. We expect that what we see in our Interface Builder, that's what it should also look like when we run it in 3.5-inch screens.
2. Change the Device from 3.5-inch to 4-inch. Make sure that you're in the menu of the iOS Simulator.
If you run the app again in 4-inch screen, this is how it will look like. Everything looks sort of okay. Some of you might really find your UI not in their right places when you run it in a 4-inch screen.
3. Go back to your project and click the view and look for the "Autosizing" function (violet box).
5. Next, click the Navigation Bar at the top with the segment control.
6. Same goes for the Label "Great Day," we want it to stick on top of the screen and extend its width depending on the size of the screen.
7. Next, tap the UIImageView.
That's easy!
Just check the "Use Autolayout" in our Interface Builder and it will automatically make our 3.5-inch screens compatible to 4-inch screens.Now, wait!
"Use Autolayout" is useful if the minimum OS requirement is iOS 6 and up. What if your application's minimum OS requirement is, for example, iOS 4.3 and up? You can't use the "Use Autolayout" because your app will definitely crash if you run it in <iOS 6 devices (even in your simulator).
This is what will happen if you use Autolayout and you're running your app in < i0S 6
The Good News is we have a simple solution for that!
1. First, I created a simple "Single View Application" project. View size is set to Retina 3.5 Full Screen.2. Change the Device from 3.5-inch to 4-inch. Make sure that you're in the menu of the iOS Simulator.
If you can't find the "Autosizing" function and you find this instead (Content Hugging Priority, Constratins, etc)
Go to the first tab of the "Utilities Area" (Right panel) and UNcheck the "Use Autolayout".
Once done unchecking the "Use Autolayout," you should be able to see the Autosizing function on the fifth tab.
Now, we're ready...
4. Click the View and change the Autosizing such that it will look like this. Tap or click all the vertical and horizontal lines in the inner and outer area of the box.
This means that we want our view to extend or expand its width and height depending on the size of the screen.
This means that we want our Navigation Bar to stick on top and extend its width depending on size of screen. We don't, or rather, we can't tap the inner vertical line of the box.. that means that we cannot change the height of the navigation bar. (And this is true since the height of the NavBar will always be 44.)
6. Same goes for the Label "Great Day," we want it to stick on top of the screen and extend its width depending on the size of the screen.
The Autosizing means that we want our UIImageView to expand its width and height while still sticking on the top. The left and right lines (outer area of the box) means that the UIImageView will be at the middle of the view.
8. For the two buttons, we want them to be at the bottom always, so...
9. Run your application again both in 3.5inch screens and 4-inch screens.
//4-inch Screen //3.5-inch Screen
Done!
Tuesday, May 7, 2013
SQLite Tutorial Part 2
Credits:
Most of the codes are from Techotopia and I refer this tutorial from their SQLite tutorialFor more information and detailed discussion, refer to their site. :D
This will be a continuation of our first SQLite Tutorial. We will only edit our previous project so I suggest you go over with SQLite Tutorial Part1 first.
- Delete the SQLite Tutorial app in your device / simulator since we are going to change the database.
- Delete the derived data of SQLiteTutorial on your Organizer (upper-right corner of Xcode IDE).
We will be covering the following topics:
1. Having multiple columns in our table named "SAMPLETABLE".
2. Getting all data in SAMPLETABLE (with multiple columns)
3. Search and return data from our SAMPLETABLE.
1. Let's edit our ViewController.xib such that we can enter information of a person.
Change the "keyboard type" of the textfield "Year" so that the user can only input numbers.
2. Create IBOutlet properties for these textfields (and be sure to remove the old IBOutlet we created before for it might cause our program to crash especially that it's no longer connected to any textfields in our view). Synthesize these properties on the Implementation Section.
//Interface Section
@property (strong, nonatomic) IBOutlet UITextField *firstNameTextField;
@property (strong, nonatomic) IBOutlet UITextField *lastNameTextField;
@property (strong, nonatomic) IBOutlet UITextField *courseTextField;
@property (strong, nonatomic) IBOutlet UITextField *yearTextField;
//Implementation Section
@synthesize firstNameTextField;
@synthesize lastNameTextField;
@synthesize courseTextField;
@synthesize yearTextField;
3. Connect these outlets to our objects in ViewController.xib.
Remember to set our File's Owner as the delegate of our new textfields (for dismissing the keyboard).
Right-click textField and point the delegate to File's Owner.
4. Remember the prepareDatabase() method we had before in ViewController.m? We'll change the part in our sql_stmt (SQL Statement).
Instead of:
"CREATE TABLE IF NOT EXISTS SAMPLETABLE (ID INTEGER PRIMARY KEY AUTOINCREMENT, MESSAGE TEXT)";
We'll have:
"CREATE TABLE IF NOT EXISTS SAMPLETABLE (ID INTEGER PRIMARY KEY AUTOINCREMENT, FirstName TEXT, LastName TEXT, Course TEXT, Year INT)";
5. We'll also edit the insert_stmt of our addTextToDatabase() method in ViewController.m. It should now look like this:
NSString *insertSQL = [NSString stringWithFormat: @"INSERT INTO SAMPLETABLE (FirstName, LastName, Course, Year) VALUES (\"%@\", \"%@\", \"%@\", \"%@\")", self.firstNameTextField.text, self.lastNameTextField.text, self.courseTextField.text, self.yearTextField.text];
Having multiple columns in our table named "SAMPLETABLE" - DONE!
6. In our getTextFromDB method in ListViewController.m, we'll just add a few lines inside our while loop. This loop while (sqlite3_step(statement) == SQLITE_ROW) will continue to go to the next row until there's no more row to go down with.
while (sqlite3_step(statement) == SQLITE_ROW) {
//NSString *personID = [[NSString alloc]
initWithUTF8String: (const char *) sqlite3_column_text(statement, 0)];
initWithUTF8String: (const char *) sqlite3_column_text(statement, 0)];
NSString * firstName = [[NSString alloc]
initWithUTF8String:(const char *) sqlite3_column_text(statement, 1)];
NSString * lastName = [[NSString alloc]
initWithUTF8String:(const char *) sqlite3_column_text(statement, 2)];
NSString * course = [[NSString alloc]
initWithUTF8String:(const char *) sqlite3_column_text(statement, 3)];
NSString * year = [[NSString alloc]
initWithUTF8String:(const char *) sqlite3_column_text(statement, 4)];
[list addObject:[NSString stringWithFormat:@"%@ %@ %@-%@", firstName, lastName, course, year]];
}
Again, the 0, 1, 2, 3, and 4 refers to the column number in our query results. So, IF our query statement is SELECT FirstName, LastName FROM SAMPLETABLE we should expect 2 columns in our query results, right? So to get the first name and last name of the result:
NSString * firstName = [[NSString alloc]
initWithUTF8String:(const char *) sqlite3_column_text(statement, 0)];
NSString * lastName = [[NSString alloc]
initWithUTF8String:(const char *) sqlite3_column_text(statement, 1)];
You may also choose to edit your tableView's cellForRowAtIndexPath method according to your preference.
Getting all data in SAMPLETABLE (with multiple columns) - DONE!
7. Create a new UIViewController class and import this new class to our AppDelegate. Allocate and initialize the new class and add it in tabbarController's array of viewControllers.
You must now have these lines in your appDelegate's didFinishLaunchingWithOptions method:
SearchViewController *searchVC = [[SearchViewController alloc] initWithNibName:@"SearchViewController" bundle:nil];
tabController. viewController = [[NSArray alloc] initWithObjects:self.viewController, listVC, searchVC, nil];
8. Add textfields to our SearchViewController.xib file so the user may choose to search by name, course, or year. Remember also to set the delegates of the textfields to the File's Owner (to dismiss the keyboard).
9. Add <UITextFieldDelegate> to SearchViewController's interface section. And the textFieldShouldReturn method to SearchViewController class.
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
[textField resignFirstResponder];
return NO;
}
10. Import sqlite3.h to SearchViewController class. Copy databasePath and myDatabase variables we had from ViewController and ListViewController classes. Create another NSMutableArray for the searchResults.
- #import <sqlite3.h>
- @property (strong, nonatomic) NSString *databasePath;
- @property (nonatomic) sqlite3 *myDatabase;
- @property (strong, nonatomic) NSMutableArray *searchResult;
- @synthesize databasePath;
- @synthesize myDatabase;
- @synthesize searchResult;
11. Let's create a findStudent method (IBAction). I basically copied the getTextFromDB() method from ListViewController but we will tweak it a little in the SQL Query. Connect this IBAction to the Search Button in our SearchViewController.xib.
- (IBAction) findStudent :(id)sender {
NSString *docsDir;
NSArray *dirPaths;
// Get the documents directory
dirPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
docsDir = dirPaths[0];
// Build the path to the database file
databasePath = [[NSString alloc]
initWithString: [docsDir stringByAppendingPathComponent:
@"sampleDatabase.db"]];
const char *dbpath = [databasePath UTF8String];
sqlite3_stmt *statement;
if (sqlite3_open(dbpath, &myDatabase) == SQLITE_OK)
{
NSString *querySQL = [NSString stringWithFormat: @"SELECT * FROM SAMPLETABLE WHERE %@", [self whereClause]]; //whereClause method in Step 12
const char *query_stmt = [querySQL UTF8String];
if (sqlite3_prepare_v2(myDatabase, query_stmt, -1, &statement, NULL) == SQLITE_OK) {
[searchResult removeAllObjects];
while (sqlite3_step(statement) == SQLITE_ROW) {
//NSString *personID = [[NSString alloc] initWithUTF8String: (const char *) sqlite3_column_text(statement, 0)];
NSString * firstName = [[NSString alloc]
initWithUTF8String:(const char *) sqlite3_column_text(statement, 1)];
NSString * lastName = [[NSString alloc]
initWithUTF8String:(const char *) sqlite3_column_text(statement, 2)];
NSString * course = [[NSString alloc]
initWithUTF8String:(const char *) sqlite3_column_text(statement, 3)];
NSString * year = [[NSString alloc]
initWithUTF8String:(const char *) sqlite3_column_text(statement, 4)];
[searchResult addObject:[NSString stringWithFormat:@"%@ %@ %@-%@", firstName, lastName, course, year]];
}
sqlite3_finalize(statement);
}
sqlite3_close(myDatabase);
}
//Result/s of the query will be shown in our Console Area.
for (int i = 0; i < [searchResult count]; i++) {
NSLog(@"Result: %@", [searchResult objectAtIndex:i]);
}
}
12. I created another method that will append and finalize the where clause of our SQL query.
- (NSString *) whereClause {
NSString *where = @"";
BOOL withWhereClause = NO;
if (self.firstNameTextField.text.length > 0) {
where = [where stringByAppendingString:[NSString stringWithFormat:@"FirstName=\"%@\"", self.firstNameTextField.text]];
withWhereClause = YES;
}
if (self.lastNameTextField.text.length > 0) {
if (withWhereClause) {
where = [where stringByAppendingString:@" and "];
}
where = [where stringByAppendingString:[NSString stringWithFormat:@"LastName=\"%@\"", self.lastNameTextField.text]];
withWhereClause = YES;
}
if (self.courseTextField.text.length > 0) {
if (withWhereClause) {
where = [where stringByAppendingString:@" and "];
}
where = [where stringByAppendingString:[NSString stringWithFormat:@"Course=\"%@\"", self.courseTextField.text]];
withWhereClause = YES;
}
if (self.yearTextField.text.length > 0) {
if (withWhereClause) {
where = [where stringByAppendingString:@" and "];
}
where = [where stringByAppendingString:[NSString stringWithFormat:@"Year=\"%@\"", self. yearTextField.text]];
}
//NSLog(@"where clause: %@", where);
return where;
}
Search and return data from our SAMPLETABLE - DONE!
Hit Run!
Download Sample Project here.
Subscribe to:
Posts (Atom)
Blocks From one Class to Another
One popular scenario in using blocks is when you need to update your view after a web service call is finished.
-
This is a tutorial request from Ze Kitez . As much as possible, I tried to do everything in Interface Builder, but sadly, some of the lo...
-
Tutorial Request from Mazrizan Kamisan We'll have the portrait view in another tutorial which will be a continuation of this landscape...
-
Credits: Most of the codes are from Techotopia and I refer this tutorial from their SQLite tutorial For more information and detaile...