Make Better Things



I like to make better things.

Play Video on iPhone and iPad

On the iPhone, all videos must be played full-screen. However, on the iPad, this rule has been relaxed — you can now embed videos within your iPad applications. This makes it possible for you to embed more than one video in any View window. This article discusses the two classes supported in the iPhone SDK for video playback.

Playing Video on the iPhone

Using Xcode, create a new View-based Application (iPhone) project and name it PlayVideo. Drag a sample video into the Resources folder of your Xcode project

Adding a video to resource folder

Adding a video to resource folder

To playback video on your iPhone application, you need to add the MediaPlayer framework to your project. Right-click on Frameworks folder and add the MediaPlayer.framework to your project

Adding MediaPlayer framework to project

Adding MediaPlayer framework to project

Put below code in PlayVideoViewController.m:


#import "PlayVideoViewController.h"
#import <MediaPlayer/MediaPlayer.h>

@implementation PlayVideoViewController

- (void)viewDidLoad {

    NSString *url = [[NSBundle mainBundle]
        pathForResource:@"Stock_Footage_Demobroadband"
                 ofType:@"mp4"];

    MPMoviePlayerController *player =
        [[MPMoviePlayerController alloc]
            initWithContentURL:[NSURL fileURLWithPath:url]];

    [[NSNotificationCenter defaultCenter]
        addObserver:self
           selector:@selector(movieFinishedCallback:)
               name:MPMoviePlayerPlaybackDidFinishNotification
             object:player];

    //---play movie---
    [player play];
    [super viewDidLoad];
}

- (void) movieFinishedCallback:(NSNotification*) aNotification {
    MPMoviePlayerController *player = [aNotification object];
    [[NSNotificationCenter defaultCenter]
        removeObserver:self
                  name:MPMoviePlayerPlaybackDidFinishNotification
                object:player];
    [player autorelease];
}

Basically, we are using the MPMoviePlayerController class to control the playback of a video:

MPMoviePlayerController *player =
[[MPMoviePlayerController alloc]
            initWithContentURL:[NSURL fileURLWithPath:url]];

We then use the NSNotificationCenter class to register a notification so that when the movie is done playing (either the movie ends or the user taps on the Done button located on the top left corner of the screen):

[[NSNotificationCenter defaultCenter]
addObserver:self
           selector:@selector(movieFinishedCallback:)
               name:MPMoviePlayerPlaybackDidFinishNotification
             object:player];

When the movie stops playing, we should unregister the notification and then release the player object:

- (void) movieFinishedCallback:(NSNotification*) aNotification {
 MPMoviePlayerController *player = [aNotification object];
 [[NSNotificationCenter defaultCenter]
 removeObserver:self
 name:MPMoviePlayerPlaybackDidFinishNotification
 object:player];
 [player autorelease];
}

Now we can test our app by command + return.

All movies played full-screen on the iPhone. Observe that by default, the movie is always played in landscape mode. To force the movie to be played in portrait mode, you can set the orientation of the player, like this:

[player setOrientation:UIDeviceOrientationPortrait animated:NO];

Doing so forces the player to play in portrait mode

Playing the movie in portrait mode in iPhone

Playing the movie in portrait mode in iPhone

Playing Video on the iPad

Playing video on the iPad is similar to that on the iPhone. However, you need to make some modifications to the code, if not the video will not play back correctly.

To play the movie correctly on the iPad, modify the application as shown below:


- (void)viewDidLoad {
    NSString *url = [[NSBundle mainBundle]
        pathForResource:@"Stock_Footage_Demobroadband"
                 ofType:@"mp4"];
    MPMoviePlayerController *player =
        [[MPMoviePlayerController alloc]
            initWithContentURL:[NSURL fileURLWithPath:url]];

    [[NSNotificationCenter defaultCenter]
        addObserver:self
           selector:@selector(movieFinishedCallback:)
               name:MPMoviePlayerPlaybackDidFinishNotification
             object:player];

    //---play partial screen---
    player.view.frame = CGRectMake(184, 200, 400, 300);
    [self.view addSubview:player.view];

    //---play movie---
    [player play];

    [super viewDidLoad];
}

