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.
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.
Figure 16-2. A simulated view showing the copied text
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.
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.
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:
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.
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.
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.
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.
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:
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:
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:
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.
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 Open Printer Simulator with the simulator open, as shown in Figure 16-9.
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.
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.
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.
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.
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.
Figure 16-13. Output of the simulated printing of a simple text page
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 UIPrintFormatter—UIViewPrintFormatter.
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.
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.
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:
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:
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.
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.