Chapter    13

Data Transmission Recipes

As time has progressed and technology has developed, one of the clearest trends to be noticed is the growth in user-generated content. With the improvement of design technologies, Internet connection speeds, and network availability, the amount of data generated electronically per year has increased at a nearly unbelievable rate. The heart of this matter is based around the idea of allowing users to easily take in and re-distribute information. You can incorporate these same concepts into your development through a variety of built-in classes in order to improve the functionality and usefulness of your applications.

In this chapter, you will need only a physical device to implement texting functionality, which you will build in your first recipe. All your other functionalities can simulated.

Recipe 13-1: Composing Text Messages

Text messaging is still one of the most popular methods of transmitting data between individuals. It’s quick, easy, and powerful, and is used across nearly all age groups. In iOS you can incorporate text messaging into your applications and provide the simple cross-application functionality that can so easily improve the overall quality of an application.

Start off by creating a new project called “Send It Out,” which you use throughout this chapter. As usual, use the Single View Application template to create the project.

To fully demonstrate a few of the functionalities of this topic, specifically choose to develop your application for the iPad, rather than the iPhone. Be sure the Device Family is set accordingly in the next screen after entering the project’s name. Because some of the functionalities you will test require a physical device to be fully capable, you can make this application for the iPhone as well, and simply adjust the view elements as you wish.

After clicking through to create your project, switch over to your view controller’s .xib file. In the Utilities pane, set the orientation (under the Simulated Metrics section, under the Attributes inspector tab) to landscape, and be sure that the background color is set to light gray, as shown in Figure 13-1.

9781430245995_Fig13-01.jpg

Figure 13-1.  Configuring the Orientation and Background color of the iPad app

Start off by adding a UITextView, with the default Lorem Ipsum text, to the top half of your view, as well as a UIButton, with the label Text Message, to the bottom. Connect these to your view controller as outlets with property names inputTextView and textMessageButton. Also, create an action named textMessage for the button.

Before you proceed, go ahead and round the corners of your UITextView to improve your application’s visual quality. Add the following import statement to your view controller’s header file:

//
//  ViewController.h
//  Send It Out
//
 
#import <UIKit/UIKit.h>
#import <QuartzCore/QuartzCore.h>
 
@interface ViewController : UIViewController
 
@property (weak, nonatomic) IBOutlet UITextView *inputTextView;
@property (weak, nonatomic) IBOutlet UIButton *textMessageButton;
 
- (IBAction)textMessage:(id)sender;
 
@end
 

Next add the following line to the end of your viewDidLoad method:

- (void)viewDidLoad
{
    [super viewDidLoad];
    self.inputTextView.layer.cornerRadius = 15.0;
}

If you test your app in the iPad simulator, your view should now resemble that simulated in Figure 13-2 once you rotate the simulator. This can be done through either the Rotate Left (Cmd (image) + left arrow key) or Rotate Right (Cmd (image) + right arrow key) commands found in the Hardware menu of the iOS simulator.

9781430245995_Fig13-02.jpg

Figure 13-2.  A simulated view of your user interface

You will also configure your view controller as the delegate for your UITextView. Add the UITextViewDelegate protocol to the view controller’s @interface declaration:

//
//  ViewController.h
//  Send It Out
//
 
// . . .
 
@interface ViewController : UIViewController<UITextViewDelegate>
 
// . . .
 
@end

And, assign the Text View’s delegate property in the viewDidLoad method in ViewController.m:

- (void)viewDidLoad
{
    [super viewDidLoad];
    self.inputTextView.layer.cornerRadius = 15.0;
    self.inputTextView.delegate = self;
}

Next, add the MessageUI framework to your project. Do this under the Build Phases tab of your project (see Recipe 1-2, Linking a Framework, for details). Also, add the required import statement to your view controller’s header file.

Your view controller acts as a delegate for the Message Compose View Controller that you’ll create later, so add the MFMessageComposeViewControllerDelegate protocol to ViewController.h as well:

//
//  ViewController.h
//  Send It Out
//
 
#import <UIKit/UIKit.h>
#import <QuartzCore/QuartzCore.h>
#import <MessageUI/MessageUI.h>
 
@interface ViewController : UIViewController<UITextViewDelegate ,
    MFMessageComposeViewControllerDelegate>
 
// . . .
 
@end

Now switch back to ViewController.m, where you implement one of your UITextView’s delegate methods to ensure that your keyboard is properly dismissed when the user taps the Enter key.

- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range
 replacementText:(NSString *)text
{
if ([text isEqualToString:@" "])
    {
        [textView resignFirstResponder];
        return NO;
    }
    return YES;
}

Now you can implement your -textMessage: action method such that the text of your UITextView is transposed into a text message. You simply set the recipient to a fake number.

-(IBAction)textMessage:(id)sender
{
    if ([MFMessageComposeViewController canSendText])
    {
        MFMessageComposeViewController *messageVC =
            [[MFMessageComposeViewController alloc] init];
        messageVC.messageComposeDelegate = self;
        messageVC.recipients = @[@"3015555309"];
        messageVC.body = self.inputTextView.text;
        [self presentViewController:messageVC animated:YES completion:nil];
    }
    else
    {
        NSLog(@"Text Messaging Unavailable");
    }
}

