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-driven 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 will be able to be simulated.

Recipe 13–1: Composing Text Messages

Text messaging is quite easily one of the currently most incredibly 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 actually incorporate text messaging into your applications in order to easily provide your users with the simple cross-application functionality that can so easily improve the overall quality of an application.

Start off by creating a new project called “SendItOut”, which you will use throughout this chapter, with a class prefix “Main”. Select the Single View Application template to create a simple project, as in Figure 13–1.

Image

Figure 13–1. Creating a single view application

In order to fully demonstrate a few of the functionalities of this topic, you will specifically choose to develop your application for the iPad, rather than the iPhone. Make sure the Device Family is set accordingly in the next screen after entering the project's name. Since 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. Configure your project with the name “SendItOut”, class prefix “Main”, and make sure the Use Automatic Reference Counting box is checked, as in Figure 13–2.

Image

Figure 13–2. Configuring your project settings

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) to landscape, and make sure that the background color is set to gray.

You will 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 with property names textViewInput and textButton, and connect the button to an IBAction,-textPressed:.

Before you proceed, you will 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.

#import <QuartzCore/QuartzCore.h>

Next add the following line to the end of your -viewDidLoad method.

self.textViewInput.layer.cornerRadius = 15.0;

Your view should now resemble that simulated in Figure 13–3 once you rotate your simulator. This can be done through either the “Rotate Left” (Image+left) or “Rotate Right” (Image+right) commands found in the Hardware menu of the iOS simulator.

Image

Figure 13–3. A simulated view of your user interface

Next, you will configure your view controller as the delegate for your UITextView. Add the following line to your -viewDidLoad method.

self.textViewInput.delegate = self;

Next, you will add the MessageUI framework to your project. Do this under the Build Phases tab of your project as usual, and add the required import statement to your view controller's header file.

#import <MessageUI/MessageUI.h>

You need to instruct your view controller to conform to certain protocols. Adjust your header file so that the UITextViewDelegate, MFMessageComposeViewControllerDelegate, and UINavigationControllerDelegate are all conformed to.

At this point, your fully set-up header file will now read like so:

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

@interface MainViewController : UIViewController <UITextViewDelegate, MFMessageComposeViewControllerDelegate, UINavigationControllerDelegate>

@property (strong, nonatomic) IBOutlet UITextView *textViewInput;
@property (strong, nonatomic) IBOutlet UIButton *textButton;
-(IBAction)textPressed:(id)sender;

@end

Now you will implement one of your UITextView's delegate methods in order 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 FALSE;
    }
return TRUE;
}

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

-(void)textPressed:(id)sender
{
if ([MFMessageComposeViewController canSendText])
    {
MFMessageComposeViewController *messageVC = [[MFMessageComposeViewController alloc] init];
        messageVC.messageComposeDelegate = self;
        messageVC.recipients = [NSArray arrayWithObject:@"3015555309"];
        messageVC.body = self.textViewInput.text;
        [self presentModalViewController:messageVC animated:YES];
    }
else
    {
NSLog(@"Error, Text Messaging Unavailable");
    }
}

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

The MFMessageComposeViewController andits counterpart you will encounter later, the MFMailComposeViewController, 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.textViewInput.text = @"Message sent.";
    }
else if (result == MessageComposeResultFailed)
    {
NSLog(@"Message Failed to Send!");
    }
    [self dismissModalViewControllerAnimated:YES];
}

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 is 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:

[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(availabilityChange:)
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)availabilityChange:(id)sender
{
if ([MFMessageComposeViewController canSendText])
    {
NSLog(@"Text Messaging Available");
    }
else
    {
NSLog(@"Text Messaging Unavailable");
    }
}

Your application 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 will have to test this on your physical device with 3G capabilities. To test this application as it is exactly, you will need a 3G-capable iPad, but you could edit the project to work for an iPhone instead.

Recipe 13–2: Composing E-mail

Just as you were able to create and configure text messages to be sent from your application, you can also use the MessageUI framework that you dealt with in the previous recipe to configure mail messages using the counterpart to the MFMessageComposeViewController class, which is known as MFMailComposeViewController.

Building upon your set-up application, you will add another button with the label “Mail”. Use the property name mailButton, and assign it an action called -mailPressed:.