Essentially, you are now specifying the area to play back the movie. On the iPad, you can now embed the movie by adding the view exposed by the player to your view window:

player.view.frame = CGRectMake(184, 200, 400, 300);
    [self.view addSubview:player.view];

Now when you run the app in simulator it will look like this.

Playing embeded video on iPad

Playing embeded video on iPad

Note that setOrientation method of the MPMoviePlayerController class is not supported on the iPad.

You can zoom to play the movie full screen by clicking on the two-arrow icons located at the bottom right corner of the player. However, when the movie is done, a black screen appears and there is no way to get back to the View window.

You can play the video full screen by tapping on the two-arrow icon

You can play the video full screen by tapping on the two-arrow icon

Playing Full Screen Movie for iPad

If you want to play a movie full screen on the iPad, you can use the new MPMoviePlayerViewController class. Modify the project as follows:


- (void)viewDidLoad {
    NSString *url = [[NSBundle mainBundle]
        pathForResource:@"Stock_Footage_Demobroadband"
                 ofType:@"mp4"];

    MPMoviePlayerViewController *playerViewController =
    [[MPMoviePlayerViewController alloc]
        initWithContentURL:[NSURL fileURLWithPath:url]];

    [[NSNotificationCenter defaultCenter]
        addObserver:self
           selector:@selector(movieFinishedCallback:)
               name:MPMoviePlayerPlaybackDidFinishNotification
             object:[playerViewController moviePlayer]];

    [self.view addSubview:playerViewController.view];

    //---play movie---
    MPMoviePlayerController *player = [playerViewController moviePlayer];
    [player play];

    [super viewDidLoad];
}

- (void) movieFinishedCallback:(NSNotification*) aNotification {
    MPMoviePlayerController *player = [aNotification object];
    [[NSNotificationCenter defaultCenter]
        removeObserver:self
                  name:MPMoviePlayerPlaybackDidFinishNotification
                object:player];
    [player stop];
    [self.view removeFromSuperView];
    [player autorelease];
}

The MPMoviePlayerViewController class is only available on the iPad (SDK 3.2). Like the MPMoviePlayerController class, you register a notification for the player and then add the view exposed by the MPMoviePlayerViewController class to the current View window:

    [self.view addSubview:playerViewController.view];

    //---play movie---
    MPMoviePlayerController *player = [playerViewController moviePlayer];
    [player play];

When the movie has finished playing (or the user has tapped on the Done button), the player is stopped and then removed from the view stack:

- (void) movieFinishedCallback:(NSNotification*) aNotification {
    MPMoviePlayerController *player = [aNotification object];
    [[NSNotificationCenter defaultCenter]
        removeObserver:self
                  name:MPMoviePlayerPlaybackDidFinishNotification
                object:player];
    [player stop];
    [self.view removeFromSuperView];
    [player autorelease];
}

Run the app to see the results.

Playing the movie full screen using the MPMoviePlayerViewController class

Playing the movie full screen using the MPMoviePlayerViewController class

Open Phone, SMS, Email, Map and browser apps in iPhone SDK

Here is how you can open default Phone app, SMS app, Email app, Maps app and browser app with openURL.

Open default Phone app in iPhone:


[[UIApplication sharedApplication] openURL:[NSURL URLWithString:@"tel://8004664411"]];

Open default SMS app in iPhone:


[[UIApplication sharedApplication] openURL:[NSURL URLWithString:@"sms://466453"]];

Open default Email app in iPhone:


[[UIApplication sharedApplication] openURL:[NSURL URLWithString:@"mailto://devprograms@apple.com"]];

Open default Maps app in iPhone:


NSString* addressText = @"1 Infinite Loop, Cupertino, CA 95014";

// URL encode the spaces

addressText =  [addressText stringByAddingPercentEscapesUsingEncoding: NSASCIIStringEncoding];

NSString* urlText = [NSString stringWithFormat:@"http://maps.google.com/maps?q=%@", addressText];

// lets throw this text on the log so we can view the url in the event we have an issue

[[UIApplication sharedApplication] openURL:[NSURL URLWithString:urlText]];

Open default Browser app in iPhone:


[[UIApplication sharedApplication] openURL:[NSURL URLWithString:@"https://www.makebetterthings.com/blogs/"]];

