Monday, February 4, 2013

How to make a YouTube app using MGBox and JSONModel

How to make a YouTube app using MGBox and JSONModel

This tutorial will take you through the process of creating a simple YouTube app for iOS. The app will be:

  • JSON API driven
  • using JSON data models
  • using advanced layout technics like 2 column layouts
  • feature asynchronious networking

At the end of this tutorial you will have created an app which allows the user to search YouTube and watch the videos, which the search yeilded; all wrapped in a nice sleek interface:

yt_run1

yt_playing

Let’s get started!

Xcode project setup

Let’s create the project and download the libraries you are going to need. Create a new XCode project and as a project template use “Master-Detail application”.

yt_proj_select

Call the new project “YTBrowser” and make sure you have ARC enabled and you are using Storyboard for your new Xcode project.

For your project you will need to download two 3rd party libraries:

  1. MGBox2 – a library that makes creating floating layouts a bit easier. If you haven’t encounter MGBox I assure you it’s an interesting threat.
  2. JSONModel – a fremework for creating JSON data models. It’ll help you consume the JSON API from YouTube.

Go ahed and download MGBox2 from https://github.com/sobri909/MGBox2 and copy the “MGBox” sub-folder into your Xcode project. You can also have a quick look at the demo projects coming with the MGBox2 download, but what you are going to do for your YouTube app is heavily based on one of the demos, so you can also skip this step.

For the MGBox2 to work you will need to add two Apple frameworks to your project, so select the “YTBrowser” project file in the project tree on the left, then make sure the “YTBrowser” target is selected in the middle column and then choose the “Build Phases” tab. Open up the “Link Binary With Library” strip and click on the “+” button. Add the two frameworks:

  • CoreText.framework
  • QuartzCore.framework

Now go to https://github.com/icanzilb/JSONModel and download the .zip file and copy the “JSONModel” sub-folder into your XCode project. As with MGBox2 there are a number of demos included within the download, so later on do give them a try.

Finally go ahead and in your Xcode project file tree create a new empty file group called “YouTubeModels” – here you will be creating your data models for the data you are going to consume from YouTube.

You Xcode project file tree should look something like that by now:

yt_proj_setup

With that the project setup is complete and you can start adding functionality!

Creating the “browser” screen

Select “MainStoryboard.storyboard” from your file list and center the view on the MasterViewController. You won’t need the table view controller that XCode created for you, because you will use MGBox2 for your layout. So, click once on the header of the “Master” screen and then press “Backspace” to delete this screen. Then drag in a new view controller from the objects panel and drop in on the empty space. You should have the storyboard look like this:

yt_new_viewcontroller

Now to embed the new view controller inside the navigation controller you already have – select the most left controller in your storyboard called “Navigation Controller” and switch to its outlets in the right Xcode panel, drag from the “root view controller” outlet to the brand new view controller you just added:

yt_connect_viewcontroller

Cool. Now double click the header of the empty view controller and enter “YouTube browser”.

Next take a “UIScrollView” control from the control list and drop it on the MasterViewController screen. If it doesn’t auto-resize to take up the whole screen space, resize the view manually. Your MasterViewController screen should look like this now:

yt_browser_setup

One last step – select the scroll view and from the 3rd tab (look at the picture below) change the class for the view to “MGScrollView” – this is the scroll view for MGBox elements.

yt_mgscroll

Finally you will need to reconnect the already existing MasterViewController class with the new view controller you just created in your storyboard.

Select the new view controller and open up the 3rd tab in the right panel – enter there “MasterViewController” like so:

yt_mastercontroller

Open “MasterViewController.h” and change the parent class of the controller to “UIViewController” like so:

@interface MasterViewController : UIViewController @end

Finally open up “MasterViewController.m” and first delete all the existing code you don’t need anymore and paste in this empty template:

#import "MasterViewController.h"#import "DetailViewController.h" #import "MGBox.h"#import "MGScrollView.h" @interface MasterViewController () {    IBOutlet MGScrollView* scroller;}@end @implementation MasterViewController - (void)viewDidLoad{    [super viewDidLoad]} @end

Now you will need to connect your IBOutlet to the MGScrollView. Open again the storyboard file and Ctrl-drag from your view controller to the scroll view:

yt_dragscroller

Congrats, your project is all setup! Let’s start coding!

Create the search bar

For your YouTube browser app you will need a search bar at the top of the UI – this is the perfect task to get started with MGBox2!

