Chapter     16

Data Transmission Recipes

As time has progressed and technology has developed, one of the clearest trends has been 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 redistribute information. While the social sharing tools Apple provides (see Chapter 8) are great for sharing content with multitudes of people or quickly generating email and text, sometimes you want a more personal experience, such as composing text messages and emails directly or even allowing the user to print something to keep. This chapter will cover these important features, which are often overlooked by other iOS reference sources.

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

Recipe 16-1: Composing Text Messages

Text messaging is still one of the most popular methods to transmit data between individuals. It’s quick, easy, and powerful, and it’s 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 by creating a new project called “Recipe 16-1 Composing Text Messages,” which you will use throughout this chapter. As usual, use the Single View Application template to create the project.

After clicking through to create your project, switch to the Main.storyboard file to begin editing the interface. Add 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 the property names inputTextView and textMessageButton. Set the background of the view controller to “Group Table View Background Color.” Also, create an action named “textMessage” for the button.

Before you proceed, import the QuartzCore framework into the view controller’s header file, as shown in Listing 16-1. This will allow you to add a corner radius to the text view, which will give it a nicer look.

Listing 16-1.  Importing QuartzCore into the ViewController.h file

//
//  ViewController.h
//  Recipe 16-1 Composing Text Messages
//

#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

Now add the line shown in bold in Listing 16-2 to the viewDidLoad method. This will create the rounded corners on the text view.

Listing 16-2.  Creating the text view rounded corners

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

You can now test the app in the iPhone simulator. You won’t need a physical device until you are ready to send a text message. Your view should now resemble the view simulated in Figure 16-1.

9781430259596_Fig16-01.jpg

Figure 16-1. A simulated view of your user interface

To properly dismiss the keyboard, set the view controller as the delegate for your UITextView. Add the UITextViewDelegate protocol to the view controller’s @interface declaration, as shown in Listing 16-3.

Listing 16-3.  Setting the UITextViewDelegate protocol

//
//  ViewController.h
//  Recipe 16-1 Composing Text Messages
//

// ...

@interface ViewController : UIViewController <UITextViewDelegate>

// ...

@end

Now assign the text view’s delegate property in the viewDidLoad method of the ViewController.m, as shown in Listing 16-4.

Listing 16-4.  Setting the text view delegate property