Call SOAP web service from iPhone

Here is my experience with calling SOAP web services from iPhone SDK.

For this tutorial i am using w3school’s CelsiusToFahrenheit SOAP web service example here you can find the docs for this service.

To call a SOAP service first i create a string with the SOAP request as follows.

NSString *soapMessage = @"<?xml version="1.0" encoding="utf-8"?>n"
	"<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">n"
	"<soap:Body>n"
	"<CelsiusToFahrenheit xmlns="http://tempuri.org/">n"
	"<Celsius>50</Celsius>n"
    "</CelsiusToFahrenheit>n"
	"</soap:Body>n"
	"</soap:Envelope>n";

After creating the SOAP request I create a NSMutableRequest to send this request to server.

 	NSURL *url = [NSURL URLWithString:@"http://w3schools.com/webservices/tempconvert.asmx"];
	NSMutableURLRequest *theRequest = [NSMutableURLRequest requestWithURL:url];
	NSString *msgLength = [NSString stringWithFormat:@"%d", [soapMessage length]];

	[theRequest addValue: @"text/xml; charset=utf-8" forHTTPHeaderField:@"Content-Type"];
	[theRequest addValue: @"http://tempuri.org/CelsiusToFahrenheit" forHTTPHeaderField:@"SOAPAction"];
	[theRequest addValue: msgLength forHTTPHeaderField:@"Content-Length"];
	[theRequest setHTTPMethod:@"POST"];
	[theRequest setHTTPBody: [soapMessage dataUsingEncoding:NSUTF8StringEncoding]];

	NSURLConnection *theConnection = [[NSURLConnection alloc] initWithRequest:theRequest delegate:self];

	if( theConnection )
	{
		webData = [[NSMutableData data] retain];
	}
	else
	{
		NSLog(@"theConnection is NULL");
	}

After firing the request we can collect the XML response in the NSURLConnection’s delegate methods.


-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
	[webData setLength: 0];
}
-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
	[webData appendData:data];
}
-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
	NSLog(@"ERROR with theConenction");
	[connection release];
	[webData release];
}
-(void)connectionDidFinishLoading:(NSURLConnection *)connection
{
	NSLog(@"DONE. Received Bytes: %d", [webData length]);
	NSString *theXML = [[NSString alloc] initWithBytes: [webData mutableBytes] length:[webData length] encoding:NSUTF8StringEncoding];
	NSLog(@"%@",theXML);
	[theXML release];
}

After collecting the XML response in theXML string in -(void)connectionDidFinishLoading:(NSURLConnection *)connection
we can parse this string using TBXML for any other XML parser you like.

Add Maps in iPhone apps (MapKit)

The MapKit framework allows you to integrate a quite fully-featured map in your applications. In this way, your user won’t need to quit your app to check the geographical information in the Maps app.

Let’s give a look at the MapKit and have some fun with the maps. Using Interface Builder you can embed a map in your app in few seconds. It is enough you just add a MKMapView object wherever you need and that’s it. But let’s try together.

Create a new View-based project and name it “Mapper”. The first thing we need to do is to add the MapKit framework. Go to Project -> Edit Active Target and choose the General tab. There, add the MapKit.framework to the Linked Libraries pressing the “+” button. Now, double click the MapperViewController.xib file. Then, drag a Map View object from the Library and drop it onto the View. Save and quit IB. Now, go back to Xcode and select Build and Run from the Build menu.

Map in Inspector

Map in Inspector

Do you like it? You got a fully featured map. You can zoom in and out and you can pan left and right and up and down. Now, re-open the MapperViewController.xib file and select the MapView you previously added to it. Open the Inspector and go to the Attributes pane.

As you can see, you can change different map attributes. For example, you can change the map type to Map, Satellite and Hybrid. You can allow the zooming and the scrolling and you can also activate the current user location. You can change these features in IB, but you can also change them programmatically. To do so, we need to create an outlet in your class and connect it to the map. Open MapperViewController.h and import the MapKit header. Add this line

#import <MapKit/MapKit.h>

Then, add the following outlet:

IBOutlet MKMapView *mapView;

Create also a property:

@property (nonatomic, retain) IBOutlet MKMapView *mapView;