The setup for your -mailPressed: method is very similar to your previous -textPressed: method. You will create your composing view controller, configure it, and then present it.

-(void)mailPressed:(id)sender
{
if ([MFMailComposeViewController canSendMail])
    {
MFMailComposeViewController *mailVC = [[MFMailComposeViewController alloc] init];
        [mailVC setSubject:@"SendItOut"];
        [mailVC setToRecipients:[NSArray arrayWithObject:@"[email protected]"]];
        [mailVC setMessageBody:self.textViewInput.text isHTML:NO];
        mailVC.mailComposeDelegate = self;
        [self presentModalViewController:mailVC animated:YES];
    }
else
    {
NSLog(@"Error: Mail Unavailable");
    }
}

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

Since you set your mailComposeDelegate property to your view controller, you will need to specify that it will conform to the MFMailComposeViewControllerDelegate protocol, in addition to those already specified, so go ahead and add this to your header file.

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

-(void)mailComposeController:(MFMailComposeViewController *)controller
didFinishWithResult:(MFMailComposeResult)result error:(NSError *)error
{
if (result == MFMailComposeResultSent)
NSLog(@"Mail Successfully 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 dismissModalViewControllerAnimated:YES];
}

Now, your new application will be able to present a view controller for sending mail, as shown in Figure 13–4. Unlike the MFMessageViewController, however, you can actually test this functionality in the iOS simulator.

Image

Figure 13–4. Your application composing an e-mail

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

One additional issue that you may have to deal with when making use of the MFMailComposeViewController occurs if you allow the making of the recipients to be based on userinput. It is highly possible that a user may incorrectly format a mail address. This will not cause your application to throw an exception, though it may cause your recipient to simply be ignored by the MFMailComposeViewController. You can either format your user input to attempt to avoid this, or you can simply set up a regular expression to verify if the given address fits the required format.

Attaching Data to Mail

The MFMailComposeViewController includes functionality for you to attach data to your e-mail 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 NSStringthat defines to the controller the data type of the attachment. These values are not local to iOS, and so are not defined in the Apple documentation. They can, however, be easily found online. Wikipedia offers a very distinct article on all the possible values athttp://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 e-mail.

You will add functionality to your application to access the user's image library, select an image, and then use that image to attach to your e-mail.

Start off by adding a UIImageView underneath your UITextView, along with a UIButton underneath that with the label “Get Image”. Your view will now resemble that simulated in Figure 13–5.

Image

Figure 13–5. Your new user interface with ability to select an image

You will need properties imageViewContent andgetImageButton, along with the method -getImagePressed: for these. You will be presenting a UIPopoverController from your getImageButton, so make sure the sender type of this method is set to a UIButton* instead of id. Go ahead and make a UIImageproperty as well, called selectedImage, to store a reference to the chosen image, making sure to properly synthesize it.

Whenever you want to access the photo library of an iPad, you need to use a UIImagePickerController set inside of a UIPopoverController. You will need to create a UIPopoverController property called pop, and make sure to synthesize it as well.

Before you continue, instruct your view controller to conform to the UIImagePickerControllerDelegate and UIPopoverControllerDelegate protocols. Overall your header file should now resemble the following:

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

@interface MainViewController : UIViewController <UITextViewDelegate,
MFMessageComposeViewControllerDelegate, UINavigationControllerDelegate,
MFMailComposeViewControllerDelegate, UIImagePickerControllerDelegate,
UIPopoverControllerDelegate>

@property (strong, nonatomic) IBOutlet UITextView *textViewInput;
@property (strong, nonatomic) IBOutlet UIButton *textButton;
@property (strong, nonatomic) IBOutlet UIButton *mailButton;
@property (strong, nonatomic) IBOutlet UIImageView *imageViewContent;
@property (strong, nonatomic) IBOutlet UIButton *getImageButton;
@property (strong, nonatomic) UIImage *selectedImage;
@property (strong, nonatomic) UIPopoverController *pop;

-(IBAction)textPressed:(id)sender;
-(IBAction)mailPressed:(id)sender;
-(IBAction)getImagePressed:(UIButton *)sender;
///Pay close attention to the above (UIButton *) parameter type.

@end

If you have correctly handled all your new properties so far, your -viewDidUnload: method should resemble the following:

- (void)viewDidUnload
{
    [self setPop:nil];
    [self setSelectedImage:nil];
    [self setTextViewInput:nil];
    [self setTextButton:nil];
    [self setMailButton:nil];
    [self setImageViewContent:nil];
    [self setGetImageButton:nil];
    [super viewDidUnload];
}

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

-(void)getImagePressed:(UIButton *)sender
{
UIImagePickerController *picker = [[UIImagePickerController alloc] init];
if ([UIImagePickerController
isSourceTypeAvailable:UIImagePickerControllerSourceTypePhotoLibrary])
    {
        picker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
        picker.delegate = self;

self.pop = [[UIPopoverController alloc] initWithContentViewController:picker];
pop.delegate = self;
        [pop presentPopoverFromRect:sender.frame inView:self.view
permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
    }
}

Now you just need to implement your UIImagePickerControllerDelegate protocol methods.

-(void)imagePickerControllerDidCancel:(UIImagePickerController *)picker
{
    [self.pop dismissPopoverAnimated:YES];
}

-(void)imagePickerController:(UIImagePickerController *)picker
didFinishPickingMediaWithInfo:(NSDictionary *)info
{
UIImage *image = [info valueForKey:@"UIImagePickerControllerOriginalImage"];
self.selectedImage = image;
self.imageViewContent.image = image;
self.imageViewContent.contentMode = UIViewContentModeScaleAspectFill;

    [self.pop 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 accessing the Safari app on the simulator, finding an image online, then clicking and holding the image to save it to the library. In Figure 13–6, your app is shown with an image already selected, with the Get Image button then pressed again.

Image

Figure 13–6. Running your app and selecting an image to display

Now you can continue to add the chosen image into your e-mail.You will modify your -mailPressed: method to include attaching the image if one has been selected.

-(void)mailPressed:(id)sender
{
if ([MFMailComposeViewController canSendMail])
    {
MFMailComposeViewController *mailVC = [[MFMailComposeViewController alloc] init];
        [mailVC setSubject:@"SendItOut"];
        [mailVC setToRecipients:[NSArray arrayWithObject:@"[email protected]"]];
        [mailVC setMessageBody:self.textViewInput.text isHTML:NO];
        mailVC.mailComposeDelegate = self;

/////NEW IMAGE CODE
if (self.selectedImage != nil)
        {
NSData *imageData = UIImageJPEGRepresentation(self.selectedImage, 1.0);
            [mailVC addAttachmentData:imageData mimeType:@"image/jpeg" fileName:@"SelectedImage"];
        }
/////END OF NEW CODE

        [self presentModalViewController:mailVC animated:YES];
    }
else
    {
NSLog(@"Error: Mail Unavailable");
    }
}

Finally, you can modify your MFMailComposeViewController's delegate method to properly reset your application.

-(void)messageComposeViewController:(MFMessageComposeViewController *)controller
didFinishWithResult:(MessageComposeResult)result
{
if (result == MessageComposeResultSent)
    {
self.textViewInput.text = @"Message sent.";
self.selectedImage = nil;
self.imageViewContent.image = nil;
    }
else if (result == MessageComposeResultFailed)
    {
NSLog(@"Message Failed to Send!");
    }
    [self dismissModalViewControllerAnimated:YES];
}

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

Image

Figure 13–7. Your application composing e-mail with an attached image

Recipe 13–3: Printing an Image

Now that you have your application set up to be able to access 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, adjust your application delegate's -application:didFinishLaunchingWithOptions method like so:

- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
// Override point for customization after application launch.
self.viewController = [[MainViewController alloc] initWithNibName:@"MainViewController"
bundle:nil];

/////CHANGED CODE
__strong UINavigationController *navcon = [[UINavigationController alloc]
initWithRootViewController:self.viewController];
self.window.rootViewController = navcon;
/////END OF CHANGED CODE

    [self.window makeKeyAndVisible];
return YES;
}

You may also have to move up the lower buttons in your view a bit in order to make sure they aren't pushed off-screen by the navigation bar.

Add the following lines to the end of your -viewDidLoad method to configure your navigation bar.

self.title = @"Send It Out!";

if ([UIPrintInteractionController isPrintingAvailable])
{
UIBarButtonItem *printButton = [[UIBarButtonItem alloc]
initWithTitle:@"Print"style:UIBarButtonItemStyleBorderedtarget:self
action:@selector(printPressed:)];

self.navigationItem.rightBarButtonItem = printButton;
}

This condition will confirm to you that printing is possible on whichever device you run your application on before allowing the print button to be shown.

Now, you can continue on to implement your -printPressed: method in order to add your printing functionality, primarily through the use of the UIPrintInteractionController class. This class will be your “hub” of activity when it comes to configuring print jobs. You will go through and discuss the steps to setup 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.

You will 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 you will choose to simply print out your selectedImage.

UIImage *image = self.selectedImage;
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, you must simply 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 theapplication's Print button is placed in a toolbar, such as yours.
  • -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 -printPressed:method in its entirety will look like so:

-(void)printPressed:(id)sender
{
if ([UIPrintInteractionController isPrintingAvailable] && (self.selectedImage != nil))
    {
UIPrintInteractionController *pic = [UIPrintInteractionController
sharedPrintController];

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

UIImage *image = self.selectedImage;
        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 due to Domain: %@, Code: %@", error.domain, error.code);
             }
else
             {
NSLog(@"Printing Cancelled");
             }
         }];
    }
}

Now when you run your application, after selecting your image, a small controller will appear once you press the print button, 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–8.

Image

Figure 13–8. Your app with a new Print button, unable to find any AirPrint Printers

Luckily, when you installed the most recent version of Xcode, you were also given a fantastic application called Printer Simulator. With this program, you will be able to fully simulate print jobs from your application. 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!

You can easily open this program by searching for it in Spotlight.

Upon running the Printer Simulator application, a variety of printer types will be automatically registered for use. It will look similar to Figure 13–9.

Image

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

Now, upon testing your application, you should see multiple different types of simulated printers with which to test your application. You can choose different types to see how your printout is affected by the style of printer, as in Figure 13–10.

Image

Figure 13–10. Selecting a simulated printer from your app

Once you have selected a printer, you have the option to print multiple copies as well as change the paper type before you print. At this point, you should start seeing activity in your Printer Simulator, and shortly afterward, a PDF file will be opened with your final printout, resembling that shown in Figure 13–11.

Image

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, you will modify your -viewDidLoad method to add an extra button to print the text in your UITextView. Change the condition statement in the method that you've already made to look like so:

if ([UIPrintInteractionController isPrintingAvailable])
    {
UIBarButtonItem *printButton = [[UIBarButtonItemalloc] initWithTitle:@"Print Image"
style:UIBarButtonItemStyleBordered target:self action:@selector(printPressed:)];

UIBarButtonItem *printTextButton = [[UIBarButtonItem alloc] initWithTitle:@"Print Text"
style:UIBarButtonItemStyleBordered target:self action:@selector(printTextPressed:)];

self.navigationItem.rightBarButtonItems = [NSArray arrayWithObjects:printButton,
printTextButton, nil];
    }

The new selector to print your text will then be implemented asfollows:

-(void)printTextPressed:(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.textViewInput.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 due to Domain: %@, Code: %@", error.domain, error.code);
             }
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, since 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.
    • Values of 72.0 as insets translate to 1inch, so you have given your output 1-inch insets, and specified a 6-inch width for your content.
    • The statePageproperty will be used more at a later point, but allows you to specify the page in your job for your formatter to be applied to.

When printing simple text, it is also quite easy to apply the preceding method to printing out 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. Since you set your text view's text as the content of your print formatter, you will simply get a document with some Lorem Ipsum text in it, as in Figure 13–12.

Image

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

Recipe 13–5: Printing a View

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

Start by modifying your -viewDidLoad's conditional setup to now appear like so:

if ([UIPrintInteractionController isPrintingAvailable])
    {
UIBarButtonItem *printButton = [[UIBarButtonItem alloc] initWithTitle:@"Print Image"
style:UIBarButtonItemStyleBorderedtarget:self action:@selector(printPressed:)];

UIBarButtonItem *printTextButton = [[UIBarButtonItem alloc] initWithTitle:@"Print Text"
style:UIBarButtonItemStyleBordered target:self action:@selector(printTextPressed:)];

UIBarButtonItem *printViewButton = [[UIBarButtonItem alloc] initWithTitle:@"Print View"
style:UIBarButtonItemStyleBordered target:self action:@selector(printViewPressed:)];

self.navigationItem.rightBarButtonItems = [NSArray arrayWithObjects:printButton,
printTextButton, printViewButton, nil];
    }

Your newest printing method,-printViewPressed:, will closely resemble 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.textViewInput 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 due to Domain: %@, Code: %@", error.domain, error.code);
             }
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 (from the MapKit framework as discussed in Chapter 4). Since your application makes use of only one of these, you will simply have it print your UITextView's view, resulting in an output like that in Figure 13–13.

Image

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

Despite the UIViewPrintFormatter's limitations, it can be an incredibly easy to way easily print out 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.

In order 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 file name of “SendItOutPageRenderer”, make sure that the file will be a subclass of UIPrintPageRenderer, as in Figure 13–14.

Image

Figure 13–14. Creating a UIPrintPageRenderer subclass

Click through to create your new file.

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

#import <UIKit/UIKit.h>

@interface SendItOutPageRenderer : UIPrintPageRenderer

@property (nonatomic, strong) NSString *title;
@property (nonatomic, strong) NSString *author;

@end

In order 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 are able to override any of thesefive methods (including -drawPageAtIndex:inRect:) in order to customize your printing content. In your case, you will override the header, footer, and print-formatter methods.

You will have your header print out the document's author on the left, and the title on the right. Your method will then look 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 will look similar, and will print out a centered page number. Since 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 will 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 printFormattermanually using its own -drawInRect:forPageAtIndex: method. In order 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, make 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];

    [[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(availabilityChange:)
name:@"MFMessageComposeViewControllerTextMessageAvailabilityDidChangeNotification"
object:nil];

self.textViewInput.layer.cornerRadius = 15.0;
self.textViewInput.delegate = self;

self.title = @"Send It Out!";

if ([UIPrintInteractionController isPrintingAvailable])
    {
UIBarButtonItem *printButton = [[UIBarButtonItem alloc] initWithTitle:@"Print Image"
style:UIBarButtonItemStyleBordered target:self action:@selector(printPressed:)];

UIBarButtonItem *printTextButton = [[UIBarButtonItem alloc] initWithTitle:@"Print Text"
style:UIBarButtonItemStyleBordered target:self action:@selector(printTextPressed:)];

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(printCustomPressed:)];

self.navigationItem.rightBarButtonItems = [NSArray arrayWithObjects:printButton,
printTextButton, printViewButton, printCustomButton, nil];
    }
}

Finally, you can implement your -printCustomPressed: action.

-(void)printCustomPressed:(id)sender
{
if ([UIPrintInteractionControllerisPrintingAvailable])
    {
UIPrintInteractionController *pic = [UIPrintInteractionController
sharedPrintController];

UIPrintInfo *printInfo = [UIPrintInfoprintInfo];
        printInfo.outputType = UIPrintInfoOutputGeneral;
        printInfo.jobName = self.title;
        printInfo.duplex = UIPrintInfoDuplexLongEdge;
        printInfo.orientation = UIPrintInfoOrientationPortrait;
        pic.printInfo = printInfo;

UISimpleTextPrintFormatter *simplePF = [[UISimpleTextPrintFormatter alloc]
initWithText:[self.textViewInput.text stringByAppendingString:@"THIS TEXT IS MY FIRST
PAGE"]];
UIViewPrintFormatter *viewPF = [self.textViewInputview PrintFormatter];

SendItOutPageRenderer *sendPR = [[SendItOutPageRendereralloc] 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 due to Domain: %@, Code: %@", error.domain, error.code);
             }
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. Since 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.

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

Image

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–15may 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

You are responsible, when creating your applications, to always have the user in mind. Every single aspect of your application should be designed to both allow and helpthe 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 e-mails, 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 e-mail an interesting image to a friend, could easily be the dividing line between what makes a customer buy your app over someone else's. By understanding and utilizing these “extra” functionalities, you are able to drastically improve the functionality of your applications in order to better serve your endusers.

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

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