The implementation of this method should appear fairly straightforward. After using the canSendText method to check for texting availability, you create an instance of the MFMessageComposeViewController class, and then configure it with your fake recipient, as well as the intended text. Finally, you simply present the controller to allow your user to review the text message before sending it.

The MFMessageComposeViewController and its counterpart, the MFMailComposeViewController, which you will encounter later, are both classes that allow you to set their initial conditions and present them, but they do not allow you any control of the class once it has been shown. This is to ensure that the user has the final say in whether a message or mail sends, rather than any application sending it without informing the user.

You can implement your MFMessageComposeViewController’s messageComposeDelegate method to handle the completion of the message like so:

-(void)messageComposeViewController:(MFMessageComposeViewController *)controller didFinishWithResult:(MessageComposeResult)result
{
    if (result == MessageComposeResultSent)
    {
        self.inputTextView.text = @"Message sent.";
    }
    else if (result == MessageComposeResultFailed)
    {
        NSLog(@"Failed to send message!");
    }
    [self dismissViewControllerAnimated:YES completion:nil];
}

Along with the two possible values of MessageComposeResults demonstrated in the previous code, there is a third result, MessageComposeResultCancelled, which indicates that the user cancelled the sending of the text message.

A new functionality in iOS 5.0 was the ability to receive notifications about the changing of the availability of text messaging. You can register for such notifications by adding the following line to the -viewDidLoad method in ViewController.m:

- (void)viewDidLoad
{
    [super viewDidLoad];
    self.inputTextView.layer.cornerRadius = 15.0;
    self.inputTextView.delegate = self;
    [[NSNotificationCenter defaultCenter] addObserver:self
        selector:@selector(textMessagingAvailabilityChanged:)
        name:MFMessageComposeViewControllerTextMessageAvailabilityDidChangeNotification
        object:nil];
}

The selector specified here can easily be defined to simply inform you of the change. In a full application, you might likely make use of a UIAlert to notify the user of this change as well, but you will avoid this process for demonstration purposes.

-(void)textMessagingAvailabilityChange:(id)sender
{
    if ([MFMessageComposeViewController canSendText])
    {
        NSLog(@"Text Messaging Available");
    }
    else
    {
        NSLog(@"Text Messaging Unavailable");
    }
}

Your app can now copy the body of your UITextView into a text message to be sent off to your fake recipient. If you test this, however, keep in mind that the text messaging functionality will not be available on the iOS simulator. You have to test this on your physical device with 3G capabilities. To test this application as it is exactly, you need a 3G-capable iPad, but you could edit the project to work for an iPhone instead.

Recipe 13-2: Composing Email

Just as you could create and configure text messages to be sent from your application, you can also use the MessageUI framework to configure mail messages using the counterpart to the MFMessageComposeViewController class, which is MFMailComposeViewController.

Building on the application from Recipe 13-1, add an option to email the message. Start by adding another button with the label “Mail Message”. Create an outlet named mailMessageButton and an action called mailMessage for the button.

Similar to what you did in Recipe 13-1 with the MFMessageComposeViewController, you’ll prepare the main view controller to be a delegate of the MFMailComposeViewController:

//
//  ViewController.h
//  Send It Out
//
 
#import <UIKit/UIKit.h>
#import <QuartzCore/QuartzCore.h>
#import <MessageUI/MessageUI.h>
 
@interface ViewController : UIViewController<UITextViewDelegate,
    MFMessageComposeViewControllerDelegate, MFMailComposeViewControllerDelegate>
 
@property (weak, nonatomic) IBOutlet UITextView *inputTextView;
@property (weak, nonatomic) IBOutlet UIButton *textMessageButton;
@property (weak, nonatomic) IBOutlet UIButton *mailMessageButton;
 
- (IBAction)textMessage:(id)sender;
- (IBAction)mailMessage:(id)sender;
 
@end

The setup for your mailMessage: method is also very similar to your previous textMessage: method. You create your composing view controller, configure it, and then present it. Here is the implementation:

- (IBAction)mailMessage:(id)sender
{
    if ([MFMailComposeViewController canSendMail])
    {
        MFMailComposeViewController *mailVC =
            [[MFMailComposeViewController alloc] init];
        [mailVC setSubject:@"Send It Out"];
        [mailVC setToRecipients:@[@"[email protected]"]];
        [mailVC setMessageBody:self.inputTextView.text isHTML:NO];
        mailVC.mailComposeDelegate = self;
        [self presentViewController:mailVC animated:YES completion:nil];
    }
    else
    {
        NSLog(@"E-mailing Unavailable");
    }
}

As you can see, the MFMailComposeViewController has a few extra properties compared to the MFMessageComposeViewController to specifically configure a more complex email.

The MFMailComposeViewControllerDelegate protocol defines only one method, which you are required to implement to properly handle the completed use of the view controller by the user. You give this a simple implementation to log the result.

-(void)mailComposeController:(MFMailComposeViewController *)controller didFinishWithResult:(MFMailComposeResult)result error:(NSError *)error
{
    if (result == MFMailComposeResultSent)
        self.inputTextView.text = @"Mail sent.";
    else if (result == MFMailComposeResultCancelled)
        NSLog(@"Mail Cancelled");
    else if (result == MFMailComposeResultFailed)
        NSLog(@"Error, Mail Send Failed");
    else if (result == MFMailComposeResultSaved)
        NSLog(@"Mail Saved");
    [self dismissViewControllerAnimated:YES completion:nil];
}

