We are going to build an app that will read and write to a Laravel API.
Create a new Laravel app called noteApp.
composer create-project laravel/laravel noteApp --prefer-dist
Add the Way Generators
Update composer.
composer update
Turn on MAMP and test the site.
open /app/config/database.php
and set you mysql credentials. We are using MAMP so just use the defaults. Create or use an existing database locally.
Note: If you are using a existing database its good practice to set a prefix in the
database.php
.
You should have something like this.
'mysql' => array(
'driver' => 'mysql',
'host' => 'localhost',
'database' => 'app',
'username' => 'root',
'password' => 'root',
'charset' => 'utf8',
'collation' => 'utf8_unicode_ci',
'prefix' => 'notes_',
),
We are going to create an app that creates notes, so we want to create a new resource call note
.
Run.
php artisan generate:resource note --fields="body:text"
Open app/routes.php
and a the new resource.
Route::Resource('notes', 'NotesController');
Test your new routes. In terminal type
php artisan routes
Your should see all the new verbs for creating/updating/deleting notes
.
Open app/controllers/notesController.php
and the the functionality for creating updating and deleting notes.
Your file should look like this.
<?php
class NotesController extends \BaseController {
// GET
public function index() {
return Response::json(['notes'=>Note::all()]);
}
// POST
public function store() {
$note = new Note();
$note->body = Input::get('body', 'empty note');
$note->save();
return Response::json($note);
}
// GET
public function show($id) {
$note = Note::find($id);
return $note;
}
// PUT
public function update($id) {
$note = Note::find($id);
if(Input::has('body')) {
$note->body = Input::get('body', 'empty note');
$note->save();
return Response::json(['note'=>$note, 'message'=>'Note Updated']);
}
return Response::json(['note'=>$note, 'message'=>'No Body Sent']);
}
// DELETE
public function destroy($id) {
$note = Note::find($id);
$note->delete();
return Response::json($note);
}
}
Note: Most browsers/clients do not understand the PUT/UPDATE/DELETE methods. Laravel will accept a parameter named
_method
and interrupt this with the correct function to call. i.e_method="PUT"
.
- In Chrome get the app called PostMan this is a nice little REST app for Chrome that allows you to test all the verbs for you API.
Looking at your routes php artisan routes
you can see how to all the URIs.
POST a new Note
. Add the key body
and value my new note
.
http://laraveldemo:8888/notes
GET all the Notes
.
http://localhost:8888/notes
DELETE a Note
.
http://localhost:8888/notes/1
Open XCode and create a new IOS project and select Single View Application. Name is NoteApp, select iPhone and save.
Delete everything in the Main.storyboard
. Select the file and hit (⌘+a) then (delete)
Delete the ViewController.h
and ViewController.m
files.
File new. (⌘+n) Cocoa Touch -> Objective-C class Click Next Class: NotesController Subclass of: UITableViewController Click Next Create
Click on the Main.storyboard
and drag a new Table View Controller
to the stage. With the Controller selected third button at the top of the inspector. Set the Custom Class
to NotesController
. Now in the top menu click Editor -> Embed In -> Navigation Controller
Hit (⌘+r) and test the app. Everything should be connected and you should see a TableViewController in the simulator.
We are going to write all the loading functions in the AppDelegate
this is good practice so that we can access these functions throughout the app.
In the AppDelegate.h
add #define BASE_URL @"http://localhost:8888"
this will save us from having to type this a bunch and easy to set for production.
Singleton AppDelegate
This is a nice helper function to get the AppDelegate
instance. Create this static method.
AppDelegate.h
+(AppDelegate*)getInstance;
Load Notes
+(AppDelegate*)getInstance {
return (AppDelegate*)[[UIApplication sharedApplication] delegate];
}
We can now call this simply by typing [AppDelegate getInstance]
Lets make a function that will load any url with a http method. We also want to have a callback for when the data is done loading. We are going to use a Block
.
in your AppDelegate.h
create a typedef
of the Block
.
typedef void(^RequestBlock)(NSDictionary*data);
This function can take a url that we want to load, params saved in a dictionary as key value pairs and a callback function. Add this to AppDelegate.h
-(void)makeRequest:(NSString*)urlString method:(NSString*)method params:(NSDictionary*)params onComplete:(RequestBlock)callback;
AppDelegate.m
-(void)makeRequest:(NSString*)urlString method:(NSString*)method params:(NSDictionary*)params onComplete:(RequestBlock)callback {
// create the url
NSURL * url = [NSURL URLWithString:[NSString stringWithFormat:@"%@/%@", BASE_URL, urlString]];
NSMutableURLRequest * request = [[NSMutableURLRequest alloc] initWithURL:url];
// set the method (GET/POST/PUT/UPDATE/DELETE)
[request setHTTPMethod:method];
// if we have params pull out the key/value and add to header
if(params != nil) {
NSMutableString * body = [[NSMutableString alloc] init];
for (NSString * key in params.allKeys) {
NSString * value = [params objectForKey:key];
[body appendFormat:@"%@=%@&", key, value];
}
[request setHTTPBody:[body dataUsingEncoding:NSUTF8StringEncoding]];
}
// submit the request
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
// do we have data?
if(data && data.length > 0) {
NSDictionary * json = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:nil];
// if we have a block lets pass it
if(callback) {
callback(json);
}
}
}];
}
Now lets test this function. In AppDelegate.m
didFinishLaunchingWithOptions
add the function to load all the notes. Note: If we have no params to pass just pass nil
.
[self makeRequest:@"notes" params:nil method:@"GET" onComplete:^(NSDictionary *data) {
NSLog(@"Json Loaded: %@", data);
}];
Run & Build. You will see output in the console of the a NSDictionary
of all the notes.
First include the AppDelegate
in the NotesController
NotesController.h
#import "AppDelegate.h"
We want to have a NSMutableArray
of all the notes.
NotesController.h
@property (strong, nonatomic) NSMutableArray * notes;
NotesController.m
@synthesize notes;
Load the notes when the view loads.
- (void)viewDidLoad {
[super viewDidLoad];
[[AppDelegate getInstance] makeRequest:@"notes" params:nil method:@"GET" onComplete:^(NSDictionary *data) {
if([data objectForKey:@"notes"]) {
notes = [NSMutableArray arrayWithArray:[data objectForKey:@"notes"]];
// now reload the tableview
[self.tableView reloadData];
}
}];
}
Setup The TableView Now that we have the data loaded we can set the sections and number of rows.
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return notes ? 1 : 0; // safe way to not load rows if notes is nil
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return notes.count;
}
Setup the cell.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString * cellID = @"NOTES_CELL";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellID];
if(cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:cellID];
}
// get the note
NSDictionary * note = [notes objectAtIndex:indexPath.row];
// create a date formatter so we can display the date
NSDateFormatter * df = [[NSDateFormatter alloc] init];
[df setDateFormat:@"yyyy-MM-dd H:mm:ss"];
// make a date object from the timestamp
NSDate * date = [df dateFromString:[note objectForKey:@"created_at"]];
// change the format weekday/month/day/year
[df setDateFormat:@"EE MMM d yyyy"];
NSString * dateStr = [df stringFromDate:date];
// change the format hour:min
[df setDateFormat:@"h:mm a"];
NSString * timeStr = [df stringFromDate:date];
// update the cell
cell.textLabel.text = dateStr;
cell.detailTextLabel.text = timeStr;
return cell;
}
At this point we are loading the Notes
and able to display them in our tableview -Yeah! Now we need to make some notes.
Lets add right bar button item that will launch a note creator view.
Add the bar button
In the viewDidLoad
add the following code.
UIBarButtonItem * noteButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemCompose
target:self
action:@selector(openNoteCreator:)];
self.navigationItem.rightBarButtonItem = noteButton;
And add this function
-(void)openNoteCreator:(id)sender {
}
Run the app and you will see a compose icon in the top right. Let create the NoteCreatorController
. Like before create a new class that subclasses a UIViewController
.
Connect the NoteCreatorController
in the NotesController
.
Import the class
** NotesController.m**
#import "NoteCreatorController.h"
Update the openNoteCreator
method.
-(void)openNoteCreator:(id)sender {
NoteCreatorController * noteVC = [[NoteCreatorController alloc] init];
[self.navigationController presentViewController:noteVC animated:YES completion:nil];
}
We need a few things in the class. Buttons to close the modal and save the note as well a textview to write the note.
Create a close function.
-(void)close {
[self dismissViewControllerAnimated:YES completion:nil];
}
Create a save function (we will fill this in later)
-(void)save {
}
Create button to call these two methods. You will create them in the viewDidLoad
method.
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
UIToolbar * toolbar = [[UIToolbar alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, 64)];
// close button
UIBarButtonItem * closeBtn = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemCancel target:self action:@selector(close)];
// spacer
UIBarButtonItem * spacer = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil];
// save button
UIBarButtonItem * saveBtn = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemSave target:self action:@selector(save)];
// set the items for toolbar
toolbar.items = @[closeBtn, spacer, saveBtn];
[self.view addSubview:toolbar];
}
The TextView
We need a UITextView
and have this controller respond to the UITextView
protocol.
NoteCreatorController.h
@interface NoteCreatorController : UIViewController <UITextViewDelegate>
@property (strong, nonatomic) UITextView * textView;
@end
NoteCreatorController.m
@synthesize noteTextView;
Add this to the viewDidLoad
method.
noteTextView = [[UITextView alloc] initWithFrame:CGRectMake(20, 70, self.view.frame.size.width-40, 170)];
noteTextView.backgroundColor = [UIColor lightGrayColor];
noteTextView.delegate = self;
[self.view addSubview:noteTextView];
// auto launch the keyboard
[noteTextView becomeFirstResponder];
Save Data
Lets fill out the save
method. First import the AppDelegate
in you NoteCreatorController.m
. We want to dismiss the keyboard, grab the text from the UITextView
and save it with our makeRequest
method.
Save the data:
-(void)save {
// if the keyboard is up dismiss it
if([noteTextView isFirstResponder]) {
[noteTextView resignFirstResponder];
}
NSString * body = noteTextView.text;
// save this data
[[AppDelegate getInstance] makeRequest:@"notes"
method:@"POST"
params:@{@"body": body}
onComplete:^(NSDictionary *data) {
// we need to dismiss the view controller
// and update the tableview in NotesController
}];
}
Close the Notes Creator & Update TableView
In order to update the NotesController
we need a reference to the instance of this controller. Lets create a function in NotesController
that will update the UITableView
with a new note.
In NotesController.h
add:
-(void)addNewNote:(NSDictionary*)note;
In NotesController.m
add:
code
In the NoteCreatorController
we need a weak
reference to the NotesController
.
In NoteCreatorController.h
add:
@property (weak, nonatomic) NotesController * notesControllerRef;
In NoteCreatorController.m
add:
@synthesize notesControllerRef;
In the openNoteCreator
method add the reference.
-(void)openNoteCreator:(id)sender {
NoteCreatorController * noteVC = [[NoteCreatorController alloc] init];
noteVC.notesControllerRef = self;
[self.navigationController presentViewController:noteVC animated:YES completion:nil];
}
Now when we close the NoteCreatorController
we can call the addNewNote
when the animation is complete. Our save
method now looks like this.
-(void)save {
// if the keyboard is up dismiss it
if([noteTextView isFirstResponder]) {
[noteTextView resignFirstResponder];
}
NSString * body = noteTextView.text;
// save this data
[[AppDelegate getInstance] makeRequest:@"notes"
method:@"POST"
params:@{@"body": body}
onComplete:^(NSDictionary *data) {
// we need to dismiss the view controller
// and update the tableview in NotesController
[self dismissViewControllerAnimated:YES completion:^{
[notesControllerRef addNewNote:[data objectForKey:@"note"]];
}];
}];
}
Looks good we are now adding a new cell and saving our new note to the database.
Lets add a way to edit and delete a note. First a weak
reference of a NSDictionary
note in the NoteCreatorController
. We do this so that when we tap on a TableViewCell
we pass the note and can update the text in the NoteCreatorController
.
In NoteCreatorController.h
@property (weak, nonatomic) NSDictionary * noteRef;
In NoteCreatorController.m
@synthesize noteRef;
We want to modify the ViewDidLoad
to check if we have a noteRef
. If we have a note we set the text else we show the keyboard.
if(noteRef == nil) {
[noteTextView becomeFirstResponder];
}
else {
noteTextView.text = noteTextView.text;
}
Now in our NotesController
we add the method.
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSDictionary * note = [notes objectAtIndex:indexPath.row];
NoteCreatorController * noteVC = [[NoteCreatorController alloc] init];
noteVC.notesControllerRef = self;
noteVC.noteRef = note;
[self.navigationController presentViewController:noteVC animated:YES completion:nil];
}
Create a update Cell method in NoteController
.
-(void)updateNote:(NSDictionary *)note {
NSInteger foundIndex = -1;
// find the note we just updated
for (NSInteger i=0; i<notes.count; i++) {
NSDictionary * n = [notes objectAtIndex:i];
if([[n objectForKey:@"id"] isEqualToString:[note objectForKey:@"id"]]) {
foundIndex = i;
break;
}
}
// did we find a note - replace with updated note
if(foundIndex != -1) {
[notes replaceObjectAtIndex:foundIndex withObject:note];
[self.tableView reloadRowsAtIndexPaths:@[[NSIndexPath indexPathForRow:foundIndex inSection:0]] withRowAnimation:UITableViewRowAnimationFade];
}
}
Now when we open this Note
in the NotesCreatorController
we want to update the note and not save it as a new note. Lets modify our save
function to the following.
-(void)save {
// if the keyboard is up dismiss it
if([noteTextView isFirstResponder]) {
[noteTextView resignFirstResponder];
}
NSString * body = noteTextView.text;
// if we have a note ref then update else save the note
if(noteRef) {
// We first create the path notes/{note id}
// The verbs PUT/UPDATE/DELETE are not supported
// with most browsers/clients so we need to pass
// a _method. Laravel knows how to interespt this
// into the correct method.
NSString * noteURL = [NSString stringWithFormat:@"notes/%@", [noteRef objectForKey:@"id"]];
[[AppDelegate getInstance] makeRequest:noteURL
method:@"POST"
params:@{@"body": body, @"_method": @"PUT"}
onComplete:^(NSDictionary *data) {
// we need to dismiss the view controller
// and update the tableview in NotesController
[self dismissViewControllerAnimated:YES completion:^{
[notesControllerRef updateNote:[data objectForKey:@"note"]];
}];
}];
}
else {
[[AppDelegate getInstance] makeRequest:@"notes"
method:@"POST"
params:@{@"body": body}
onComplete:^(NSDictionary *data) {
// we need to dismiss the view controller
// and update the tableview in NotesController
[self dismissViewControllerAnimated:YES completion:^{
[notesControllerRef addNewNote:[data objectForKey:@"note"]];
}];
}];
}
}
Note: Most browsers/clients do not understand the PUT/UPDATE/DELETE methods. Laravel will accept a parameter named
_method
and interrupt this with the correct function to call. i.e_method="PUT"
.
Awesome, we are now updating our Notes, time to delete a note. Lets add a delete button to our toolbar in the NotesCreatorController
and create a deleteNote
method.
In NotesCreatorController.m
UIBarButtonItem * deleteBtn = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemTrash target:self action:@selector(deleteNote)];
We also need a removeNote
in our NotesController
. Create this function.
** NotesController.h**
-(void)removeNote:(NSDictionary*)note;`
** NotesController.m**`
-(void)removeNote:(NSDictionary *)note {
NSInteger foundIndex = -1;
// find the note we just updated
for (NSInteger i=0; i<notes.count; i++) {
NSDictionary * n = [notes objectAtIndex:i];
if([[n objectForKey:@"id"] isEqualToString:[note objectForKey:@"id"]]) {
foundIndex = i;
break;
}
}
// did we find a note - remove this note
if(foundIndex != -1) {
[notes removeObjectAtIndex:foundIndex];
[self.tableView deleteRowsAtIndexPaths:@[[NSIndexPath indexPathForRow:foundIndex inSection:0]] withRowAnimation:UITableViewRowAnimationFade];
}
}
Back in the NotesCreatorController.m
we need to create the deleteNote
function and call the removeNote
method in our UITableView
. We can pretty much copy and past from our update method and change it to DELETE
.
-(void)deleteNote {
NSString * noteURL = [NSString stringWithFormat:@"notes/%@", [noteRef objectForKey:@"id"]];
[[AppDelegate getInstance] makeRequest:noteURL
method:@"POST"
params:@{@"_method": @"DELETE"}
onComplete:^(NSDictionary *data) {
// we need to dismiss the view controller
// and update the tableview in NotesController
[self dismissViewControllerAnimated:YES completion:^{
[notesControllerRef removeNote:[data objectForKey:@"note"]];
}];
}];
}
At this point we should be creating, updating, and deleting notes. This is a good start for making an app connected to a REST API. You can build on this and really make something awesome!
Thanks, T