Remember to synthesize the property and don’t forget to release it in the dealloc method. Now, open MapperViewController.xib and connect the File’s Owner outlet (mapView) with the map object you previously added (see Figure below).

Map connection

Map connection

Now, you can go back to the code (after saving in IB) and add the following lines inside the viewDidiLoad method of the MapperViewController.m:

-(void) viewDidLoad
{
 [super viewDidLoad];
 [mapView setMapType:MKMapTypeStandard];
 [mapView setZoomEnabled:YES];
 [mapView setScrollEnabled:YES];
 }

Setting the Region

If you want to represent a specific portion of the map with a specific zoom level, you need to define a Map Region. This is a C structure composed by the Center and the Span. The Center is represented by the Longitude and the Latitude of the Region center, while the Span defines how much of the map is visible and is also the zoom level. A large Span corresponds to a low zoom level, while a small Span corresponds to a high zoom level.

Let’s define the Region for our map. At the end of the viewDidLoad method, add the following code:

MKCoordinateRegion region = { {0.0, 0.0 }, { 0.0, 0.0 } };
region.center.latitude = 41.902245099708516;
region.center.longitude = 12.457906007766724;
region.span.longitudeDelta = 0.01f;
region.span.latitudeDelta = 0.01f;
[mapView setRegion:region animated:YES];

The region is defined around the San Peter Cathedral in Rome. Now, if you want to pan or zoom programmatically, you just need to change programmatically the Region. If you change the Span, you zoom. If you change the Center, you pan. Simple, no?

User Location

This is very simple too. If you want to show the user location, you just set the flag in IB, as previously discussed, or you just add the following line of code:

[mapView setShowsUserLocation:YES];

Converting coordinates

It is often useful to convert the geographical coordinate to the view coordinates and vice versa. You can do this, using these methods:

– convertCoordinate:toPointToView:
– convertPoint:toCoordinateFromView:
– convertRegion:toRectToView:
- convertRect:toRegionFromView:

Annotations

A map without any useful information on it is most of the time useless. The magic happens when the map helps you to find some service or some friend or when it shows any interesting information to your app. If you want to add information to the map you need to add annotations.
In the MapKit framework, an annotation is composed by two parts: an annotation object and an annotation view. The Annotation Object contains information related to the geographical coordinates, while the Annotation View represents the view associated to the annotation object that will be displayed on request.
An annotation object is any object that conforms to the MKAnnotation protocol. Let’s add an annotation object to our map. We need to define a new class. Right-click the Classes Group and select Add->New Files.

Select an Objective-C class as Subclass of NSObject and name it MyAnnotation. Open the MyAnnotation.h and add the bold code:

#import <UIKit/UIKit.h>
#import <MapKit/MapKit.h>

@interface MyAnnotation : NSObject
{
     CLLocationCoordinate2D coordinate;
     NSString *title;
    NSString *subtitle;
}
@property (nonatomic, assign) CLLocationCoordinate2D coordinate;
@property (nonatomic, copy) NSString *title;
@property (nonatomic, copy) NSString *subtitle;
@end

Now, switch to the implementation file and add:

#import "MyAnnotation.h"

@implementation MyAnnotation

@synthesize coordinate, title, subtitle;

-(void)dealloc
{
    [title release];
    [subtitle release];
    [super dealloc];
}
@end

We have now our annotation class with the coordinates, a title and a subtitle for the Callout view (see below). Let’s instantiate an object and add it to the map.
Still in the viewDidLoad method add the following lines of code:

MyAnnotation *ann = [[MyAnnotation alloc] init];
ann.title = @"Rome";
ann.subtitle = @"San Peter";
ann.coordinate = region.center;
[mapView addAnnotation:ann];

Here, we created an ann object and set its title, subtitle and coordinates (for simplicity, I am setting the annotation coordinates to the map center, but you can put there any coordinates you like). You can create an array of annotations and then add it to the map.

Finally, we need to set our MapperViewController class as the delegate of the mapView. So, just add this line of code at the end of the viewDidLoad:

[mapView setDelegate:self];

and add the protocol name in the interface of the class:

@interface MapperViewController : UIViewController