Open up “MasterViewController.m” and let’s add the search bar.

First in the interface declaration add one more ivar just below the IBOutlet declaration:

MGBox* searchBox;

This ivar will hold the reference to the search box.

At the end of the viewDidLoad method add these few lines to do the basic setup of the scroll view:

//setup the scroll viewscroller.contentLayoutMode = MGLayoutGridStyle;scroller.bottomPadding = 8;scroller.backgroundColor = [UIColor colorWithWhite:0.25 alpha:1];

This sets the scroll view to grid mode (because you will show two columns of results), then you set a bit of bottom padding, and finally a nice shade of gray for background.

Next you need a new MGBox for the search bar, keep adding the code snippets to viewDidLoad:

//setup the search boxsearchBox = [MGBox boxWithSize:CGSizeMake(320,44)];searchBox.backgroundColor = [UIColor colorWithWhite:0.5 alpha:1];

The [MGBox boxWithSize:] constructor takes in just a box size and creates a new box for you.

You will then want to add to the box view a search field. Takes a bit more code to setup all the field properties, but you just go ahead and copy the code below at the end of viewDidLoad:

//setup the search text fieldUITextField* fldSearch = [[UITextField alloc] initWithFrame:CGRectMake(4,4,312,35)];fldSearch.borderStyle = UITextBorderStyleRoundedRect;fldSearch.backgroundColor = [UIColor whiteColor];fldSearch.font = [UIFont systemFontOfSize:24];fldSearch.delegate = self;fldSearch.placeholder = @"Search YouTube...";fldSearch.text = @"pomplamoose";fldSearch.clearButtonMode = UITextFieldViewModeAlways;fldSearch.autocapitalizationType = UITextAutocapitalizationTypeNone;[searchBox addSubview: fldSearch];

This will be your search field. It’s all set-up, plus it has the default value of “pomplamoose” – that’s the name of one of my favorite bands, the app will be searching by default for their videos.

You probably have already noticed that the controller will also be the delegate of the text field – scroll to the top of the file and add the text field delegate protocol to the class interface like so:

@interface ViewController () <UITextFieldDelegate>

One last step – scroll back to viewDidLoad and add the line to actually add the search bar to the UI:

//add search box[scroller.boxes addObject: searchBox];

This is the method to add new boxes to the scroller view – you will use it also later when you show the video results on this screen.

Looks like your job here is done! Run the Xcode project and check out the iPhone simulator!

yt_firstrun

Did you notice what’s wrong with this screenshot? There’s no search bar of course.

Since MGBox does some quite complicated layouting, it does not do it automatically. It’s up to you to choose the precise moment you want the MGScrollView to recalculate its layout. So go ahead and at the end of viewDidLoad add a call to the layout method:

[scroller layoutWithSpeed:0.3 completion:nil];

This should nicely show up the new layout of the scroll view – i.e. show the search box. Run the project again:

yt_secondrun

Nice!

Handling searches

Let’s add the basic search logic. First of all go ahed and create a new method in your implementation:

-(void)searchYoutubeVideosForTerm:(NSString*)term{    NSLog(@"Searching for '%@' ...", term);}

That’ll be the search method placeholder. Right now it just prints the search term in the console, but you will also add the logic shortly.

First of all go and add an automated search for the default term, at the end of viewDidLoad add:

//fire up the first search[self searchYoutubeVideosForTerm: fldSearch.text];

This will fire off a search immediately after the app launches so the user does not stare at an empty screen. Sweet!

Then also go ahead and add text field methods to handle new searches:

//fire up API search on Enter pressed-(BOOL)textFieldShouldReturn:(UITextField *)textField{    [textField resignFirstResponder];    [self searchYoutubeVideosForTerm:textField.text];    return YES;}

That should just about do it. You can run the project again and you should be able to use the search field to invoke searches – so far just logging the searches. Give the app a try and check out the console output – here’s mine after trying the search field a few times:

2013-02-04 20:54:50.400 YTBrowser[80052:c07] Searching for 'pomplamoose' ...2013-02-04 20:54:58.842 YTBrowser[80052:c07] Searching for 'Kitten' ...2013-02-04 20:55:06.290 YTBrowser[80052:c07] Searching for 'Panda sneeze' ...

Now you can go on with implementing the searchYoutubeVideosForTerm: method that will try to fetch search results from YouTube.