- (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 General tab, with the root project selected, from the project navigator (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. Listing 16-5 shows these additions to the ViewController.h file.

Listing 16-5.  Importing the framework and declaring a message compose delegate

//
//  ViewController.h
//  Recipe 16-1 Composing Text Messages
//

#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 will need to implement one of the UITextView’s delegate methods, as shown in Listing 16-6. This will ensure that your keyboard is properly dismissed when the user taps the “Enter” key.

Listing 16-6.  Implementing the textView:shouldChangeTextInRange:replacemetnText:Delegate method

- (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. For this example, simply set the recipient to a fake number. This implementation is shown in Listing 16-7.

Listing 16-7.  Filling out the textMessage: action method

-(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 the textMessage: method should appear fairly straightforward. After using the canSendText method to check for texting availability, create an instance of the MFMessageComposeViewController class and then configure it with your fake recipient as well as the intended text. Finally, 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, as shown in Listing 16-8.

Listing 16-8.  Implementing the messageComposeViewController:didFinishWithResult: delegate method

-(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 preceding code, there is a third result, MessageComposeResultCancelled, which indicates that the user has cancelled the sending of the text message.

An added functionality in iOS 5.0 was the ability to receive notifications about the changing of the availability of text messaging. This functionality remains in iOS 7. You can register for such notifications by adding the following line to the viewDidLoad method in ViewController.m, as shown in Listing 16-9.

Listing 16-9.  Registering an NSNotification for messaging availability

- (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 in Listing 16-9 can be defined so as 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 we will avoid this process for demonstration purposes. Listing 16-10 shows the implementation of the method called in Listing 16-9.

Listing 16-10.  Implementing the textMessagingAvailabilityChanged: method

-(void)textMessagingAvailabilityChanged:(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 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 cellular capabilities. Your simulation should appear as it does in Figure 16-2 once the “Text Message” button is clicked.

9781430259596_Fig16-02.jpg

Figure 16-2. A simulated view showing the copied text

Recipe 16-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 email messages using the counterpart to the MFMessageComposeViewController class, which is MFMailComposeViewController.

Building on the application from Recipe 16-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 16-1 with the MFMessageComposeViewController, you’ll prepare the main view controller to be a delegate of MFMailComposeViewController. Your ViewController.h should now look like Listing 16-11.

Listing 16-11.  The complete ViewController.h file

//
//  ViewController.h
//  Recipe 16-2 Composing Composing Email
//

#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. Listing 16-12 shows the implementation.

Listing 16-12.  Implementing the mailMessage: method

- (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 different properties than the MFMessageComposeViewController that specifically configures a more complex email. These properties correlate to what you would expect from an email: a subject, recipients, and message body.

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. Give this a simple implementation to log the result, as shown in Listing 16-13.

Listing 16-13.  Implementing the mailComposeController:didFinishWithResult:error: method

-(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 email, as shown in Figure 16-3. Unlike the MFMessageViewController, however, you can actually test this functionality in the iOS simulator.

9781430259596_Fig16-03.jpg

Figure 16-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 multiple emails to any addresses, real or fake. The simulator does not actually send 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 has three parameters:

  • attachment: This parameter, an 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.
  • mimeType: This parameter 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.
  • fileName: This parameter is an NSString property to set the preferred name for the file sent in the email.

You will 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 by adding a UIImageView under 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, select its Clip Subviews attribute in the attributes inspector. Your view should now resemble Figure 16-4.

9781430259596_Fig16-04.jpg

Figure 16-4. Your new user interface with the 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 iPhone, you need to use a UIImagePickerController set inside a view controller. Declare a UIImagePickerController property called “picker” in the main view controller’s header file. You also need to instruct the view controller to conform to the UIImagePickerControllerDelegate and UINavigationControllerDelegate protocols. Overall your header file should now resemble Listing 16-14.

Listing 16-14.  The complete ViewController.h file

//
//  ViewController.h
//  Recipe 16-2 Composing Email
//

#import <UIKit/UIKit.h>
#import <QuartzCore/QuartzCore.h>
#import <MessageUI/MessageUI.h>

@interface ViewController : UIViewController<UITextViewDelegate,
    MFMessageComposeViewControllerDelegate, MFMailComposeViewControllerDelegate ,
    UIImagePickerControllerDelegate,
    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) UIImagePickerController *picker;

- (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, as shown in Listing 16-15.

Listing 16-15.  Implementing the getImage: method

- (IBAction)getImage:(id)sender
{
    self.picker = [[UIImagePickerController alloc] init];
    if ([UIImagePickerController
         isSourceTypeAvailable:UIImagePickerControllerSourceTypePhotoLibrary])
    {
        self.picker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
        self.picker.delegate = self;
        
        [self presentViewController:self.picker animated:YES completion:nil];
    }
}

Now you just need to implement your UIImagePickerControllerDelegate protocol methods, as shown in Listing 16-16.

Listing 16-16.  Implementing the UIImagePickerControllerDelegate protocol methods

-(void)imagePickerControllerDidCancel:(UIImagePickerController *)picker
{
    [self.picker dismissViewControllerAnimated:YES completion:nil];
}

-(void)imagePickerController:(UIImagePickerController *)picker
didFinishPickingMediaWithInfo:(NSDictionary *)info

{
    self.imageView.image = [info valueForKey:@"UIImagePickerControllerOriginalImage"];
    self.imageView.contentMode = UIViewContentModeScaleAspectFill;
    
    [self.picker dismissViewControllerAnimated:YES completion:nil];
}

As a final touch, to keep with the rounded corners theme, add a 15-point radius to the image view by setting the cornerRadius property in the viewDidLoad method, as shown in Listing 16-17.

Listing 16-17.  Adding a cornerRadius property to the image view

- (void)viewDidLoad
{
    [super viewDidLoad];
        // Do any additional setup after loading the view, typically from a nib.
    self.inputTextView.layer.cornerRadius = 15.0;
    self.imageView.layer.cornerRadius = 15.0;
    self.inputTextView.delegate = self;

//...

}

At this point, if you run the application you 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 16-5, the app is shown with an image already selected.

9781430259596_Fig16-05.jpg

Figure 16-5. Running the 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. Listing 16-18 shows this change.

Listing 16-18.  Modifying the mailMessage: method to attach an image

- (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 once the email has been successfully sent (Listing 16-19).

Listing 16-19.  Modifying the mailComposeController:didFinishWithResult:error: method

-(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 attempt to send an email after selecting an image, you should see the chosen image inserted into your message, as in Figure 16-6.

9781430259596_Fig16-06.jpg

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

Recipe 16-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.

For this recipe we’ll be building on the preceding recipe. Start by adding a new button titled “Print Image” and arrange it as shown in Figure 16-7. Add an action for the button titled “print” as well.

9781430259596_Fig16-07.jpg

Figure 16-7. Interface with added “Print Image” button

Now you can add the print: action method 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 will discuss the steps to set up this class individually before we look at 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, as shown in Listing 16-20.

Listing 16-20.  Creating a UIPrintInteractionController instance

UIPrintInteractionController *pic = [UIPrintInteractionController sharedPrintController];

Up next, you must configure the printInfo property of your controller, which specifies the settings for the print job. This is shown in Listing 16-21.

Listing 16-21.  Configuring the printInfo property

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

As you can see in Listing 16-21, 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 more configuration shortly.

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

  • Set a single item to be printed.
  • Set multiple items to be printed.
  • Specify an instance of UIPrintFormatter to the controller to configure the layout of your page.
  • 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. To use these more simple options, this item must be either an image or a PDF file, so we’ll simply print your selectedImage, as shown in Listing 16-22.

Listing 16-22.  Setting an image for print

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, as shown in Listing 16-23. You don’t need to specify a portrait orientation as this is the default case.

Listing 16-23.  Configuring the printInfo

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.
  • 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 rectangle 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. We’ll be using this method in our project.

With this final method call, your print: method in its entirety will look like Listing 16-24.

Listing 16-24.  The full print: method implementation

- (IBAction)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 presentAnimated:YES completionHandler:
         ^(UIPrintInteractionController *printInteractionController, BOOL completed,
           NSError *error)
         {
             if (!completed && (error != nil))
             {
                 NSLog(@"Error Printing: %@", error);
             }
             else
             {
                 NSLog(@"Printing Completed");
             }
         }];
    }
}

Now when you run your application and select an image, you can print the image by tapping the “Print” button. This presents a view 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, such as printers compatible with the AirPrint technology that is built into the iOS operating system, you won’t see any available printers to use, as shown in Figure 16-8.

9781430259596_Fig16-08.jpg

Figure 16-8. 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, Choose File arrow.jpg Open Printer Simulator with the simulator open, as shown in Figure 16-9.

9781430259596_Fig16-09.jpg

Figure 16-9. 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 16-10.

9781430259596_Fig16-10.jpg

Figure 16-10. Printer Simulator registering multiple types of printers to simulate

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 shown in Figure 16-11.

9781430259596_Fig16-11.jpg

Figure 16-11. 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 16-12.

9781430259596_Fig16-12.jpg

Figure 16-12. Output of printing an image from a simulated printer

Recipe 16-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, add a new button titled “Print Text” and create an action for it titled “printText.”

The new method for printing your text is then implemented, as shown in Listing 16-25.

Listing 16-25.  Implementing the printText: method

- (IBAction)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 presentAnimated:YES completionHandler:
         ^(UIPrintInteractionController *printInteractionController, BOOL completed,
           NSError *error)
         {
             if (!completed && (error != nil))
             {
                 NSLog(@"Error Printing: %@", error);
             }
             else
             {
                 NSLog(@"Printing Completed");
             }
         }];
    }
}

There are two main differences between this method and the print method shown in Listing 16-24 of the last recipe.

  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.
    • a.  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.
    • b.  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 16-13.

9781430259596_Fig16-13.jpg

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

Recipe 16-5: Printing a View

This recipe builds on Recipe 16-4, except that this time, rather than printing plain text, we will print a text view.

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

Start by creating a new button titled “Print view” and create an action for it titled “printViewPressed.”

Your newest printing method, printView:, closely resembles your preceding one, printText: from Recipe 16-4, with the key change of using a UIViewPrintFormatter instead. Listing 16-26 shows this implementation.

Listing 16-26.  Implementing the printViewPressed: method

- (IBAction)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 presentAnimated:YES completionHandler:
         ^(UIPrintInteractionController *printInteractionController, BOOL completed,
           NSError *error)
         {
             if (!completed && (error != nil))
             {
                 NSLog(@"Error Printing View: %@", error);
             }
             else
             {
                 NSLog(@"Printing Completed");
             }
         }];
    }
}

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 16-14.

9781430259596_Fig16-14.jpg

Figure 16-14. Simulated printing output, specifically of a UITextView

Note   Although we’re showing how to print a UITextView, the UIViewPrintFormatter class is capable of printing any UIView object.

That concludes this recipe. In the next recipe we’ll cover a custom formatted print using page renderers.

Recipe 16-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 to 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 DTPageRenderer, be sure that the file is a subclass of UIPrintPageRenderer, as shown in Figure 16-15.

9781430259596_Fig16-15.jpg

Figure 16-15. 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.

Listing 16-27.  Adding properties to the DTPageRenderer.h file

//
//  DTPageRenderer.h
//  Recipe 16-6 Formatted Printing with Page Renderers
//

#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 will have your header print the document’s author on the left and the title on the right. The method to do this looks like Listing 16-28.

Listing 16-28.  Implementing the drawHeaderForPageAtIndex:inRect: override method

- (void)drawHeaderForPageAtIndex:(NSInteger)pageIndex  inRect:(CGRect)headerRect
{
    if (pageIndex == 0)
    {
        UIFont *font = [UIFont fontWithName:@"Helvetica" size:12.0];
        CGSize titleSize = [self.title sizeWithAttributes:@{NSFontAttributeName: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 withAttributes:@{NSFontAttributeName:font}];
        [self.author drawAtPoint:drawPointAuthor withAttributes:@{NSFontAttributeName: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. Listing 16-29 shows this implementation.

Listing 16-29.  Implementing the drawFooterPageAtIndex:inRect: override method

- (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 sizeWithAttributes:@{NSFontAttributeName: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 withAttributes:@{NSFontAttributeName:font}];
}

Finally, to deal with interlaced print formatters, implement the drawPrintFormatter:forPageAtIndex: method shown in Listing 16-30 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.

Listing 16-30.  Implementing the drawPrintFormatter:forPageAtIndex: override method

-(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 sizeWithAttributes:@{NSFontAttributeName: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 withAttributes:@{NSFontAttributeName: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 implementation file, be sure to import the newly created DTPageRenderer.h file:

#import "DTPageRenderer.h"

Add a new button to the layout titled “Print Custom” and give it an action titled “printCustom.” As usual, you will need to implement the action method, which is shown in Listing 16-31.

Listing 16-31.  Implementing the printCustom: method

- (IBAction)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:@"iOS 7 Recipes"]];
        
        DTPageRenderer *sendPR = [[DTPageRenderer 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];

        pic.printPageRenderer = sendPR;
        
        pic.showsPageRange = YES;
        
        [pic presentAnimated:YES completionHandler:
         ^(UIPrintInteractionController *printInteractionController, BOOL completed,
           NSError *error)
         {
             if (!completed && (error != nil))
             {
                 NSLog(@"Error Printing: %@", error);
             }
             else
             {
                 NSLog(@"Printing Completed");
             }
         }];
    }
}

This method includes the following extra steps from Recipe 16-5:

  1. Create print formatters to be given to different pages. For this recipe, we have simply chosen to create one text view print formatter.
  2. Create an instance of your DTRenderer 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 will be a one-page text document, complete with simple headers, footers, and even a text overlay, as shown in Figure 16-16.

9781430259596_Fig16-16.jpg

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

Due to the simplicity of your application, the screen shot in Figure 16-16 might not look like much, but considering your application of custom headers, footers, overlay content, and a page formatter, 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 should always have the user in mind. Every single aspect of your application should be designed to both allow and help the user 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, 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 dramatically 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