The last step is to implement a delegate method that manages the annotations during the panning and zooming. The approach is very similar to the UITableView. We create a static identifier and we try to reuse the annotation view with the same identifier, as we do with the cell of the tableview. If we are not able to dequeue an annotation view, we need to allocate one. Then, we choose the pin color and allow a callout view. Then, we animate the drop of the pin.
Add the following code to the MapperViewController.m file:

- (MKAnnotationView *)mapView:(MKMapView *)mV viewForAnnotation:(id )annotation
{
    MKPinAnnotationView *pinView = nil;
    if(annotation != mapView.userLocation)
    {
         static NSString *defaultPinID = @"com.invasivecode.pin";
         pinView = (MKPinAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:defaultPinID];
         if ( pinView == nil )
            pinView = [[[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:defaultPinID] autorelease];
        pinView.pinColor = MKPinAnnotationColorPurple;
        pinView.canShowCallout = YES;
        pinView.animatesDrop = YES;
    }
    else
        [mapView.userLocation setTitle:@"I am here"];
    return pinView;
}

That’s it. Below, you can see the result.

MapKit on iPhone

MapKit on iPhone

Add UITextField in UIAlertView

Hello All, here’s my experience with adding a UITextField to a UIAlertView. All of this is done in code, so there’s no interface builder(IB) to handle the layout.

Adding a UITextField to a UIAlertView is as simple as inistantiating the UITextField and adding it as a subview of your UIAlertView. I set eh background color of the test field to be white so you can see where it is.

UIAlertView *myAlertView = [[UIAlertView alloc] initWithTitle:nil message:nil delegate:self cancelButtonTitle:@"Cancel" otherButtonTitles:nil];
UITextField *myTextField = [[UITextField alloc] initWithFrame:CGRectMake(12.0, 45.0, 260.0, 25.0)];
[myAlertView addSubview:myTextField];
[myAlertView show];
[myAlertView release];

Here’s a pic of the abboration that is your custom UIAlertView thus far.

Add UITextField in UIAlertView - 1

Add UITextField in UIAlertView -1

After fiddling around with all the values for a while, I finally found the proper settings that allow the alert view to be displayed properly with the text field fully visible and functional. Since the UIAlertView isn’t “made” to support this type of user interaction, you have to specify a message component for the alert view. This provides enough spacing so that you can insert the text field which will cover the message and thus keep things from over lapping. Here’s the revised code and a picture of the properly formatted UIAlertView.

UIAlertView *myAlertView = [[UIAlertView alloc] initWithTitle:@"Your title here!" message:@"this gets covered" delegate:self cancelButtonTitle:@"Cancel" otherButtonTitles:@"OK", nil];
UITextField *myTextField = [[UITextField alloc] initWithFrame:CGRectMake(12.0, 45.0, 260.0, 25.0)];
[myTextField setBackgroundColor:[UIColor whiteColor]];
[myAlertView addSubview:testTextField];
[myAlertView show];
[myAlertView release];
Add UITextField in UIAlertView - 2

Add UITextField in UIAlertView - 2

After getting all that done, I ran into one more problem. When you touch the text field to enter text, the keyboard slides into view and covers part of the alert view.

Add UITextField in UIAlertView - 3

Add UITextField in UIAlertView - 3

To fix this, I created a translation transform and applied it to the alert view to move it higher up the screen so it wouldn’t interfere with the keyboard. With that done, this is what it looks like. You still need to set up delegate methods for the alert view and the text field, but those are pretty simple and straight forward.

UIAlertView *myAlertView = [[UIAlertView alloc] initWithTitle:@"Your title here!" message:@"this gets covered" delegate:self cancelButtonTitle:@"Cancel" otherButtonTitles:@"OK", nil];
UITextField *myTextField = [[UITextField alloc] initWithFrame:CGRectMake(12.0, 45.0, 260.0, 25.0)];
[myTextField setBackgroundColor:[UIColor whiteColor]];
[myAlertView addSubview:testTextField];
CGAffineTransform myTransform = CGAffineTransformMakeTranslation(0.0, 130.0);
[myAlertView setTransform:myTransform];
[myAlertView show];
[myAlertView release];
Add UITextField in UIAlertView - 4

Add UITextField in UIAlertView - 4