For the actual network call to the YouTube servers you will use the JSONHTTPClient class that comes with the JSONModel framework. Scroll to the top of your .m file and add an import for the umbrella header of the JSONModel framework:

#import "JSONModelLib.h"

Now back to the searchYoutubeVideosForTerm: method. First let’s escape the search term (using the imperfect built-in escaping method), add just below the NSLog line:

//URL escape the termterm = [term stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];

Then build the GET call to youtube.com that returns search results in JSON format:

NSString* searchCall = [NSString stringWithFormat:@"http://gdata.youtube.com/feeds/api/videos?q=%@&max-results=50&alt=json", term];

And you are ready to make the HTTP request and fetch back the results:

[JSONHTTPClient getJSONFromURLWithString: searchCall      completion:^(NSDictionary *json, JSONModelError *err) {        //got JSON back        NSLog(@"Got JSON from web: %@", json)}];

[JSONHTTPClient getJSONFromURLWithString:completion:] does the heavy lifting for you and fires an async network request to the given URL and fetches back the JSON and tries to convert it to an NSDictionary for you. If anything goes wrong you will receive an error in the completion block.

Just after the NSLog line, go ahead and add this simple piece of code to handle errors:

if (err) {  [[[UIAlertView alloc] initWithTitle:@"Error"                              message:[err localizedDescription]                             delegate:nil                    cancelButtonTitle:@"Close"                    otherButtonTitles: nil] show];  return;}

So, if JSONHTTPClient sends you back an error, you will show an alert with the error message provided and exit the block. Easy!

Now run the project and voila! You should already be able to fire off searches towards YouTube and see the JSON responses coming back and being dumped in the console. Wow – that was easy right?

Creating a rock-solid JSON app

So, you get back an NSDictionary and you feel like you’re good to go. Truth however is that you have no notion of what kind of JSON comes back from this network call and you have no idea if it is what you expect or not, whether it’s a valid feed, and what type of data comes back.

That’s why you are going to create few data models to load the JSON into and make sure everything is as you expect it.

I always use the Charles Web Proxy to debug JSON APIs – it’s not obligatory for you to use it, but you can download the trial and give it a try – it’s best tool I found for the purpose.

Let’s dissect what kind of data comes back from the YouTube API – the response structure looks something like that:

yt_json1

If you look at this tree like representation of the JSON feed you will notice that most of the data is located in a key called “feed” and then you get tons of different keys containing different data. You are welcome to browse around and see what’s in there, but for this tutorial you will focus on the data found in the key “entry“. “entry” holds an array of search results and that’s what you will be interested in.

Let’s drill down and see how a single object in the “entry” array looks like:

yt_jsonentry

When you open up a single object from the “entry” array you will see it contains different sub-objects. That’s also the very reason why Charles is so awesome for debugging JSON APIs. You will be fetching couple of things out of each entry object:

  1. title/$t – the video title
  2. link – an array of links to different video formats
  3. media$group/media$thumbnail – an array of thumbnails of the video

These 3 should be enough to build your YouTube browser.

Let’s create a data model for a single video and then process the JSON feed.

Create a new Objective-C class inside the “YouTubeModels” group and call it “VideoModel“, make it inherit “JSONModel“.

Paste in the initial model code in VideoModel.h:

#import "JSONModel.h" @interface VideoModel : JSONModel @property (strong, nonatomic) NSString* title; @end

First you start with only having the title in your data model. Have a look again at the image above. The VideoModel class will map to a single entry in the “entry” array. However the “title” key is not located directly in an entry object. Therefore you will need to map the “title” property from your VideoModel to the path “title/$t” in your JSON object. Let’s do that!

Switch to VideoModel.m and let’s add the keyMapper method, which is the method to create mappings for non-matching property-to-key relations between your model and JSON object. It’s very easy, just add this code to the implementation file:

+(JSONKeyMapper*)keyMapper{    return [[JSONKeyMapper alloc] initWithDictionary:@{            @"title.$t": @"title",            }];}

As you can see the process if very easy, you just need to have the keyMapper method return a new JSONKeyMapper instance. Rememeber to create a JSONKeyMapper instance by calling the initWithDictionary: constructor and give it a dictionary of pairs in the format:

@"key path in your JSON object" : @"name of model property"

JSONModel will do all the work of drilling down your JSON structure, validating the structure and fetching the data into your model’s property. Nice!

So, that’s actually all the code you need to write to fetch and validate the “title” JSON value to your model. Notice no extra logic coding is required.

Let’s next take on the “link” value – it contains an array of links, so let’s fetch the array into a model property. Peak into the contents of the “link” array:

yt_linkjson

As you can see each object in the “link” array contains several keys, but you will need only the “href“. The “href” key of the first object in the array holds the link to the youtube website.

Let’s quickly build a data model that fetches the data from a single link object. Create a new Objective-C class in the “YouTubeModels” group and call it “VideoLink“, make it inherit “JSONModel“.

Open up “VideoLink.h”, delete all the code, and paste in:

#import "JSONModel.h" //1@protocol VideoLink @end @interface VideoLink : JSONModel //2@property (strong, nonatomic) NSURL* href; @end

First you declare a new protocol called VideoLink. You need to define a protocol matching the name of the class, because you will use a typing feature of JSONModel for the link array – you will see how in a moment. For now just keep the new VideoLink protocol in mind.

Second you define an “href” property – this will be automatically mapped to the “href” JSON key, you don’t need to add any extra code. Your VideoLink model is finished. Let’s see how do you use it in your VideoModel class.

Consider again the “entry” object JSON structure – you have a key called “link” and it is containing objects for which you already have a data model class “VideoLink“:

yt_linkstructure

So the additions you need to do to VideoModel.h are very simple:

//add an import to the new data model class#import "VideoLink.h" //add inside the interface declaration@property (strong, nonatomic) NSArray<VideoLink>* link;

First you import the new class, and then you add a new property to your VideoModel class.

The new property is an NSArray (matching the “link” array value in your JSON), but you also make this NSArray property conform to the “VideoLink” protocol. What this definition tells JSONModel is that you want to have an array model property, which holds instances of VideoLink objects. Sweet!

You actually don’t need to code anything else – JSONModel matches the “link” property to the “link” JSON key and then reads all elements in the array and converts them to VideoLink objects.

Now for the final step – you will need to combine what you just learned into one handy model property. You will need to fetch an array of video thumbnail objects and at the same time create mapping to the array.

Let’s have a look:

yt_thumbs

  1. You will need to create mapping for the key path “media$group/media$thumbnail” to a property called “thumbnail” in your model
  2. The mapped “media$thumbnail” array contains JSON objects, which you will write a data model class for

Just as before open up VideoModel.m and alter the keyMapper method, so that it also maps the media$thumbnail key:

+(JSONKeyMapper*)keyMapper{    return [[JSONKeyMapper alloc] initWithDictionary:@{            @"media$group.media$thumbnail":@"thumbnail",            @"title.$t": @"title",            }];}

Now create a new class for the new data model and call it “MediaThumbnail“, make it inherit “JSONModel“. Let’s look at the JSON structure for each thumbnail object:

[yt_thumbjson.png]

You won’t use all of those keys, but let’s capture them all in the data model just to show how you convert different data types. Open up “MediaThumbnail.h“, delete all code and paste in:

#import "JSONModel.h" @protocol MediaThumbnail @end @interface MediaThumbnail : JSONModel @property (strong, nonatomic) NSURL* url;@property (assign, nonatomic) int width;@property (assign, nonatomic) int height;@property (strong, nonatomic) NSString* time@end

That’s it. No implementation code needed. Note how JSONModel will convert the JSON values for you:

  • url – JSONModel will convert the incoming string to an NSURL object for you automatically
  • width and height – JSONModel will convert the incoming NSNumber objects to int values, because you declared int properties
  • time – the incoming NSString object will stay intact

Your MediaThumbnail data model class is finished. Let’s add the integration code to VideoModel.h

Open up VideoModel.h and add:

//at the top of the file#import "MediaThumbnail.h" //inside interface@property (strong, nonatomic) NSArray<MediaThumbnail>* thumbnail;

Just as before you declare a property, which will automatically fetch the objects from the mapped array and then these objects will be automatically converted to MediaThumbnail data models.

Your data models are ready! Notice that you actually did NOT need to write any implementation code!

Getting models out of JSON

So far your data models don’t do much. Let’s give ‘em some action!

Switch to MasterViewController.m and add these couple of lines:

//at the top #import "VideoModel.h" //inside the interface declaration - a new ivarNSArray* videos;

The “videos” array will hold the video results coming back from the YouTube API call.

Now find the completion block inside the searchYoutubeVideosForTerm: method. Add this just after the check for an error:

//initialize the modelsvideos = [VideoModel arrayOfModelsFromDictionaries:        json[@"feed"][@"entry"]        ]if (videos) NSLog(@"Loaded successfully models");

Note the [VideoModel arrayOfModelsFromDictionaries:] method (inherited from JSONModel) – it takes in a JSON array object and converts it into an NSArray of models. You pass in the “feed/entry” array and then VideoModel takes care to convert all of the objects inside the array to video models. And since JSONModel is then also cascading the models inside the VideoModel it takes care to automatically create links and thumb models.

If you run the Xcode project right now you should see the “Loaded successfully models” at the end of the output in the Xcode console.

Phew! It looks like it all works out well – you fetch the JSON from YouTube and also have your data models working.

What is left right now is to create a custom MGBox to show video thumbnails in your app.

MGBox for creating custom layouts

The custom MGBox you are going to build is actually heavily based on one of the examples coming with MGBox, so I won’t really go into detail about it, you can play with the code and figure out things yourselves.

Create a new class and call it “PhotoBox“, make it inherit “MGBox“. Inside the PhotoBox.h interface file delete the code and paste in:

#import "MGBox.h" @interface PhotoBox : MGBox +(PhotoBox *)photoBoxForURL:(NSURL*)url title:(NSString*)title; @property (strong, nonatomic) NSString* titleString; @end

You declare one new class method which will create a box with a given title and load an image from a given URL. Also a property to hold the box’s title.

Now onto the implementation. Open up PhotoBox.m and add a method, that is automatically called on MGBox instances when they are created:

- (void)setup {   //positioning  self.topMargin = 8;  self.leftMargin = 8//background  self.backgroundColor = [UIColor colorWithRed:0.94 green:0.94 blue:0.95 alpha:1]//shadow  self.layer.shadowColor = [UIColor colorWithWhite:0.12 alpha:1].CGColor;  self.layer.shadowOffset = CGSizeMake(0, 0.5);  self.layer.shadowRadius = 1;  self.layer.shadowOpacity = 1;  self.layer.rasterizationScale = 1.0;  self.layer.shouldRasterize = YES;}

What this code does is:

  1. setup margin around the boxes – since they are floating UI elements they need margins set
  2. set the background of the boxes
  3. and finally add nice shadow effect as an eye candy

Next let’s quickly create the factory method for your custom PhotoBox. Add this method:

+ (PhotoBox *)photoBoxForURL:(NSURL*)url title:(NSString*)title{  // box with photo number tag  PhotoBox *box = [PhotoBox boxWithSize:CGSizeMake(150,100)];  box.titleString = title; }

The code inside the body so far does only 2 things – create a vanilla box with a pre-set size and then set the titleString property to the given title. Now you can also go on with adding a spinner to the box and fire-off a network call to fetch the image at the given URL.

Let’s add the spinner code at the end of the method so far:

// add a loading spinnerUIActivityIndicatorView *spinner = [[UIActivityIndicatorView alloc]  initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite]; spinner.center = CGPointMake(box.width / 2, box.height / 2); spinner.autoresizingMask = UIViewAutoresizingFlexibleTopMargin  | UIViewAutoresizingFlexibleRightMargin  | UIViewAutoresizingFlexibleBottomMargin  | UIViewAutoresizingFlexibleLeftMargin;spinner.color = UIColor.lightGrayColor; [box addSubview:spinner];[spinner startAnimating];

The code just creates a new UIActivityIndicatorView and centres it within the box.

Keep adding code to the method, next we set a code block to be executed the first the box is added to the layout:

//do the photo loading async__weak id wbox = box;box.asyncLayoutOnce = ^{  [wbox loadPhotoFromURL:url];};

The asyncLayoutOnce block will be invoked once and in a separate thread.

Finally your method will also need to return the new box instance, add as a last line in this method:

return box;

You now only need to write the method that fetches the image from the web. Add this final method to the class body:

- (void)loadPhotoFromURL:(NSURL*)url{   // fetch the remote photo  NSData *data = [NSData dataWithContentsOfURL:url]}

Alright, that should about do it as for fetching image data from Internet. Now you need to convert the data to an UIImage and add it to the box.

Add some more code to the method:

  // do UI stuff back in UI land  dispatch_async(dispatch_get_main_queue(), ^{     // ditch the spinner    UIActivityIndicatorView *spinner = self.subviews.lastObject;    [spinner stopAnimating];    [spinner removeFromSuperview]// failed to get the photo?    if (!data) {    self.alpha = 0.3;    return;    }     //show the image   });

Since you now need to work on the UI, you dispatch a new code block to be executed on the main app thread.

First you remove the last added view to the box – the spinner; once the network call is done you don’t need it at all.

Then you check if “data” holds any data – if not then you “disable” the box by setting its alpha to 0.3.

In the end if all was good you will add the image to the box. Just below the “//show the image” comment add:

// got the photo, so lets show itUIImage *image = [UIImage imageWithData:data];UIImageView *imageView = [[UIImageView alloc] initWithImage:image];[self addSubview:imageView]; imageView.size = self.size;imageView.alpha = 0;imageView.autoresizingMask = UIViewAutoresizingFlexibleWidth| UIViewAutoresizingFlexibleHeight; // fade the image in[UIView animateWithDuration:0.2 animations:^{imageView.alpha = 1;}];

You create an UIImage object, and then an image view out of it too. After some positioning you add it to the box and finally you call a fade in animation to make the image loading nicer.

The only thing left is to add the video title to the box – you have the string stored in the “titleString” property, so you need to only create a new UILabel and add it to the box. You will also add some shadow to the label for eye candy. Still within the code block where you pasted code before add this:

    UILabel* label = [[UILabel alloc] initWithFrame:CGRectMake(0,0,self.frame.size.width,20)];    label.backgroundColor = [UIColor clearColor];    label.text = self.titleString;    label.font = [UIFont boldSystemFontOfSize:16.0];    label.textColor = [UIColor whiteColor];    [self addSubview:label];     label.layer.shadowColor = [UIColor colorWithWhite:0.12 alpha:1].CGColor;    label.layer.shadowOffset = CGSizeMake(0, 0.5);    label.layer.shadowRadius = 1;    label.layer.shadowOpacity = 1;    label.layer.rasterizationScale = 1.0;    label.layer.shouldRasterize = YES;

Alright! Now you have a fancy box view to show video thumbnails for your video search results.

Let’s show the boxes in the app.

Visualizing the results

Switch to “MasterViewController.m” and import the new class at the top of the file:

#import "PhotoBox.h"

Then find the line saying:

if (videos) NSLog(@"Loaded successfully models");

This is the moment when you have the JSON feed fetched and your models are created, that’s the place where you want to also show the results on screen.

Add this line, which calls the method to render the results:

//show the videos[self showVideos];

And finally add the showVideos method to your class:

-(void)showVideos{    //clean the old videos    [scroller.boxes removeObjectsInRange:NSMakeRange(1, scroller.boxes.count-1)]}

First you remove all boxes from the scroll view, except the very first one. Effectively you remove all video search results, but you leave the search bar box. Nice :]

Then add the code to add boxes for each video result:

//add boxes for all videosfor (int i=0;i<videos.count;i++) {     //get the data    VideoModel* video = videos[i];    MediaThumbnail* thumb = video.thumbnail[0]//create a box    PhotoBox *box = [PhotoBox photoBoxForURL:thumb.url title:video.title];    box.onTap = ^{        [self performSegueWithIdentifier:@"videoViewSegue" sender:video];    }//add the box    [scroller.boxes addObject:box];}

The code just loops over the videos array and does for each video:

  1. Get the current VideoModel instance out of videos
  2. Get the MediaThumbnail instance
  3. Create a box providing the video thumbnail URL and the video title
  4. Set the code block to execute when the box is tapped
  5. Finally add the new box to the scroll view

In the end just add the code to re-layout the scroll view (outside of the loop please):

//re-layout the scroll view[scroller layoutWithSpeed:0.3 completion:nil];

Note how when you tap a video you invoke a certain segue from your storyboard by its name (the segue still does not exist, don’t worry). You also pass the video data model to the segue, this way you pass it to the detail view controller.

If you run the project now you should see something like this:

yt_run1

Awesome! Try entering other search terms and check if the app runs fine:

yt_run2

Showing YouTube videos in your app

There’s however still one problem. If you tap on a video the app crashes because you still don’t have the segue in your storyboard.

Open MainStoryboard.storyboard and Ctrl-drag from the MasterViewController class to the empty Detail View Controller screen. From the popup menu choose “push” to create a new push segue.

yt_pushsegue

Now select the new segue in the storyboard:

yt_selsegue

and give it the identifier of “videoViewSegue”:

yt_pushid

Now drag a UIWebView from the object palette onto the Detail View Controller and resize it so it takes up the whole screen. You are quickly going to add the code to the view controller to just show the video from YouTube in the Detail View Controller.

First open up DetailViewController.h and replace the existing class interface with:

#import "VideoModel.h" @interface DetailViewController : UIViewController @property (strong, nonatomic) VideoModel* video; @end

You need one property which holds a VideoModel instance of the video you will show in the web view. Switch now to DetailViewController.m and again delete all the code that was pre-written by Xcode.

Paste in this basic implementation:

#import "DetailViewController.h" @interface DetailViewController (){    IBOutlet UIWebView* webView;}@end @implementation DetailViewController -(void)viewDidLoad{    [super viewDidLoad]} @end

You have one outlet and an empty viewDidLoad method. Open up your storyboard and Ctrl-drag from your Detail View Controller object to the webview, select “webView” from the popup menu:

yt_webview

Go back to the code file and add in viewDidLoad:

VideoLink* link = self.video.link[0];

This will get the first link from the links array – this is the URL of the video on YouTube. This one you will need to embed in your web view.

However the whole URL is not good for embedding the video, you will need only the video id, which is found in the “v” parameter in the URL. This short code will take the video id from the URL you have:

NSString* videoId = nilNSArray *queryComponents = [link.href.query componentsSeparatedByString:@"&"];for (NSString* pair in queryComponents) {    NSArray* pairComponents = [pair componentsSeparatedByString:@"="];    if ([pairComponents[0] isEqualToString:@"v"]) {        //fetch the video id and exit the loop        videoId = pairComponents[1];        break;    }}

After the video id is fetched from the video URL, let’s add a little check to handle errors and some logging to see what we’ve got back:

if (!videoId) {    [[[UIAlertView alloc] initWithTitle:@"Error" message:@"Video ID not found in video URL" delegate:nil cancelButtonTitle:@"Close" otherButtonTitles: nil]show];    return;} NSLog(@"Embed video id: %@", videoId);

Alright! You’ve got the video id, you can now just load some HTML in the web view to embed the video.

NSString *htmlString = @"<html><head>\<meta name = \"viewport\" content = \"initial-scale = 1.0, user-scalable = no, width = 320\"/></head>\<body style=\"background:#000;margin-top:0px;margin-left:0px\">\<iframe id=\"ytplayer\" type=\"text/html\" width=\"320\" height=\"240\"\src=\"http://www.youtube.com/embed/%@?autoplay=1\"\frameborder=\"0\"/>\</body></html>"; htmlString = [NSString stringWithFormat:htmlString, videoId, videoId][webView loadHTMLString:htmlString baseURL:[NSURL URLWithString:@"http://www.youtube.com"]];

First you build up the HTML string to show the video, and then you call loadHTMLString: baseURL: to load it in the web view.

And that’s pretty much everything!

Just switch back to MasterViewController.m – you still need to pass the selected video to the detail view controller.

Hook to the prepareForSegue: method – just add it to your controller’s code:

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{    DetailViewController* controller = segue.destinationViewController;    controller.video = sender;}

That should pass the video to the detail view controller nicely.

Run the project and tap a video.

Note: If you have a crash at this point do the following. Xcode probably inserted an outlet to a label in the detail view controller, that you don’t need anymore at this point. In the storyboard file select the detail view controller, and check out it’s outlets – you might see this:

yt_label

Just click on the little “x” button to the left of the word “Label” – that should remove the obsolete outlet.

Now your app is fetching video results from YouTube and you can also play the videos in an embedded web view. Sweet!

yt_playing

That’s a wrap for this tutorial, I hope you enjoyed it!

Where to go from here?

Well, you can browse through the output of the YouTube search API and see how you can extend your data models to fetch more details per result.

Or you can also have a look at the included with JSONModel demos and see if you can come up with an interesting YouTube – ServiceX mashup.

The complete demo project you can also download from GitHub: https://github.com/JSONModel/YouTubeBrowserDemo

In any case drop me a comment, let me know what you think about the tutorial!

Cheers, Marin

The post was originally published on the following URL: http://www.touch-code-magazine.com/how-to-make-a-youtube-app-using-mgbox-and-jsonmodel/

  ·



Source : touch-code-magazine[dot]com

0 comments:

Post a Comment