Laravel-IOSTutorial
Laravel-IOSTutorial copied to clipboard
Laravel & IOS
We are going to build an app that will read and write to a Laravel API.
Create App
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.
Setup Database
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_',
),
Build a Resource
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
_methodand interrupt this with the correct function to call. i.e_method="PUT".
Test the API.
- 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
IOS
Open XCode and create a new IOS project and select Single View Application. Name is NoteApp, select iPhone and save.
Setup the Main.storyboard
Delete everything in the Main.storyboard. Select the file and hit (⌘+a) then (delete)
Delete the ViewController.h and ViewController.m files.
Create NotesController
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.
Load the data from the server
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.
Load Notes - NotesController
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.
Create 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];
}
Build the note creator
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.
Edit/Delete a note
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
_methodand 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