App Frameworks • iOS • 49:47
iOS now contains a full featured printing system that allows any application to create great printed output. In this session you will learn about how printing works on iOS, the printing models available to your application and how to determine which approach is best. We will provide step by step walk through of the necessary APIs and program structure to allow your application to printing with desktop quality and richness, with a minimum of development effort.
Speakers: Dave DeLong, Howard Miller, Andrew Platzer
Unlisted on Apple Developer site
Downloads from Apple
Transcript
This transcript was generated using Whisper, it has known transcription errors. We are working on an improved version.
I'm Howard Miller. I am the manager for the printing engineering team. Today I'll be assisted by Andrew Platzer from iOS Applications and Frameworks and we have Dave DeLoong who is going to do a demo. Our topics for today, we're going to talk about the printing system in iOS. We're going to talk about how the iOS printing paradigm is different than desktop printing paradigms and then we're going to go through in detail exactly what you need to do to adopt printing in your application.
So the iOS printing system, we got to start from the ground up. Everybody here has printed from the desktop. Raise your hand if you've never printed from the desktop. It's only like two people out here. It's a little bit painful no matter what platform you're on. We think the Mac is the best though. iOS we went for great user experience, ease of use. A user should be able to print anything and get high quality output without a lot of hassle. We want it to be driverless, no software to install, no configuration.
The typical Windows user spends four hours setting up a new printer. A Mac user may not spend four hours, but that's only because I have three and a half gigabytes of drivers that Apple hosts on its server so you can get the right driver. And iOS, we didn't want to have customers deal with that pain. And then from an application perspective, we wanted to make sure that printing was super easy to adopt in your application, yet we wanted to provide you a full featured and powerful printing system.
So let's talk about Apple's printing system architectures. We actually have two, they're very closely related. Mac OS X, for those who are Mac OS X developers have seen this diagram before. Your application sat on the top of a myriad of APIs, Cocoa APIs, Carbon APIs, Cups APIs. We had the entire graphic system at your disposal. You could write a rich application and then you could spend months trying to figure out how to get it to print.
The printing system is layered on top of our world class spooling system, Cups, which is used on all platforms except for Microsoft. And then as I mentioned, our three and a half gigabytes of printer drivers and you could talk to over 50,000 printers. In Lion, we introduced AirPrint. Again, no third party drivers involved, directly to the AirPrint printers. Going to Mac OS or iOS, we kept the best parts of the Mac OS X printing system.
Our underlying printing system and the spooling system are the industry standard systems that you have come used to. We put a very thin set of APIs on top of that printing system to make it very easy for your application to quickly adopt printing. And then on iOS, the only printers that we print to are AirPrint enabled printers.
So let me talk a bit about AirPrint. This presentation is not about AirPrint. It's about what you do in your application, but I want you to know what's going on with this protocol. Again, we designed it for great user experience, no drivers, full quality output. It's supported on iOS 4.2 and later, multitasking devices only, so your really old devices are not going to work. And then it's built into Lion, so 10.7. It's standard based, Bonjour, IPP, your alphabet soup, and it is a zero cost license for all printer manufacturers to adopt AirPrint.
So we introduced this in fall of 2010 in iOS 4.2 and we started with HP. Everybody says, I read it in the press all the time, why did Apple only pick HP? Well, it's pretty simple. HP sells more than half of all printers sold in the world. If HP likes your protocol, if HP likes your PDL, you actually have a pretty good start in the world. So HP gave us in the beginning a list of printers.
Moving forward into 2011, HP is continuing to drive forward with AirPrint. They have already shipped to date 10 million AirPrint-enabled printers and 2 million printers that are in the field that are now firmware upgradable to support AirPrint. They've already given us a list of printers in 2010 so far, or 2011. By the end of 2011, virtually every network printer that HP makes, OfficeJet, DeskJet, LaserJet, will all be AirPrint-enabled. And of course, HP is not the only vendor. In 2011 you will see printers from all of your favorite manufacturers.
So what does it take to make an app that prints? Not a lot. When we introduced this, we started with our four main apps on the iOS. We adopted printing in Safari, Books, Mail, Photos, and then the hidden application, Quick Look. If you can see something in Quick Look, we can also print it.
In iOS 5, we added the two outliers, Notes and Maps. And then, of course, Apple's productivity apps also all print. And then there are, this slide will say hundreds, but there are thousands of apps that have already adopted printing. Of course, my legal department wouldn't let me put your icon up here, so I apologize.
So the IOS printing paradigm, how is this different than the desktop? The desktop is based on an old model of pick a printer, and then years later run an app where you create a document that you later print. IOS, everything is dynamic. When the user decides they want to print, that's the first time you find out what printer you're going to get. At that point, your application will figure out what paper sizes are available. And in some modern printers that we're working on now, exactly what paper is loaded into the printer.
You get to do your layout at print time. So you think of Safari, right? Web pages are designed for users to read on the screen. Print is not their focus. So laying out for print doesn't have to happen until the user is finally on the web page that they want, and they want to print. There are still a few document-centric applications in the world. By that, what I mean are documents for which you're creating the content for a piece of paper. Think pages.
Pages, you would lay out your newsletter so that you can print it. So that it could be printed on a piece of paper. But those are now rare. So the printing system has been reoriented towards dynamic content, dynamic content layout at print time. And then just to make it easy for the user, we've tried to eliminate all of the superfluous options and just have the few focused options that users need to have.
So for you guys to get your application ready for printing, I talk about format for paper, not for the display. We love our retina display. Those 750,000 pixels are great. But a typical piece of paper, A4 or letter is about 8 million dots at the lowest resolution that we support. You have a lot more space.
You have a lot higher contrast in output. And you have a lot more pixels to work with. So you should be able to do a lot more with that space. High quality drawing, let me talk about that. Because it's higher resolution, probably the icons that you're putting on screen that look great might not be of high enough resolution to look great when you print. And then I want to talk about the content.
You guys are already doing this. If you're on an iPhone or an iPod touch, you have limited screen real estate and you've decided what limited information to put in front of the user. But your iPad version of that content has more space. And so you've added in additional information for the user. Paper is like that. There's even more space. So these are drawn to scale.
Here is an iPhone application. This is our Maps application. Just the essential information is put in front of the user at that point. When we implemented for iPad, there is additional information about this map that would be of use to the user. We can put that on the iPad because there's more space.
And I know you guys just got a little bit of a hard time with this. But I want to show you how to do that. So you can see that the iPad is not only the same as the iPad but the iPad is the same as the iPad. So you can see that the iPad is the same as the iPad.
And you can see that the iPad is the same as the iPad. So you can see that the iPad is the same as the iPad. And you can see that the iPad is the same as the iPad. And you can see that the iPad is the same as the iPad.
And you can see that the iPad is the same So with that, I'm going to bring up Andrew Platzer who's going to take you through each of our APIs that are going to show you how to print. Now while we don't have free ice cream for you, we actually do have a complete set of printing APIs that will make it easy for you to adopt printing.
Good afternoon, my name is Andrew Platzer and I work on UIKit and on printing and I'm going to take you through the printing API so that you can add printing to your application. So as Howard mentioned, it's much simpler model than you have on the desktop. We don't have any more drivers, so you don't have to worry about trying to configure printing for each particular type of printer. And we don't have a huge number of different panels that the user needs to set up. So instead we've given you a much, much simpler API.
One that allows you to print very much more compartmentalized or to print items directly, I'll talk about that. There's no page setup. So just assume whatever paper size we give you, whatever printable area we give you, you should format for that. You shouldn't try to sort of plan ahead or anything like that.
There's no, when you go to print, printing is a modal task. So we'll take over the HI when you say presenting the sheet or whatever and we'll let the user enter all their information, run the print operation and so on, send off the information to the printer spooler which will go out to the printer and then return control back to your application.
And because you're working from a very small screen on the phone, it's not WYSIWYG. You don't have to, you're not going to be able to present all the information that you want to be able to print out. So we're not going to sort of have a view-based drawing system like we do on Mac OS X.
So what can you print? We've broken things down into sort of three different types of things to print. The easiest are items. These are pre-canned, printable objects like JPEGs or PDFs. You can just give them to us and we'll print them. You don't have to do anything at all. Now if you've got something a bit more complex that requires pagination for printing such as text, we've used something called print formatters.
And these you can print simple text or markup text and we'll do the right thing when we're printing across page boundaries. And then finally if you want to do your own drawing, you draw your own images, your own labels and text, etc. or a core text or something like that, we give you print page renderers and those are objects effectively sort of like the equivalent of a data source for printing.
So as I said, it's a very simple API and these are really only classes you're ever going to need to worry about. This is UI Print, the main class, sorry, is UI Print Interaction Controller. It's a singleton, you'll ask for it, it's a shared object. And there's a couple of informational classes that go along with it, Print Info and Print Paper.
Now when you're printing text, we have give you two formatters, one for simple text with a few settings and one that just takes a string of markup text, HTML text that we'll print out. And then finally we've got a somewhat abstract base class called UI Print Page Renderer that you'll subclass to do each individual page drawing or to calculate the number of pages.
So first I want to give you an example of how to print an image or a PDF, something that already knows how to print in a single page or across multiple pages. It's already been paginated. And for the simplest case, you really only need to do three things. One is ask for the shared print interaction controller, and that's an object that you'll talk to to do all your printing.
You'll set the data. You'll give us the data or URL or whatever it is you want to print, the JPEG that you've got, you download it from the internet or whatever. And then finally you just present it using sort of the standard idioms for presenting on the iPhone or the iPad.
And so here's some code. This is all you need to do to print a JPEG or a PDF. You'll get the shared printed action controller. Again, you don't need to allocate it, just ask for it. Set the item. So in this case it's some data or it could be URL or some other objects. And then on the phone here we've got a present animated with a completion handler that gets called when the printing is finished.
And on the iPad there's a somewhat slight different set of calls. But in the end we'll put up a sheet, cover your HI, handle all the interaction with the user. And then when the user says print, we'll call into your app if we need any more information, send off the job to the printer and then return control back to you.
Now you wanna do a couple of extra steps to really get a full check, to really do a full printing system. There's a few extra steps. You want to do, you wanna check the printing, so you wanna make sure -- or check the data, sorry -- that you can actually print the data.
You wanna tell us the output type, and this is very important because it'll help us and help the user choose which kind of paper they wanna print on or what size of paper. And there's some additional print settings to help with the output to specify other options the user to choose from.
So here's a full example. And this is something, if you're just printing a JPEG or a PDF, this is literally the code you'll want to do. So the existing code's already there. We'll add a few extra steps. First, before we even begin printing, we want to make sure that we can print. This method takes a URL. And we'll read the URL and just verify that it's printable. It may not be for some various reasons.
We'll then set the print info. This is an object that takes some various settings. You'll allocate a new one, a fresh one, just calling a print info method on the class. Here we're telling it that it's going to be a grayscale output. And then we'll set the controller's print info, and it'll make a copy of this information.
And then we have a couple of extra settings, as I mentioned. One is that in the print info, you can tell us the job name so that later on, when the user's looking at a list of their printing jobs, they'll see what the name of it is. And the other is to tell the controller that, well, I want to show a page range control, which is an optional interface element. So if you know, for example, that this is a PDF, you can tell us, oh, well, let them choose a page range if you want to let them.
So in your HI, in general you'll be able to print almost any device, but there are some, the really old ones won't be able to print or maybe who knows what's coming up. You'll want to check that printing is available. There's a class method, is printing available? And you should check to see if this returns yes. If it returns no, hide your interface. Usually it's a print button and an alert like you see here where you just have a print. Or maybe there's a single bar button item that is your print item. You'll want to hide that in that case.
Now that you know that you can print, here's what you can print. We take single items, as I mentioned, PDFs, JPEGs, PNGs, anything Image.io can support. Or if you're using the image picker to get something from your photo album, we return that as either an AL asset or an AL asset URL.
And you can pass that to us directly and we'll just read that and hand it off to the printing system. So you don't need to load a large image into your application taking up a lot of memory. You can just get the URL and pass it to us.
Or if you generated your own content using various UI image calls or even CI image, you can pass one of those in. Now we'll take those as a single item, as printing item method in UI Print Interaction Controller, or as an array of items. Each item will appear as a separate job when it's in the job list.
So as I said, you want to check the data. You want to make sure that what you're printing, what you have, the URL or the data is printable. And there's two methods, can print URL, can print data. And so if you're going to allow someone to print a particular object, you should check before you maybe let them print that this is printable.
And there might be various reasons as to why you can't print it. One, of course, if it's an unknown format, an image format we've never seen, it might be that it's been corrupt. So if you've downloaded it, you don't know where it comes from. And images, you never know where it comes from.
And if you don't know how they're being generated, it might have some problems before it can print. And if it's a PDF, it might be locked. So it might already have a password lock on it or just be marked, might be marked as not being printable, in which case you can see the PDF, but you can't print it.
So there are a few extra settings. These are done, there's the one I showed you shows page range and that's on your UI Print Interaction Controller. And that, if possible, so if it's PDF, it will show the page range controller, on photos it won't. There's, on the print info, there's four main settings.
The output type, and this is very important, I'll talk about that, tells us what kind of output data you're going to try to print. You can also specify whether or not there's an, landscape or portrait orientation is the preferred orientation. And whether or not you're going to allow duplex printing.
And finally, as I said, you can specify, and I showed you an example, the job name. And this is useful, for example, in the print order, where the user is going to see a whole bunch of printing and they don't want to just see untitled as you see here. They want to see an actual name so they can decide which one if they want to cancel it before it prints.
So there's three kinds of output types. The first one is UIPrintInfoOutputPhoto. And that tells us it's a photo. That lets us tell the printer, print as high quality as possible, and it will choose the right size paper for photo printing. So, for example, four by six. We'll turn off duplex mode.
So even though the printer may say we support duplex printing, you don't want to duplex print a photo. So we won't show that option. And we'll just say, oh, it's single-sided. And because it's a single item per page, we're not going to show a page range control, where all they can just say is choose drawing from page one to page one. There's no point.
Now, if you've got more general output, like, for example, a web page where you've got mixed color and black and white text, you'll use UiPrintInfoOutputGeneral. And that'll choose a much more sort of normal quality. It'll try to choose a document paper size, like a letter sized or A4. It'll allow the user to choose duplex mode if the printer supports it. And it'll allow the user to choose a page range if you set that option on the UiPrint Interaction Controller.
And in the final case, if you happen to know that this is only going to be black and white text, for example, notes here, where, you know, there's no images and the user can't change the font color or anything like that, use OutputGrayScaleMode. Then we'll choose a faster printing speed. We won't use the color printing cartridges if the printer knows how to handle that. But just like in the general case, we will present a document size paper if possible, double-sided, and print range.
So as I say, this is sort of like the really the one extra bit of information you want to set the output type. So it lets you use Smart Paper and all these other settings depending on the type of output. And it really helps on the user experience.
So that's simple printing and that really gets you a lot of stuff. If you've got an image you generate or images you download or you're talking to the image picker to get images from the photo library, this is all you need to do. That one function, right, plus a little bit of interface glue code to call that function or decide if you can show the print option in your application. You need to check that the printing is available. You need to check that what you can print, what data you have or what URL you have you can print and you need to tell us the output type.
So now I want to talk a little bit about formatters. This is for text, which is really the sort of complex case when you're going to print. You don't want to, when you're drawing text, break across text bound. You don't want to have the text break across the bottom of the page, the top half of one line is on one page and the bottom half line is the other page. And then the page layout may change, the text layout may change depending on the width of the page. So a wider page you'll have more text on one page or with markup text you may even have images move around a little bit.
You can use these formatters as a standalone object or along with print page renderers which I'll talk about later. In the case for a single object, just set the single text and give it to the print interaction controller. And for the simple case, you can give us the font, the color and the alignments if you want left justified or centered and you want gray output as opposed to black and white. The Markup Text Formatter supports regular HTML, proper HTML with the angle brackets, etc. So it will format everything, embedded images and so on will be laid out correctly. And this is the exact same rendering engine that's used by Safari to print its content.
So here's some code on how to use it. As before, we get the shared printed interaction controller. Now we're going to create a markup text formatter and that just takes some text. This will be HTML text. Maybe you've generated or maybe you've downloaded it or you've already had it pre-calculated. Set it as the single item on the controller and we only take one print formatter in this case, not a list of them.
So the print info, in this case we know it's gray scale, maybe it's just some raw plain text with some fonts and so on that we've generated and you just want to output it as gray scale. And then you'll present the print interaction, the printing sheet and so on and the user will be able to print that text. So it's very simple.
In general, the layout will be content we printed to fill the whole printable area of the page, depending on whatever printer, whatever paper size the printer can support. So you'll normally have the paper rect and it's a 0, 0. There'll be an internal printable rect area, which is inset from that rectangle. And that'll be whatever the printer tells us is its capability. So maybe it requires quarter inch margins all around or whatever.
And the formatter will fill that area. We do give you a couple of controls, some settings on the formatter to adjust its position. And these are more useful later on when you use them with the renderer, but you can use them as well when you're printing a single formatter.
So there are two different ways of adjusting the position. The first is with a content inset and that gives you a chance to adjust the top left and right insets from the printable area, but not the bottom because we'll just keep printing as much as we can. So we might have to go multiple pages, there's no reason to change the bottom. So we'll just print as much as we can. But if you wanted to start in an extra inch in from the edges or an extra inch from the top, you can just set the content insets.
Alternatively, you could specify a fixed maximum width and height and we'll try to fit the text into that width, but if the print area, printable area is narrower than that, we'll just fit to that size rather than the maximum, something with the height and width and so on. If the width is taller than a single page, we'll go through multiple pages.
So when you're printing something like this, let's say you've got a lot of text, you're printing War and Peace or whatever, you're going to go through multiple pages. Each page will be broken at the printable rect as you can see here, so we don't draw past that area.
And we'll automatically break the text between lines. So you don't need to do any calculation. You don't have to figure out, oh, well, this line ends half here, so we're going to have to draw a little bit less. We'll just automatically just bump to the next line, bump that line to the next page.
So actually the total length of the formatter, the total length across multiple pages will probably be longer than what you would get if you just printed it on one very long page because we'll have to keep pushing text down to push it to the next page. And in this case you can actually mix the insets plus the maximum width and height. So we'll take the minimum of those if necessary.
And finally, while I said it's not a WYSIWYG system, we do allow you to print views. The most common views you'll want to print are UI Web Views and UI Text Views, where the user may have been entering information that you've allowed them to have an edit system, like for notes or whatever. You can just ask for the view print formatter for that particular view and return the custom one.
Or for any other kind of view, you can just ask for a view print formatter for that view and we'll return a generic one that we'll call drawRect. Actually, we'll call a method called drawRect for print formatter multiple times, drawing sort of each section of your view. And that, by default, just calls into drawRect. So if you just want to draw your view, you can do that.
So those are the first two, the simple case and now just single-item plain text. But let's say you want to draw a much more complex output, for example, the maps one, where you've got the map itself, maybe a map view you're going to draw, plus all the directional views on the side. So you have to do your own drawing, the strings and images, and so on.
This gives you full drawing control. And this is an object you provide. You're going to subclass UIPrintPageRenderer. And there are two basic things you need to do when you do that. You need to tell us how many pages there are going to be. And for each page, we'll ask you to draw that particular page. Think of it as a data source for printing, so it's an object that you'll write.
You can add spaces for headers and footers. We'll block out areas and that's important when you're using formatters with renderers so we know where to sort of break the text a bit early so you can draw a page number at the bottom. And we support multiple formatters in the renderer so you can have multiple text fields, one after the other, across multiple pages all with the text broken at the right place.
So I said, you're going to subclass UIPrintPageRender. It's actually pretty functional by itself, but there's probably a few things you'll want to do. The main thing is a method called number of pages, which will call when the user selects a printer. It'll tell you how big the printable area is, and you'll be able to calculate, tell us how many pages. Is it going to be 10 pages or 20 or whatever? And then for every page, we'll ask you to draw that particular page.
And you can put whatever you need if it's more than one page, multiple pages, and so on. And then once you've got the print page renderer, you'll set it as one of the options on the UIPrintInteraction control, the shared one, just like you did with an item or a formatter. And we'll use that when you present the interface.
So the render layout is similar to what you saw in the formatter. There's a paper rect and the printable rect. Then you specify a header height and a footer height and this goes all the way across the printable area and will block out the areas for that, giving you a rectangle for the header and for the footer. And then finally what's left over is the content rect.
With that, using those rectangles, we'll call you, we have a number of methods that you can override. So the first one is sort of the main entry point called draw page and index and rect. And the rectangle we pass you there is the printable rect. Now this actually does the printing, so if you don't call super on this, nothing will appear. But if all you want to do is draw your own page completely, you can just do this if you don't expect anything else to be drawn.
We'll call your method drawHeaderAtPageIndexInRect. This is the header rect. And this, by default, does nothing, so you don't need to call super on this case. Then we'll call drawContentInRect with the content rect. Then for every formatter you've added, and I'll talk about that, you can add, as I said, for text formatters, we'll tell you that we're drawing it for that particular page. And so only the ones where the content for that formatter is drawn, we'll call on that. Now that one, again, you have to call the super in order to actually get the content rendered.
But, for example, if you wanted to draw the word confidential across the text, you'd call the super first and then draw a rotated text across it, or if you wanted to draw a border or something like that. And then finally, we have drawFooterInRect, which will draw the footer area. Again, we don't do anything here. It's up to you if you want to add it in. It's not called if you give us a zero-height footer or a zero-height header in the case of the header rect drawing method.
So here's a very simple case. We've got a set of an object that will draw a set of items. Let's say it's like a table view. So you've got a bunch of items that will fill all the way across the page, all of a fixed height. So we'll have a couple of methods here.
One is draw item at index. So we'll draw item zero through n, or how many items you have. And we'll also keep track of the number of items per page, just so we don't have to recalculate it later. And then there are the two methods that we need to override, number of pages and draw content at page at index in rect. Thank you.
So here are the actual override methods. I'm not going to talk about those other ones, which you would have to implement. First, we're going to calculate how many items per page. So we'll assume a fixed height here and, you know, maybe we'll figure out that there are seven items per page that fit exactly. And then we'll calculate how many pages we need to draw that number of items per page rounding up. So if we figure we can fit seven and we've got 100 items, well, we'll need about 13 pages or so.
Then you're going to override draw content for page and index and rect. By default the super doesn't do anything so you don't need to call the super method. One thing we don't do is we don't do any clipping. So if you think you might be drawing outside of the rectangle we give you, you can set the clip beforehand.
And then for each item we're just going to draw each item one after the other starting at the particular index for that page. So you know like for page one we've got eight items per page. We can start item number eight for seven items and so on. And then we also calculate the offset that we want to draw the item at and we offset from the content rect origin which is non-zero, which was probably going to be non-zero because most printers require a bit of a margin.
So now let's say you've got some text that you want to include along with your images. So like for example the maps, maybe you've got some directions that go along with the map and this text might actually span multiple pages. We like to add multiple formatter so if you've got separate chunks of text that you want to add in, we'll add it in for you. When you add in that formatter, you need to tell us what page it starts in.
So, you know, for example, the first one might start on page zero, go for three pages so you'll say, "Okay, the next one can start on page three and we'll start drawing on page three." And you can adjust using those top insets and left and right insets. You can adjust the position of the text on that starting page.
So for example, if you figure that, "Oh, we need some stuff at the beginning, then my text starts drawing," you could just set the top inset and we'll shift the text down for the first page. And one good thing about the formatters is once you've set the start page and you set the inset, we'll actually calculate the number of pages you need to print that formatter. So if you add the formatter starting at page zero and we calculate it's going to take three pages, we'll automatically have number of pages return three.
So you don't need to override that method in this case. We know exactly how many pages it's going to take. If you say it starts at page one, we'll now say, "Well, you need four pages to print." So once you've added the formatter to the print page renderer, you can ask what page you've set it to start in and at that point also we can calculate how many pages it's going to take to print.
And then for each page--for each formatter, you can ask on the valid page range that it is what the rectangle is going to take to print it in. So for example, if you have the top inset, the first page, the rectangle will be shifted down and then it'll tell you at the end how many--what the last page rectangle is.
So here's an example and it's a very, very trivial one. We're going to add a little title at the beginning of some markup text. So we've got an HTML web page we've got, but we want to draw along the top just the new URL or something like that.
So here we've added one init method which will take the markup text and the title that we want to present. And we only need to override draw content for page at index and rect. And the reason for we need to override this one is so that we actually just draw the title.
So here's the init method and the draw method. We'll just initialize the renderer. This is a print page renderer that we're creating here. And we'll save the title for later. We'll create the formatter, just stuffing in the markup text. And most importantly, we'll set the content inset, the top one, to be shifted down by a fixed amount, the title height.
Now we'll say--we know that, let's say, the title height isn't very tall, so we can add it on the first page. So we'll just add the print formatter starting at page zero. Now, this system automatically calculates how many pages that's going to take, and we'll tell--we'll figure out what number of pages should be so that all the content will be For the actual draw method, well, the print formatter is going to take care of all the drawing of the text for you, so you don't need to worry about that at all.
In this case, we just want to draw the title on the first page, so if the page index is zero, draw the title at the top of the content rect, and then the formatter will take care of all the other drawing on all the other pages as necessary.
So it's a very powerful system. I hope it will make a lot of your life easy in terms of calculating stuff. You don't have to worry about page breaks yourself. So now I want to turn it over to Dave who will give you a demo of this and the power of this and adding it to the recipes application.
Hi everyone, my name is Dave DeLoong. I'm also on the UI kit team. I'm going to walk you through adding printing to the recipes application. This is one that we're familiar with. We've seen it before in other demos. This is what it looks like. You'll notice at the bottom we've got a new share button which we're going to use for triggering our printing. Now in the recipes application when you select a recipe, you'll see something like this. The format, the layout of this recipe is very specific to the screen real estate. Of the device we're on.
But when we're printing, as Howard mentioned earlier, we've got a lot more real estate. So we're going to change it to look something like this. We're going to add a nice border around the recipe. We'll draw the image at a much higher quality. We'll have the recipe name, some preparation information, the list of ingredients, and so on.
Now one of the things that we're going to be using to do this is the printer simulator. We don't have to burn down a forest to test printing in our application. We've got a simulator. We can test grayscale, color, and so on. We have all of these applications and settings to use.
All right, so I'm here in our recipes application. And we're in our table view controller for showing the list of recipes. And the very first thing that I want to do before I do anything is decide that I need to show the print button. So here when I want to show it, I'll first check, is printing available? If it is available, then I'll actually create my bar button item, add it to my toolbar, and continue on. Otherwise, I'll simply hide the toolbar and not show the button.
Once I've selected some recipes to print, I'll use the new TableView API to use for multiple selection, grab out all of the recipes and add them to an array. First thing, I'll get the print interaction controller like Andrew mentioned. I'll set some stuff up, which we'll talk about in a second, and then when I'm done, I'll simply present it. Now since I'm going to be doing custom rendering with that border, the image and stuff, I'll need a custom print page renderer.
So we're going to create a recipe print page renderer and simply give it that array of recipes. And we're also going to set up some information about this. We know that it's going to be mixed. It's going to be both text and images. So we'll have general of our output type. And we'll also set a job name so that we can know what this job is in the print panel.
For our renderer, We've got our initializer to save our array of recipes. We're going to have space at the top and the bottom for printing the date and time and some other footer information. And we know that the amount of space that we need to draw that, the recipe name and the image, will take about 150 points.
So the very first thing that we're going to do is override number of pages. We'll clear out any existing print formatters that we have and then set up the ones for the recipes. So let's uncomment this long method. By the way, this code is all available as part of the WWDC sample code so you can be following along as well.
For each one of these recipes, we're going to grab an HTML representation. This HTML is going to be creating an unordered list for listing all of the recipes, ingredients. We'll also be creating a list for the preparation instructions. And that's just simple, simply generating an NSString for us. With that string of markup, we'll create a markup text print formatter.
And we'll also say that this formatter needs to have some space at the top. This space will be used for drawing the recipe image, the recipe title, and the other information. Now, one of the things to be careful about here is that by setting the top offset of this recipe, we might actually be moving the recipe below the bottom of the page.
So what we need to do is say, if the top of our recipe would be beyond the bottom edge of our content area, then we actually want to move our recipe to the next page and then recalculate the top offset. Finally, we'll add our print formatter to the print page renderer, increment our page counter so we know where the next recipe starts, and there we go. So if we run this at this point, we'll build and run. We've got incomplete implementation. Comment out this for now.
If we build and run, we see the recipes app here in the simulator. And we've got our list of recipes. We know that printing is available because the toolbar is showing. I can use this to select multiple recipes. And I want to print. So first thing, I'll need to open the printer simulator. I can do that by going to the file menu and choosing Open Printer Simulator. And it pops up like this.
And I select print and we see we're presented with the modal sheet. We're simply going to save the original to the simulator. This will pop it up as a PDF inside preview. We want one copy. We hit print. At this point we get our simple markup text. We notice that we've got our space above each recipe and it is in that space that we'll be drawing the image and the name. So there we go, looks pretty good.
When we actually get to wanting to draw stuff, let's start with this. We want to draw a header at each page. In this case we'll be drawing today's date, so we'll create a date formatter, set the date format, create a string from today's date and simply draw it using NSString's draw and rect with font methods that we're used to. Similarly, drawing the footer will be to show page one of three as is in this particular case, again, drawing in Rect with a particular font.
If we run this, We will see again that this is quite simple. Since we already set up the header height and footer height in our initializer method, these callbacks will be called. If we had neglected to set our header height and footer height, then we wouldn't have seen anything. But there's today's date. There's page 1 of 3. Next we want to draw the actual print formatter. So this is a bit longer.
I'll uncomment that. Very first thing we are going to do is invoke super. Super is where the actual print formatter will be drawn onto the page. So the markup text will be rendered, all of that will happen in super. So that's going to happen first. And then we are going to draw on top of that. Drawing a print formatter will draw it as opaque. So if we were to call super last, none of our custom drawing would show up because it would just get covered up. So we will do that first.
We will figure out the rectangle that we are going to be using for this print formatter. And then for our border, we are going to be constructing that out of a Bezier path. And for the first page of this formatter, so if our page index is the start page of the formatter, we will draw the top of the line.
We will draw a left border, a right border, and finally if this is the last page, or in other words the start page plus the page count minus one, we will draw the bottom line. Set our color to be black and stroke the border. So running this, we will see that each recipe will have a box around it, again with the 150 points of space above each recipe, which we'll use next for drawing our recipe information.
Okay, so we only want to draw the recipe information if it's on the first page. So here inside our if block, we'll uncomment this. Self recipe for formatter. And we'll also say draw recipe. And I'll go back and uncomment these methods now. For drawing the recipe, we simply want to draw three things.
We want to draw the image, we want to draw the name, and we want to draw the information about the recipe. Drawing the image is again straightforward. We're going to retrieve the UI image object from this recipe. We'll scale it down to fit inside the rectangle that we have and then simply call draw in rect.
For drawing the recipe name, we'll compute the rectangle that we have to draw the string in, create a bold font, and then again simply draw a string inside our rectangle with a left alignment. And then for drawing the information about the recipe, again we'll compute the rectangle that we have to draw inside. Use a slightly less dark color and then again draw and rect.
With this we've completed all of the custom drawing that we need. We can see that we've got our border around each recipe, we've got the space for each recipe, we've got a nice image, nice large title, some information about each recipe, we've got our headers, our footers, and it really is just that simple.
Andrew? So you saw how easy it is to really just do a lot of complex drawing with just a few methods that you need to override, and a whole bunch of rectangle calculations. Now, I just wanted to cover a couple of advanced features that you might use for a more complex application, i.e., pages, for example. And there are two I want to cover.
The first is paper size. As I mentioned, we don't have a page set up, so the user hasn't specified ahead of time what printer they want and what paper they want on that printer. And the user may not even know what printer they're going to print on until they get into the range of the printer. So you should just assume that we're going to give you any kind of paper size we could possibly give you, from small to large.
And you should be very flexible about laying out your content. You should just be able to handle anything you give you. And we'll automatically choose the right paper based on what you've told us is the output type. So for example, photo, we'll choose a photo paper size. And also what the printer reports back to us. And the printer can even tell us, possibly, what kind of paper it has installed.
But if you want to do your own kind of page layout, like pages, you're going to have to implement your own interface. So if you've got your own, effectively, a page setup call-- so for example, in pages here, it gives you the choice of between A4 and letter.
In that case, you'll need to implement a method. A UI print interaction controller has a delegate, which allows you to customize the paper selection. And whenever the user selects a printer or before printing and so on, and we need to know what paper size to use so we can calculate the number of pages, we'll call your method print interaction controller choose paper. And what we'll do is pass you in a list of papers that the printer has told us it supports.
You have to get the size from the user. Here the user has selected maybe letter. And you're required from that list of papers that we give you to choose the one that fits best. And we actually give you a method that does this for you if you don't want to do any complex calculations yourself.
So for example, here's an implementation of that particular method. You have to calculate the paper size. Now here we're going to ask for 8 and 1/2 by 11. And this is the actual physical paper size, not the printable area. So you just say, well, we want 8 and 1/2 by 11 paper.
And we give you a method, a class method on UI print paper that will take the list of papers that we give you, the paper size that you want, and will return the best UI print paper that it thinks will fit your content. Now if you want to get fancier, you can of course look through this list yourself.
Each UI print paper tells you both the paper size and the printable area. So if you want to say, well, no, no, no. I need a larger piece of paper, a larger printable area, so I'm going to choose a larger piece of paper. I'm going to return that instead. Of course, returning that is no guarantee that the printer actually has that paper installed.
The other little advanced feature is, again, something that Pages use. The normal presentation methods are for on the iPhone, present animated with completion handler and then let's bring up a sheet. On the iPad, of course, we have these sort of two standard ways of presenting a popover either from a location and a rectangle in a view or from a bar button item in the most common case. And again, we'll put up sort of a modal popover that will handle the user interaction for you. But if you want to embed our printing interface in one of of your view controller hierarchies, we give you a way of doing that.
You'll override a method called PrintInteractionController ParentViewController, and that's called when you call one of the standard present methods, and we go, oh, no, no, we're not going to put up a popover or a sheet, we're just going to use the view controller you gave us. If it's a nav controller, we'll call a push method on it, just like normal, and if it's just a other kind of view controller, we'll just call it presentModal, so it's just a modal sheet that's going to be presented.
But whatever you do, don't peek at the view controller we push. Don't assume there's any kind of layout or any kind of particular set of items, controls, and so on, because we will probably be adding things or maybe removing things, changing the order and layout and view hierarchies, and if you look inside there, you're going to break.
So whatever you do, don't look inside what we put, but it does give you a way of doing this. So for example, in pages here, they have a share and print panel, a nav controller, their own nav controller with their own back button. They've added their own print item, and when you click it, we just push ours on top. We put in the printer options title, but they provide the back button. And so that's how you can add printing to your application. And it's hopefully not too complex and something you can do for all of yours. And now back to Howard with a summary.
Okay, so today you learned how easy it is to add printing to your application. If you have predefined data types, JPEGs, pictures, PDF, or even HTML, we'll do all the heavy lifting for you. If you need something more complicated, you can combine formatters and renderers to draw just about anything. You might have noticed, it was sort of implied, and I'll make it really explicit, all of the 2D drawing APIs on iOS work with printing.
So you saw that in the recipes app. You should be able to draw just about anything that you'd want. We take care of the easy stuff, text layout and all of that. You can do any level of custom stuff. And I think this will add great value to just about any application. The examples you can find on the website, and then the demo that you saw today is on the WWDC sample website.
Here's some names for further information that you may want. And then sessions, practical drawing for iOS developers. This will cover all those 2D APIs that I talked about. Again, you should be able to draw anything when you're printing. With that, I want to thank you guys for coming to the printing system session for WWDC 2011.