Now, your new application can present a view controller for sending mail, as shown in Figure 13-3. Unlike the MFMessageViewController, however, you can actually test this functionality in the iOS Simulator.

9781430245995_Fig13-03.jpg

Figure 13-3.  Composing an email using the MFMailMailComposeViewController

Quite conveniently, you can easily test all the functionalities of the MFMailComposeViewController using the simulator without any fear of sending out multiple emails to any addresses, real or fake. The simulator does not actually send out your test messages over the Internet, so you can easily test your mailComposeDelegate method’s handling of the MailComposeResults.

Attaching Data to Mail

The MFMailComposeViewController includes functionality for you to attach data to your email from your application through the use of the addAttachmentData:mimeType:fileName: method. This method takes three parameters:

  1. attachment: This instance of NSData refers to the actual data of the object that you want to send. This means for any object you want to attach, you will need to acquire the NSData for it.
  2. mimeType: This property is an NSString that the controller uses to define the data type of the attachment. These values are not specific to iOS, and so are not defined in the Apple documentation. They can, however, be easily found online. Wikipedia offers a very comprehensive article on possible values at http://en.wikipedia.org/wiki/Internet_media_type. The MIME type of a JPEG image, for example, is image/jpeg.
  3. fileName: Use this NSString property to set the preferred name for the file sent in the email.

You now add functionality to your application to access the user’s image library, select an image, and then attach that image to your email.

Start off by adding a UIImageView underneath your UITextView, along with a UIButton with the label “Get Image.” To make the Image View visible when no image is currently selected, change the Background color attribute to white. Additionally, to avoid drawing outside the image view’s frame, check its Clip Subviews attribute in the Attributes inspector. Your view now resembles that simulated in Figure 13-4.

9781430245995_Fig13-04.jpg

Figure 13-4.  Your new user interface with ability to select an image

Create outlets for the two elements and name them imageView and getImageButton respectively. Also, create the action getImage for the button.

Whenever you want to access the photo library of an iPad, you need to use a UIImagePickerController set inside of a UIPopoverController. Declare a UIPopoverController property called popover in the main view controller’s header file. You also need to instruct the view controller to conform to the UIImagePickerControllerDelegate, UIPopoverControllerDelegate and UINavigationControllerDelegate protocols. Overall your header file should now resemble the following:

//
//  ViewController.h
//  Send It Out
//
 
#import <UIKit/UIKit.h>
#import <QuartzCore/QuartzCore.h>
#import <MessageUI/MessageUI.h>
 
@interface ViewController : UIViewController<UITextViewDelegate,
    MFMessageComposeViewControllerDelegate, MFMailComposeViewControllerDelegate,
    UIImagePickerControllerDelegate, UIPopoverControllerDelegate,
    UINavigationControllerDelegate>
 
@property (weak, nonatomic) IBOutlet UITextView *inputTextView;
@property (weak, nonatomic) IBOutlet UIButton *textMessageButton;
@property (weak, nonatomic) IBOutlet UIButton *mailMessageButton;
@property (weak, nonatomic) IBOutlet UIImageView *imageView;
@property (weak, nonatomic) IBOutlet UIButton *getImageButton;
@property (strong, nonatomic) UIPopoverController *popover;
 
- (IBAction)textMessage:(id)sender;
- (IBAction)mailMessage:(id)sender;
- (IBAction)getImage:(id)sender;
 
@end

Now, you can write your getImage: method to present your popover controller with access to the photo library:

- (IBAction)getImage:(id)sender
{
    UIImagePickerController *picker = [[UIImagePickerController alloc] init];
    if ([UIImagePickerController
        isSourceTypeAvailable:UIImagePickerControllerSourceTypePhotoLibrary])
    {
        picker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
        picker.delegate = self;
        self.popover =
            [[UIPopoverController alloc] initWithContentViewController:picker];
        self.popover.delegate = self;
        [self.popover presentPopoverFromRect:self.getImageButton.frame inView:self.view
            permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
    }
}
 

Now you just need to implement your UIImagePickerControllerDelegate protocol methods.

-(void)imagePickerControllerDidCancel:(UIImagePickerController *)picker
{
    [self.popover dismissPopoverAnimated:YES];
}
 
-(void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info
{
    self.imageView.image = [info valueForKey:@"UIImagePickerControllerOriginalImage"];
    self.imageView.contentMode = UIViewContentModeScaleAspectFill;
    [self.popover dismissPopoverAnimated:YES];
}

At this point, your application can select an image and set it in your UIImageView. If you are testing this application in the simulator, you will need to acquire at least one image to put in your simulator’s photo library. You can do this by dragging an image onto the iOS Simulator window, which launches the Safari app. Click and hold the image to save it to the library. In Figure 13-5, your app is shown with an image already selected.

9781430245995_Fig13-05.jpg

Figure 13-5.  Running your app and tapping the Get Image button to attach an image to the email

Now you can continue to add the chosen image into your email. Modify your mailPressed: method to attach the image if one has been selected.

- (IBAction)mailMessage:(id)sender
{
    if ([MFMailComposeViewController canSendMail])
    {
        MFMailComposeViewController *mailVC =
            [[MFMailComposeViewController alloc] init];
        [mailVC setSubject:@"Send It Out"];
        [mailVC setToRecipients:@[@"[email protected]"]];
        [mailVC setMessageBody:self.inputTextView.text isHTML:NO];
        mailVC.mailComposeDelegate = self;
 
        if (self.imageView.image != nil)
        {
            NSData *imageData = UIImageJPEGRepresentation(self.imageView.image, 1.0);
            [mailVC addAttachmentData:imageData mimeType:@"image/jpeg"
                fileName:@"SelectedImage"];
        }
 
        [self presentViewController:mailVC animated:YES completion:nil];
    }
    else
    {
        NSLog(@"Emailing Unavailable");
    }
}

Finally, you can modify your MFMailComposeViewController’s delegate method to reset the app if the email has been successfully sent:

-(void)mailComposeController:(MFMailComposeViewController *)controller didFinishWithResult:(MFMailComposeResult)result error:(NSError *)error
{
    if (result == MFMailComposeResultSent)
    {
        self.inputTextView.text = @"Mail sent.";
        self.imageView.image = nil;
    }
    else if (result == MFMailComposeResultCancelled)
        NSLog(@"Mail Cancelled");
    else if (result == MFMailComposeResultFailed)
        NSLog(@"Error, Mail Send Failed");
    else if (result == MFMailComposeResultSaved)
        NSLog(@"Mail Saved");
    [self dismissViewControllerAnimated:YES completion:nil];
}

If you test the application in the simulator now and you attempt to send an email after selecting an image, you should see the chosen image placed into your message, as in Figure 13-6.

9781430245995_Fig13-06.jpg

Figure 13-6.  Your application composing email with an attached image

Recipe 13-3: Printing an Image

Now that you have your application set up to handle both text and images, you can continue to enhance your functionality by adding the ability to print.

Before you specifically work on printing, you will reconfigure your application’s user interface a bit to include your view controller inside of a UINavigationController so that you can get a nice toolbar across the top. To do this, first declare a UINavigationController property in AppDelegate.h:

//
//  AppDelegate.h
//  Send It Out
//
 
#import <UIKit/UIKit.h>
 
@class ViewController;
 
@interface AppDelegate : UIResponder <UIApplicationDelegate>
 
@property (strong, nonatomic) UIWindow *window;
 
@property (strong, nonatomic) ViewController *viewController;
@property (strong, nonatomic) UINavigationController *navigationController;
 
@end

And, in AppDelegate.m, make the following changes to the application:didFinishLaunchingWithOptions method:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    // Override point for customization after application launch.
    self.viewController = [[ViewController alloc] initWithNibName:@"ViewController"
        bundle:nil];
    self.navigationController = [[UINavigationController alloc]
        initWithRootViewController:self.viewController];
    self.window.rootViewController = self.navigationController;
    [self.window makeKeyAndVisible];
    return YES;
}

You also need to adjust the user interface to account for the Navigation bar; for example, the lower buttons in your view may be pushed off-screen. To see what changes are necessary, set the main view’s .xib file “Top Bar” attribute to “Navigation Bar” in the “Simulated Metrics” section of the Attributes inspector. Go ahead and correct the layout by dragging and resizing the affected elements if necessary.

Next, add the following lines to the end of your viewDidLoad method to configure your navigation bar and add a Print button to the Navigation bar if printing is available:

- (void)viewDidLoad
{
    [super viewDidLoad];
    self.inputTextView.layer.cornerRadius = 15.0;
    self.inputTextView.delegate = self;
    [[NSNotificationCenter defaultCenter] addObserver:self
        selector:@selector(textMessagingAvailabilityChanged:)
        name:MFMessageComposeViewControllerTextMessageAvailabilityDidChangeNotification
        object:nil];
    self.title = @"Send It Out!";
    if ([UIPrintInteractionController isPrintingAvailable])
    {
        UIBarButtonItem *printButton = [[UIBarButtonItem alloc] initWithTitle:@"Print"
            style:UIBarButtonItemStyleBordered target:self
            action:@selector(print:)];
        self.navigationItem.rightBarButtonItem = printButton;
    }
}

Now, you can add the action method print: method in order to add your printing functionality, primarily through the use of the UIPrintInteractionController class. This class is your “hub” of activity when it comes to configuring print jobs. We discuss the steps to set up this class individually before seeing the method as a whole.

Whenever you want to access an instance of a UIPrintInteractionController, you simply call for a reference to the shared instance through the sharedPrintController class method.

UIPrintInteractionController *pic = [UIPrintInteractionController sharedPrintController];

Up next, you must configure the printInfo property of your controller, which specifies the settings for the print job.

UIPrintInfo *printInfo = [UIPrintInfo printInfo];
printInfo.outputType = UIPrintInfoOutputPhoto;
printInfo.jobName = self.title;
printInfo.duplex = UIPrintInfoDuplexLongEdge;

As you can see, you have set the outputType to specify an image. The three possible values for this property are as follows:

  • UIPrintInfoOutputPhoto: Used specifically for photos to be printed
  • UIPrintInfoOutputGrayscale: Used when dealing only with black text so as to improve performance
  • UIPrintInfoOutputGeneral: Used for any mix of graphics and text, with or without color

You did not yet set this printInfo object as the printInfo of your controller because you will do a little bit more configuration of it shortly.

Next, you have to do an interesting specification for your UIPrintInteractionController. I say interesting because you absolutely have to do one, and only one, of four possible tasks:

  1. Set a single item to be printed.
  2. Set multiple items to be printed.
  3. Specify an instance of UIPrintFormatter to the controller to configure the layout of your page.
  4. Specify an instance of UIPrintPageRenderer, which can then have multiple instances of UIPrintFormatter assigned to it to gain full customization of your content layout over multiple pages.

Start off with the simplest option of setting a single item to be printed. This item must be either an image or a PDF file to use these simpler options, so choose to simply print your selectedImage.

UIImage *image = self.imageView.image;
pic.printingItem = image;

Now that you know what you want to print, you can check the orientation of the image and configure your printInfo accordingly.

if (!pic.printingItem && image.size.width > image.size.height)
      printInfo.orientation = UIPrintInfoOrientationLandscape;
 
pic.printInfo = printInfo;
pic.showsPageRange = YES;

Finally, present your UIPrintInteractionController. This class is equipped with three different methods for presenting itself, depending on your specific implementation:

  • presentFromBarButtonItem:animated:completionHandler:: If you are writing for an iPad, this method is designed for use when the application’s Print button is placed in a toolbar, such as in this recipe.
  • presentFromRect:inView:animated:completionHandler:: This method is also only for use with the iPad, but allows you to present the controller from any part of the view. Usually, the rect specified will be the frame of your Print button, wherever it is located.
  • presentAnimated:completionHandler:: This method should be used whenever implementing printing on an iPhone due to the smaller screen.

With this final method call, your print: method in its entirety will look like so:

-(void)print:(id)sender
{
    if ([UIPrintInteractionController isPrintingAvailable]
        && (self.imageView.image != nil))
    {
        UIPrintInteractionController *pic =
            [UIPrintInteractionController sharedPrintController];
        UIPrintInfo *printInfo = [UIPrintInfo printInfo];
        printInfo.outputType = UIPrintInfoOutputPhoto;
        printInfo.jobName = self.title;
        printInfo.duplex = UIPrintInfoDuplexLongEdge;
        UIImage *image = self.imageView.image;
        pic.printingItem = image;
        if (!pic.printingItem && image.size.width> image.size.height)
            printInfo.orientation = UIPrintInfoOrientationLandscape;
        pic.printInfo = printInfo;
        pic.showsPageRange = YES;
        [pic presentFromBarButtonItem:sender animated:YES completionHandler:
         ^(UIPrintInteractionController *printInteractionController, BOOL completed,
             NSError *error)
         {
             if (!completed && (error != nil))
             {
                 NSLog(@"Error Printing: %@", error);
             }
             else
             {
                 NSLog(@"Cancelled Printing");
             }
         }];
    }
}
 

Now when you run your application and select an image, you can print the image by tapping the Print button. This presents a small controller from which you can select a printer and further configure your specific print job. Unfortunately, if you’re testing this in your simulator or don’t have any wireless printers set up, you won’t see any available printers to use, as shown in Figure 13-7.

9781430245995_Fig13-07.jpg

Figure 13-7.  Your app with a new Print button, unable to find any AirPrint printers

Luckily, Xcode comes with a fantastic application called Printer Simulator. With this program, you can fully simulate print jobs from your app. It even gives you a PDF file of your simulated output, so you can see exactly how your image would have turned out without wasting any paper!

To run this program, open the Application folder (or the folder in which Xcode resides on your computer) in a Finder window; Ctrl-click Xcode and choose Show Package Contents as Figure 13-8 shows. Then, navigate to Contents/Applications and click the Printer Simulator.app file.

9781430245995_Fig13-08.jpg

Figure 13-8.  Showing the packaged content of the Xcode bundle

On running the Printer Simulator application, a variety of printer types are automatically registered for use. The simulator looks similar to Figure 13-9.

9781430245995_Fig13-09.jpg

Figure 13-9.  Printer Simulator registering multiple types of printers to simulate

Now, on testing your application, you should see different types of simulated printers with which to test your application. You can now choose one of the simulated printers from your app, as in Figure 13-10.

9781430245995_Fig13-10.jpg

Figure 13-10.  Selecting a simulated inkjet printer from your app

After you have selected a printer, you can print multiple copies as well as change the paper type before you print. After you tap the Print button, you should start seeing activity in your Printer Simulator, and shortly afterward, a PDF file opens with your final printout, resembling that shown in Figure 13-11.

9781430245995_Fig13-11.jpg

Figure 13-11.  Output of printing an image from a simulated printer

Recipe 13-4: Printing Plain Text

Expanding on your previous recipe, you will add functionality to make use of a print formatter to allow you to print simple text.

First, modify your viewDidLoad method to add an extra button to print the text in your UITextView. Change the condition statement in the method to look like so:

if ([UIPrintInteractionController isPrintingAvailable])
{
    UIBarButtonItem *printButton =
        [[UIBarButtonItem alloc] initWithTitle:@" Print Image"
            style:UIBarButtonItemStyleBordered target:self action:@selector(print:)];
    UIBarButtonItem *printTextButton =
        [[UIBarButtonItem alloc] initWithTitle:@"Print Text"
            style:UIBarButtonItemStyleBordered target:self
            action:@selector(printText:)];
        self.navigationItem.rightBarButtonItems = @[printButton, printTextButton];
}

The new selector to print your text is then implemented as follows:

-(void)printText:(id)sender
{
    if ([UIPrintInteractionController isPrintingAvailable])
    {
        UIPrintInteractionController *pic =
            [UIPrintInteractionController sharedPrintController];
        UIPrintInfo *printInfo = [UIPrintInfo printInfo];
        printInfo.outputType = UIPrintInfoOutputGeneral;
        printInfo.jobName = self.title;
        printInfo.duplex = UIPrintInfoDuplexLongEdge;
        pic.printInfo = printInfo;
        UISimpleTextPrintFormatter *simpleTextPF =
            [[UISimpleTextPrintFormatter alloc] initWithText:self.inputTextView.text];
        simpleTextPF.startPage = 0;
        simpleTextPF.contentInsets = UIEdgeInsetsMake(72.0, 72.0, 72.0, 72.0);
        simpleTextPF.maximumContentWidth = 6*72.0;
        pic.printFormatter = simpleTextPF;
        pic.showsPageRange = YES;
        [pic presentFromBarButtonItem:sender animated:YES
            completionHandler:
        ^(UIPrintInteractionController *printInteractionController, BOOL completed,
              NSError *error)
         {
             if (!completed && (error != nil))
             {
                 NSLog(@"Error Printing: %@", error);
             }
             else
             {
                 NSLog(@"Printing Cancelled");
             }
         }];
    }
}

There are two main differences between this method and its predecessor:

  1. The outputType property in your UIPrintInfo is modified to the UIPrintInfoOutputGeneral value, because you are no longer printing photos.
  2. Instead of setting a UIImage to the printingItem property, you set an instance of UISimpleTextPrintFormatter to the printFormatter property. This object is initialized with the desired text, and then configured through its properties.
    1. Values of 72.0 as insets translate to 1 inch, so you have given your output 1-inch insets, and specified a 6-inch width for your content.
    2. The startPage property is used more at a later point, but allows you to specify the page in your job for your formatter to be applied to.

Tip  When printing simple text, it is also quite easy to apply the preceding method to printing HTML-formatted text. To do this, simply make use of a UIMarkupTextPrintFormatter instead of a UISimpleTextPrintFormatter.

Just as before, by using the Printer Simulator, you can generate your test output. Because you set your text view’s text as the content of your print formatter, you simply get a document with some Lorem Ipsum text in it, as in Figure 13-12.

9781430245995_Fig13-12.jpg

Figure 13-12.  Output of the simulated printing of a simple text page

Recipe 13-5: Printing a View

This recipe builds on Recipe 13-4.

Just as you can print text using a UISimpleTextPrintFormatter, you can print the contents of a view using another subclass of UIPrintFormatter: UIViewPrintFormatter.

Start by modifying your viewDidLoad’s (in ViewController.m) conditional setup to now appear like so:

if ([UIPrintInteractionController isPrintingAvailable])
{
    UIBarButtonItem *printButton =
        [[UIBarButtonItem alloc] initWithTitle:@"Print Image"
        style:UIBarButtonItemStyleBordered target:self action:@selector(print:)];
 
 
    UIBarButtonItem *printTextButton =
        [[UIBarButtonItem alloc] initWithTitle:@"Print Text"
            style:UIBarButtonItemStyleBordered target:self
            action:@selector(printText:)];
    UIBarButtonItem *printViewButton =
        [[UIBarButtonItem alloc] initWithTitle:@"Print View"
            style:UIBarButtonItemStyleBordered target:self
            action:@selector(printViewPressed:)];
    self.navigationItem.rightBarButtonItems =
        @[printButton, printTextButton, printViewButton];
}

Your newest printing method, printView:, closely resembles your previous one, with the key change of using a UIViewPrintFormatter.

-(void)printViewPressed:(id)sender
{
    if ([UIPrintInteractionController isPrintingAvailable])
    {
        UIPrintInteractionController *pic =
            [UIPrintInteractionController sharedPrintController];
        UIPrintInfo *printInfo = [UIPrintInfo printInfo];
        printInfo.outputType = UIPrintInfoOutputGeneral;
        printInfo.jobName = self.title;
        printInfo.duplex = UIPrintInfoDuplexLongEdge;
        printInfo.orientation = UIPrintInfoOrientationLandscape;
        pic.printInfo = printInfo;
        UIViewPrintFormatter *viewPF = [self.inputTextView viewPrintFormatter];
        pic.printFormatter = viewPF;
        pic.showsPageRange = YES;
        [pic presentFromBarButtonItem:sender animated:YES
            completionHandler:
        ^(UIPrintInteractionController *printInteractionController, BOOL completed,
            NSError *error)
         {
             if (!completed && (error != nil))
             {
                 NSLog(@"Error Printing View: %@", error);
             }
             else
             {
                 NSLog(@"Printing Cancelled");
             }
         }];
    }
}

Unfortunately, the UIViewPrintFormatter is, at the moment, currently configured only to provide printing views of three system views: UITextView, UIWebView, and MKMapView. Because your application makes use of only one of these, you simply have it print your UITextView’s view, resulting in an output like that in Figure 13-13.

9781430245995_Fig13-13.jpg

Figure 13-13.  Simulated printing output, specifically of a UITextView

Despite the UIViewPrintFormatter’s limitations, it can provide an easy way to print the contents of any text, map, or web page.

Recipe 13-6: Formatted Printing with Page Renderers

A page renderer is essentially what allows you to fully customize the content of any print job. It allows you to not only format multiple pages with different print formatters, but also draw custom content in the header, body, and footer of any page.

To use a page renderer, you must create a custom subclass of the UIPrintPageRenderer class, from which you can override methods to customize the content of your printing job.

Create a new file, using the Objective-C class template. When you enter your filename of SendItOutPageRenderer, be sure that the file is a subclass of UIPrintPageRenderer, as in Figure 13-14.

9781430245995_Fig13-14.jpg

Figure 13-14.  Creating a UIPrintPageRenderer subclass

Click through to create your new file.

Next, define two NSString properties, title and author, in the header of your renderer.

//
//  SendItOutPageRenderer.h
//  Send It Out
//
 
#import <UIKit/UIKit.h>
 
@interface SendItOutPageRenderer : UIPrintPageRenderer
 
@property (nonatomic, strong) NSString *title;
@property (nonatomic, strong) NSString *author;
 
@end

To customize the layout of your specific page renderer, you can override methods inherited from the UIPrintPageRenderer class. The way that this class is set up is that the drawPageAtIndex:inRect: method then calls four other methods:

  • drawHeaderForPageAtIndex:inRect:: Used to specify header content; if the headerHeight property of the renderer is zero, this method will not be called.
  • drawContentForPageAtIndex:inRect:: Draws custom content within the page’s content rectangle.
  • drawFooterForPageAtIndex:inRect:: Specifies footer content; this method will also not be called if the renderer’s footerHeight property is zero.
  • drawPrintFormatter:forPageAtIndex:: Uses a combination of print formatters and custom content to overlay or fill in a view.

You can override any of these five methods (including drawPageAtIndex:inRect:) to customize your printing content. In your case, you override the header, footer, and print-formatter methods.

You have your header print the document’s author on the left, and the title on the right. Your method then looks like so:

- (void)drawHeaderForPageAtIndex:(NSInteger)pageIndex  inRect:(CGRect)headerRect
{
if (pageIndex != 0)
    {
        UIFont *font = [UIFont fontWithName:@"Helvetica" size:12.0];
        CGSize titleSize = [self.title sizeWithFont:font];
 
        CGFloat drawXTitle = CGRectGetMaxX(headerRect) - titleSize.width;
        CGFloat drawXAuthor = CGRectGetMinX(headerRect);
        CGFloat drawY = CGRectGetMinY(headerRect);
        CGPoint drawPointAuthor = CGPointMake(drawXAuthor, drawY);
        CGPoint drawPointTitle = CGPointMake(drawXTitle, drawY);
 
        [self.title drawAtPoint:drawPointTitle withFont:font];
        [self.author drawAtPoint:drawPointAuthor withFont:font];
    }
}

Your footer-implementation method looks similar, and prints a centered page number. Because the page indexes start with 0, you must remember to increment all your values by 1.

- (void)drawFooterForPageAtIndex:(NSInteger)pageIndex inRect:(CGRect)footerRect
{
    UIFont *font = [UIFont fontWithName:@"Helvetica" size:12.0];
    NSString *pageNumber = [NSString stringWithFormat:@"%d.", pageIndex+1];
 
    CGSize pageNumSize = [pageNumber sizeWithFont:font];
    CGFloat drawX = CGRectGetMaxX(footerRect)/2.0 - pageNumSize.width - 1.0;
    CGFloat drawY = CGRectGetMaxY(footerRect) - pageNumSize.height;
    CGPoint drawPoint = CGPointMake(drawX, drawY);
    [pageNumber drawAtPoint:drawPoint withFont:font];
}

Finally, to deal with interlaced print formatters, you implement the drawPrintFormatter:forPageAtIndex: method to overlay a simple text over your view. This could easily be used to place some kind of “Proprietary Content” label over images or documents in a more targeted application.

-(void)drawPrintFormatter:(UIPrintFormatter *)printFormatter forPageAtIndex:(NSInteger)pageIndex
{
    CGRect contentRect = CGRectMake(self.printableRect.origin.x,
        self.printableRect.origin.y+self.headerHeight, self.printableRect.size.width,
        self.printableRect.size.height-self.headerHeight-self.footerHeight);
    [printFormatter drawInRect:contentRect forPageAtIndex:pageIndex];
 
    NSString *overlayText = @"Overlay Text";
    UIFont *font = [UIFont fontWithName:@"Helvetica"size:26.0];
    CGSize overlaySize = [overlayText sizeWithFont:font];
 
    CGFloat xCenter = CGRectGetMaxX(self.printableRect)/2.0 - overlaySize.width/2.0;
    CGFloat yCenter = CGRectGetMaxY(self.printableRect)/2.0 - overlaySize.height/2.0;
    CGPoint overlayPoint = CGPointMake(xCenter, yCenter);
 
    [overlayText drawAtPoint:overlayPoint withFont:font];
}

In this method, it is important to note that you must draw the content of each printFormatter manually using its own drawInRect:forPageAtIndex: method. To avoid covering your header or footer, you specified a drawing area restricted by the headerHeight and footerHeight.

Now, back in your main view controller, be sure to import the newly created SendItOutPageRenderer.h file.

#import "SendItOutPageRenderer.h"

Add a final extra UIBarButtonItem to present a Print Custom option to your user. Including all functions from your previous recipes, your viewDidLoad method should now read like so:

- (void)viewDidLoad
{
    [super viewDidLoad];
    self.inputTextView.layer.cornerRadius = 15.0;
    self.inputTextView.delegate = self;
    [[NSNotificationCenter defaultCenter] addObserver:self
        selector:@selector(textMessagingAvailabilityChanged:)
        name:MFMessageComposeViewControllerTextMessageAvailabilityDidChangeNotification
        object:nil];
    self.title = @"Send It Out!";
    if ([UIPrintInteractionController isPrintingAvailable])
    {
        UIBarButtonItem *printButton =
            [[UIBarButtonItem alloc] initWithTitle:@"Print Image"
                style:UIBarButtonItemStyleBordered target:self
                action:@selector(print:)];
 
        UIBarButtonItem *printTextButton =
            [[UIBarButtonItem alloc] initWithTitle:@"Print Text"
                style:UIBarButtonItemStyleBordered target:self
                action:@selector(printText:)];
        UIBarButtonItem *printViewButton =
            [[UIBarButtonItem alloc] initWithTitle:@"Print View"
                style:UIBarButtonItemStyleBordered target:self
                action:@selector(printViewPressed:)];
        UIBarButtonItem *printCustomButton =
            [[UIBarButtonItem alloc] initWithTitle:@"Print Custom"
                style:UIBarButtonItemStyleBordered target:self
                action:@selector(printCustom:)];
        self.navigationItem.rightBarButtonItems = @[printButton, printTextButton,
            printViewButton, printCustomButton];
    }
}

Finally, you can implement your printCustom: action method:

-(void)printCustom:(id)sender
{
    if ([UIPrintInteractionController isPrintingAvailable])
    {
        UIPrintInteractionController *pic =
            [UIPrintInteractionController sharedPrintController];
        UIPrintInfo *printInfo = [UIPrintInfo printInfo];
        printInfo.outputType = UIPrintInfoOutputGeneral;
        printInfo.jobName = self.title;
        printInfo.duplex = UIPrintInfoDuplexLongEdge;
        printInfo.orientation = UIPrintInfoOrientationPortrait;
        pic.printInfo = printInfo;
        UISimpleTextPrintFormatter *simplePF =
            [[UISimpleTextPrintFormatter alloc] initWithText:[self.inputTextView.text
                stringByAppendingString:@"THIS TEXT IS MY FIRST PAGE"]];
        UIViewPrintFormatter *viewPF = [self.inputTextView viewPrintFormatter];
        SendItOutPageRenderer *sendPR = [[SendItOutPageRenderer alloc] init];
        sendPR.title = @"My Print Job Title";
        sendPR.author = @"Document Author";
        sendPR.headerHeight = 72.0/2;
        sendPR.footerHeight = 72.0/2;
        [sendPR addPrintFormatter:simplePF startingAtPageAtIndex:0];
        [sendPR addPrintFormatter:viewPF startingAtPageAtIndex:1];
        pic.printPageRenderer = sendPR;
        pic.showsPageRange = YES;
        [pic presentFromBarButtonItem:sender animated:YES
            completionHandler:
        ^(UIPrintInteractionController *printInteractionController, BOOL completed,
             NSError *error)
         {
             if (!completed && (error != nil))
             {
                 NSLog(@"Error Printing: %@", error);
             }
             else
             {
                 NSLog(@"Printing Cancelled");
             }
         }];
    }
}

This method includes the following extra steps from your previous recipe:

  1. Create multiple print formatters to be given to different pages. Because you do not have a UIWebView or MKMapView in this application, you have simply chosen to print your UITextView’s text, as well as its overall view.
  2. Create an instance of your SendItOutPageRenderer class, and configure it with a title, author, headerHeight, and footerHeight. If you did not specify the last two of these, your header and footer customization methods would not be called.
  3. Add your print formatters to your page renderer, and assign this renderer to your UIPrintInteractionController.

On testing this new functionality, your output is a two-page text document, complete with simple headers, footers, and even a text overlay, as shown in Figure 13-15.

9781430245995_Fig13-15.jpg

Figure 13-15.  Simulated printing output with a page renderer and multiple print formatters

Due to the simplicity of your application, the screenshot in Figure 13-14 may not look like much, but considering your application of custom headers, footers, overlay content, and page formatters, it actually gives a very good representation of the power of making use of a page renderer for printing when striving for ideal customizations.

Summary

When creating your applications, you are responsible to always have the user in mind. Every single aspect of your application should be designed to both allow and help the user to accomplish a goal, and each aspect should then be optimized to expedite these goals. Functionalities to transmit data, such as sending text messages, constructing emails, or creating printouts, tend to be overlooked as unnecessary in this process, and most often erroneously. Developers must always be careful to think from the user’s standpoint, and imagine what a user could do with any given feature. The simple possibility of printing content for later use, or being able to easily email an interesting image to a friend, could be the dividing line between what makes a customer buy your app over someone else’s. By understanding and utilizing these “extra” functionalities, you can drastically improve the functionality of your applications to better serve your end users.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset