UIPrintInteractionController
With all the different means to comment, mark up, save, and share right at our fingertips, it’s easy to overlook the value of a printed sheet of paper.
UIKit makes it easy to print straight from a user’s device with custom designs that you can adapt to both your content and the paper size. This article will first walk through how to format your content for printing, then detail the different ways to present (or not!) the printing interface.
The “printed” images throughout this article are taken from Apple’sPrinter Simulator. (The yellow edges represent the non-printable margins of the paper)
As of Xcode 6, the printer simulator must be downloaded as part of theHardware IO Tools for Xcode.
At the heart of theUIKit Printing APIs isUIPrint
. A shared instance of this class manages details of print jobs and configure any UI that will be presented to the user. It also provides three levels of control for the formatting of your content.
Printing is a Job
Before we look at formatting actual content for printing, let’s go through the options for configuring the print job and the print options presented to the user.
UIPrintInfo
Print job details are set in aUIPrint
instance. You can use the following properties:
job
Name String
: A name for this print job. The name will be displayed in the Print Center on the device and, for some printers, on the LCD display.orientation
UIPrint
: EitherInfo Orientation .Portrait
(the default) or.Landscape
—this is ignored if what you print has an intrinsic orientation, such as a PDF.duplex
UIPrint
:Info Duplex .None
,.Short
, orEdge .Long
. The short- and long-edge settings indicate how double-sided pages could be bound, whileEdge .None
suppresses double-sided printing (though not the UI toggle for duplexing, perplexingly).output
Type UIPrint
: Gives UIKit a hint about the type of content you’re printing. Can be any of:Info Output Type
.General
(default): For mixed text and graphics; allows duplexing..Grayscale
: Can be better than.General
if your content includes black text only..Photo
: For color or black and white images; disables duplexing and favors photo media for the paper type..Photo
: Can be better thanGrayscale .Photo
for grayscale-only images, depending on the printer.printer
ID String?
: An ID for a particular printer—you can retrieve this onlyafter the user has selected a printer through the UI and save it to use as a preset for a future print job.
In addition,UIPrint
provides adictionary
property, which can be saved and used to create a newUIPrint
instance later.
UIPrint Interaction Controller
Settings
There are a handful of settings on theUIPrint
that you can configure before displaying the printing UI. These include:
Info UIPrint
: The aforementioned print job configuration.Info Paper UIPrint
: A simple type that describes the physical and printable size of a paper type; except for specialized applications, this will be handled for you by UIKit.Paper shows
Number Of Copies Bool
: Whentrue
, lets the user choose the number of copies.shows
Page Range Bool
: Whentrue
, lets the user choose a sub-range from the printed material. This only makes sense with multi-page content—it’s turned off by default for images.shows
Paper Selection For Loaded Papers Bool
: When this istrue
and the selected printer has multiple paper options, the UI will let the user choose which paper to print on.
Formatting Your Content
Through four different properties ofUIPrint
, you can select the level of control (and complexity) you want for your content.
printing
Item Any
orObject! printing
Items [Any
: At the most basic level, the controller simply takes content that is already printable (images and PDFs) and sends them to the printer.Object]! Formatter UIPrint
: At the next level, you can use aFormatter UIPrint
subclass to format content inside your application, then hand the formatter off to theFormatter UIPrint
. You have some control over the format, and the printing API largely takes care of the rest.Interaction Controller Page Renderer UIPrint
: At the highest level, you can create a custom subclass ofPage Renderer UIPrint
, combining page formatters and your own drawing routines for headers, footers, and page content.Page Renderer
Since Thanksgiving (my favorite holiday) is around the corner, to illustrate these properties we’ll add printing to different screens of a hypothetical app for Thanksgiving recipes.
print Item
(s
)
Printing WithYou can print pre-existing printable content by setting either theprint
orprint
property ofUIPrint
. Images and PDFs can be given either as image data (in aNSData
,UIImage
, orALAsset
instance) or via anyNSURL
referencing something that can be loaded into anNSData
object. To be printable, images must be ina format thatUIImage
supports.
Let’s walk through a very simple case: showing the UI to print an image when the user taps a button. (We’ll look at alternate ways of initiating printing below.) The process will be largely the same, no matter what you’re printing—configure your print info, set up the print interaction controller, and provide your content before displaying the UI:
@IBActionfuncprint(sender:UIBar Button Item){ifUIPrint Interaction Controller.can Print URL(image URL){letprint Info=UIPrint Info(dictionary:nil)print Info.job Name=image URL.last Path Componentprint Info.output Type=.Photoletprint Controller=UIPrint Interaction Controller.shared Print Controller()!print Controller.print Info=print Infoprint Controller.shows Number Of Copies=falseprint Controller.printing Item=image URLprint Controller.present Animated(true,completion Handler:nil)}}
-(IBAction)print:(id)sender{if([UIPrint Interaction Controllercan Print URL:self.image URL]){UIPrint Info*print Info=[UIPrint Infoprint Info];print Info.job Name=self.image URL.last Path Component;print Info.output Type=UIPrint Info Output General;UIPrint Interaction Controller*print Controller=[UIPrint Interaction Controllershared Print Controller];print Controller.print Info=print Info;print Controller.printing Item=self.image URL;[print Controllerpresent Animated:truecompletion Handler:nil];}}
Easy as pie!(Or, in this case, sautéed Swiss chard.)
The
present
method is for presenting the printing UI on theiPhone. If printing from theiPad, use one of theAnimated(:completion Handler:) present
orFrom Bar Button Item(:animated:completion Handler:) present
methods instead.From Rect(:in View:animated:completion Handler:)
UIPrintFormatter
TheUIPrint
class has two subclasses that can be used to format text (UISimple
andUIMarkup
) plus another (UIView
) that can format the content of three views:UIText
,UIWeb
, andMKMap
. Print formatters have a few properties that allow you to define the printed area of the page in different ways; the final print area for the formatter will be the smallest rectangle that meets the following criteria:
content
Insets UIEdge
: A set of insets from the edges of the page for the entire block of content. The left and right insets are applied on every page, but the top inset isonly applied on the first page. The bottom inset is ignored.Insets per
Page Content Insets UIEdge
(iOS 8 only): A set of insets from the edges of the page forevery page of formatted content.Insets maximum
andContent Width maximum
Content Height CGFloat
: If specified, these can further constrain the width and height of the content area.
Though not clearly documented by Apple, all of these values are based on 72 points per inch.
The two text-based print formatters are initialized with the text they will be formatting.UISimple
can handle plain or attributed text, whileUIMarkup
takes and renders HTML text in itsmarkup
property. Let’s try sending an HTML version of our Swiss chard recipe through the markup formatter:
letformatter=UIMarkup Text Print Formatter(markup Text:html String)formatter.content Insets=UIEdge Insets(top:72,left:72,bottom:72,right:72)// 1" marginsprint Controller.print Formatter=formatter
UIMarkup Text Print Formatter*formatter=[[UIMarkup Text Print Formatteralloc]init With Markup Text:html String];formatter.content Insets=UIEdge Insets Make(72,72,72,72);// 1" marginsprint Controller.print Formatter=formatter;
The result? A handsomely rendered HTML page:
On the other hand, to use aUIView
, you retrieve one from the view you want to print via itsview
property. Here’s a look at how the formatter does its job for each of the three supported views:
1) UITextView
2) UIWebView
3) MKMapView
UIPrintPageRenderer
The built-in formatters are fine, but for themost control over the printed page, you can implement a subclass ofUIPrint
. In your subclass you can combine the print formatters we saw above with your own custom drawing routines to create terrific layouts for your app’s content. Let’s look at one more way of printing a recipe, this time using a page renderer to add a header and to draw the images alongside the text of the recipe.
In the initializer, we save the data that we’ll need to print, then set theheader
(the header and footer drawing methods won’t even be called unless you set their respective heights) and create a markup text formatter for the text of the recipe.
Complete Objective-C and Swift source code for the following examplesis available as a gist.
classRecipe Print Page Renderer:UIPrint Page Renderer{letauthor Name:Stringletrecipe:Recipeinit(author Name:String,recipe:Recipe){self.author Name=author Nameself.recipe=recipesuper.init()self.header Height=0.5*POINTS_PER_INCHself.footer Height=0.0// defaultletformatter=UIMarkup Text Print Formatter(markup Text:recipe.html)formatter.per Page Content Insets=UIEdge Insets(top:POINTS_PER_INCH,left:POINTS_PER_INCH,bottom:POINTS_PER_INCH,right:POINTS_PER_INCH*3.5)add Print Formatter(formatter,starting At Page At Index:0)}…}
@interfaceRecipe Print Page Renderer:UIPrint Page Renderer@property(nonatomic,strong)NSString*author Name;@property(nonatomic,strong)Recipe*recipe;-(id)init With Author Name:(NSString*)author Namerecipe:(Recipe*)recipe;@end@implementationRecipe Print Page Renderer-(id)init With Author Name:(NSString*)author Namerecipe:(Recipe*)recipe{if(self=[superinit]){self.author Name=author Name;self.recipe=recipe;self.header Height=0.5;self.footer Height=0.0;// defaultUIMarkup Text Print Formatter*formatter=[[UIMarkup Text Print Formatteralloc]init With Markup Text:recipe.html];formatter.per Page Content Insets=UIEdge Insets Make(POINTS_PER_INCH,POINTS_PER_INCH,POINTS_PER_INCH,POINTS_PER_INCH*3.5);[selfadd Print Formatter:formatterstarting At Page At Index:0];}returnself;}…@end
When you use one or more print formatters as part of your custom renderer (as we’re doing here), UIKit queries them for the number of pages to print. If you’re doing truly custom page layout, implement the
number
method to provide the correct value.Of Pages()
Next, we overridedraw
to draw our custom header. Unfortunately, those handy per-page content insets on print formatters are gone here, so we first need to inset theheader
parameter to match my margins, then simply draw into the current graphics context. There’s a similardraw
method for drawing the footer.
overridefuncdraw Header For Page At Index(page Index:Int,varin Rectheader Rect:CGRect){varheader Insets=UIEdge Insets(top:CGRect Get Min Y(header Rect),left:POINTS_PER_INCH,bottom:CGRect Get Max Y(paper Rect)-CGRect Get Max Y(header Rect),right:POINTS_PER_INCH)header Rect=UIEdge Insets Inset Rect(paper Rect,header Insets)// author name on leftauthor Name.draw At Point In Rect(header Rect,with Attributes:name Attributes,and Alignment:.Left Center)// page number on rightletpage Number String:NSString="\(page Index+1)"page Number String.draw At Point In Rect(header Rect,with Attributes:page Number Attributes,and Alignment:.Right Center)}
-(void)draw Header For Page At Index:(NSInteger)indexin Rect:(CGRect)header Rect{UIEdge Insetsheader Insets=UIEdge Insets Make(CGRect Get Min Y(header Rect),POINTS_PER_INCH,CGRect Get Max Y(self.paper Rect)-CGRect Get Max Y(header Rect),POINTS_PER_INCH);header Rect=UIEdge Insets Inset Rect(self.paper Rect,header Insets);// author name on left[self.author Namedraw At Point In Rect:header Rectwith Attributes:self.name Attributesand Alignment:NCString Alignment Left Center];// page number on rightNSString*page Number String=[NSStringstring With Format:@"%ld",index+1];[page Number Stringdraw At Point In Rect:header Rectwith Attributes:self.page Number Attributesand Alignment:NCString Alignment Right Center];}
Lastly, let’s provide an implementation ofdraw
:
overridefuncdraw Content For Page At Index(page Index:Int,in Rectcontent Rect:CGRect){ifpage Index==0{// only use rightmost two inches of content Rectletimages Rect Width=POINTS_PER_INCH*2letimages Rect Height=paper Rect.height-POINTS_PER_INCH-(CGRect Get Max Y(paper Rect)-CGRect Get Max Y(content Rect))letimages Rect=CGRect(x:CGRect Get Max X(paper Rect)-images Rect Width-POINTS_PER_INCH,y:paper Rect.origin.y+POINTS_PER_INCH,width:images Rect Width,height:images Rect Height)draw Images(recipe.images,in Rect:images Rect)}}
-(void)draw Content For Page At Index:(NSInteger)page Indexin Rect:(CGRect)content Rect{if(page Index==0){// only use rightmost two inches of content RectCGFloatimages Rect Width=POINTS_PER_INCH*2;CGFloatimages Rect Height=CGRect Get Height(self.paper Rect)-POINTS_PER_INCH-(CGRect Get Max Y(self.paper Rect)-CGRect Get Max Y(content Rect));CGRectimages Rect=CGRect Make(CGRect Get Max X(self.paper Rect)-images Rect Width-POINTS_PER_INCH,CGRect Get Min Y(self.paper Rect)+POINTS_PER_INCH,images Rect Width,images Rect Height);[selfdraw Images:self.recipe.imagesin Rect:images Rect];}}
With the implementation of our custom page renderer complete, we can set an instance as thepage
property on the print interaction controller and we’re ready to print.
letrenderer=Recipe Print Page Renderer(author Name:"Nate Cook",recipe:selected Recipe)print Controller.print Page Renderer=renderer
Recipe Print Page Renderer*renderer=[[Recipe Print Page Rendereralloc]init With Author Name:@"Nate Cook"recipe:selected Recipe];print Controller.print Page Renderer=renderer;
The final result is much nicer than any of the built-in formatters.
Note that the text of the recipe is being formatted by a
UIMarkup
, while the header and images are drawn via custom code.Text Formatter
Printing via a Share Sheet
With the tools we’ve learned above, adding printing capability in a share sheet is simple. Instead of usingUIPrint
to present the printing UI, we pass off our configuredUIPrint
and printing item(s), formatter, or renderer to aUIActivity
. If the user selects thePrint button in the share sheet, the printing UI will be displayed with all our configurations intact.
@IBActionfuncopen Share Sheet(){letprint Info=...letformatter=...letactivity Items=[print Info,formatter,text View.attributed Text]letactivity Controller=UIActivity View Controller(activity Items:activity Items,application Activities:nil)present View Controller(activity Controller,animated:true,completion:nil)}
-(IBAction)open Share Sheet:(id)sender{UIPrint Info*print Info=...UISimple Text Print Formatter*formatter=...NSArray*activity Items=@[print Info,formatter,self.text View.attributed Text];UIActivity View Controller*activity Controller=[[UIActivity View Controlleralloc]init With Activity Items:activity Itemsapplication Activities:nil];[selfpresent View Controller:activity Controlleranimated:YEScompletion:nil];}
While
UIPrint
and subclasses ofInfo UIPrint
andFormatter UIPrint
can be passed to aPage Renderer UIActivity
as activities, none of them seem to conform to theView Controller UIActivity
protocol, so you’ll see a (harmless) warning in your console about “Unknown activity items.”Item Source
Skipping the Printing UI
New in iOS 8 is a way to print without any presentation of the printing UI. Instead of presenting the UI each time the user presses a print button, you can provide a way for your users to select a printer somewhere in your app with the easy-to-useUIPrinter
. It accepts an optionalUIPrinter
instance in its constructor for a pre-selection, uses the same presentation options as explained above, and has a completion handler for when the user has selected her printer:
letprinter Picker=UIPrinter Picker Controller(initially Selected Printer:saved Printer)printer Picker.present Animated(true){(printer Picker,user Did Select,error)inifuser Did Select{self.saved Printer=printer Picker.selected Printer}}
UIPrinter Picker Controller*print Picker=[UIPrinter Picker Controllerprinter Picker Controller With Initially Selected Printer:self.saved Printer];[print Pickerpresent Animated:YEScompletion Handler:^(UIPrinter Picker Controller*printer Picker,BOOLuser Did Select,NSError*error){if(user Did Select){self.saved Printer=printer Picker.selected Printer;}}];
Now you can tell yourUIPrint
to print directly by callingprint
with the saved printer instead of using one of thepresent...
methods.
As one final recommendation, consider the printed page as you would any other way of interacting with your content. In the same way you scrutinize font size and weight or the contrast between elements on screen, make sure to test your print layoutson paper—the contrast, size, and margins should all be appropriate to the medium.