Introducing PDFKit on iOS

Session 241 WWDC 2017

The enhanced PDFKit framework lets your app perform essential operations, such as authoring, modifying, and displaying Portable Document Format (PDF) files. Previously available only on macOS, this powerful framework is now available on iOS 11. Gain insights and best practices on how to use this technology within your own apps.

[ Silence ]

[ Audience Applause ]

Good morning.

[ Audience Applause ]

Perfect.

Thank you very much.

So, welcome to today's session, Introduction to PDFKit on iOS.

So much has changed in the last twelve months, and we are super excited to show you all of those changes and all of the big improvements.

But before we begin, we should introduce ourselves.

My name is Jeremy Bridon.

I'm a Software Engineer here at Apple working on Core Graphics, and I'm Nicki Brower, also a Software Engineer here at Apple.

Perfect.

All right.

So, we've got a lot to talk about today, but the very first thing we're going to be talking about is the PDF file format itself.

We're then going to give a nice little discussion over the framework itself, followed by a discussion on the document, page and annotations model of that framework.

We're finally going to do a deep dive into annotations.

So, annotations are something that we changed a lot this year based on feedback from developers like you.

So, we want to put some extra effort into explaining all of that this year.

Finally, for developers, old and new, we're going to do a discussion on best practices, things that we recommend you do.

All right.

But first, let's talk about the PDF file format spec being 1300 pages, a totally unacceptable number to try and understand how the PDF file format works.

Right? You are app developers.

We want you to create these awesome applications without worrying about the details of the specification.

So, we've created an awesome framework that gives you the power of PDF without you having to read all those details.

We read it, so you don't have to, all right.

So, why use PDFs in the first place?

Well, they're this kind of universal business format.

They're universal and used in medical, financial, government, and business applications, and they're done so because they've got these wonderful features, features like strong encryption or a variable permission model.

So, some really, really nice things directly applicable to you as developers, and then also for business needs.

What is also really cool about PDF is it's this interactive model.

Right? It's not just a static document that you print and you forget about it.

No, it's this document that's fully interactive in the digital world, so I'm going to give you the tools that allow you to interact with those documents on our platform.

So, of course, if you do interact with the document, what's great is what you see in the digital world is what you eventually see printed out in the physical world.

But it's this really complicated spec, and there's so many details to worry about.

How can we kind of handle those issues today?

Nicki?

Yeah. Okay.

So, we're excited about PDFs, and we want to include them in our application.

How do we do that?

Well, we already have a solution for you, and it's a lower level solution, and it's using the CoreGraphics PDF Framework.

This framework is great because it has a one-to-one drawing model with the PDF Spec. It allows you to read and write and modify your documents, and it has a PDF presser and a PDF renderer, so you don't have to create one.

There are some limitations though.

Because it doesn't have any AppKit primitives, that means you don't get any live interaction with the document.

That means no text selection, no highlighting search results, no interaction with annotations, and it doesn't have any accessibility support.

Well, this is where PDFKit comes in.

So, PDFKit is based on the CoreGraphics Framework, but it also includes AppKit, so you get that live interaction with the document.

And now, as of this year, we are now on iOS; so not just AppKit but UIKit as well.

So, it's now even easier to open, modify, and draw your PDFs, but now you do get that interaction.

You can select text; you can interact with the annotations, and I want to point out that this year we have made a great effort to improve our accessibility support.

So, by just using PDFKit, you get voiceover, text extraction, being able to fill out forms, all for free.

All right.

Let's take a look at where PDFKit is today.

On the Mac, you can see it's used by Preview and Safari, which are our main clients, but we're all over the platform as well.

I just mentioned one, iOS, so you can see it's being used by iBooks, Mail, and QuickLooks, and hopefully we'll be able to add a few of your apps to this list as well.

Let's take a look at our framework overview.

So, all of the classes in PDFKit can be put into one of three categories.

These are our View, Document, and Support categories, and each of these have their own list of classes as well.

So, we have our View, which has a PDFView and PDFThumbnailView.

Since we're cross platform, this means on Mac OS, it's going to be an NSView, and on iOS, a UIView.

For Document, we have PDFDocument, Page, and Annotation, which we'll go into further later.

But we also have our Support classes.

These are things like PDFSelection to PDFTextSelection I mentioned, PDFOutline to create a table of contents, PDFAction to jump to a URL, and a few more.

But when it comes down to it, there are really just four core classes that do the majority of what you might want to do with a PDF, and these are PDFView, PDFDocument, Page, and Annotation.

But to simplify it even more, to create a bare bones application to display PDF and interact with it, all you really need to know about is PDFView, and Jeremy's going to show you how to do that.

[ Audience Applause ]

Awesome!

Thank you very much, Nicki.

All right.

So, we really want to emphasize, you get the power of PDF really straightforward with a lot of simple easy use of PDFKit.

What that means is got the demo application.

It's a desktop application.

It's really straightforward.

It's got a View Controller and a View; nothing else.

And in the View Controller source code, and we could review.

There's only three bits of code that we care about.

The first is we instantiate a PDFView with the parent bounds, and we know that it's filling that window in Startup.

We then use Auto Resize to make sure that that view always fills that window, so even if we resize the window, it'll always be fitted.

And finally, the only actual true bit of PDFKit code is we allow Dragging.

What that means is if I launch the application, I can drag and drop a PDF file that I have, in this case from Desktop, and it's a fully interactive view that has all of the same look, feel, and features as Preview, Safari, and all of these other first party applications.

And what I mean by that is there's text selection for free.

There's the viewer, of course.

If we go to the Table of Contents, it's fully interactive.

I could click and jump somewhere.

I could also right-click and maybe change my display layout, change a lot of other options.

I could copy and search text.

What's really cool about this is all of these features are also available to you as a developer where you could programmatically drive these kinds of changes.

All right.

So, I've introduced PDFView kind of casually, but let me give you some more details.

It's this fully customizable but easy to use PDFViewer.

And when I say fully customizable, what I mean is this, how you could change the layout, direction, page padding; you could also change so many more like zoom factors and zoom behavior.

This is also the appropriate place to do view-to-content and content-to-view coordinate transforms.

What that means is if you use your finger on iOS, and you tap that PDFView, we actually can transform that tap and figure out where exactly in the document and in that page you are touching.

Now, I've mentioned there's a, that they're easy to use, so I want to give you kind of a simple straightforward example of setting that document to a PDFView.

Here in SWF, we're initializing a document with a URL, and then we assign that document to the View itself.

We take care of everything else.

No matter what content is in that document, we will show it in PDFView.

So, I mentioned more of these display settings.

I want to show you a couple of the enumerations.

PDFDisplay mode is really cool because you can actually change that setup of the document in your PDFView.

So, the first denom is single page and, of course, you see one page at a time.

There's also single page continuous, and what that means is it's the series of pages laid out on the vertical column.

There's also two up modes, so you could kind of get this iBooks look and feel, and there's, of course, two up continuous; so, a lot of options.

Next up, new to 2017 is going to be Display Direction.

So, if you look at the continuous modes, you don't have to do vertical; you could also do horizontal; and of course, this is available on both platforms today.

There's also, unique to iOS this year, the View Pagification.

What that means is we want to give you the same look and feel as iBooks in your application; so, you could do so by turning on that feature.

So, here, you could put your finger on the device, you could swipe, and it will go ahead and change between pages for you.

And what's cool is on this bottom.

Take a closer look.

We've got PDFThumbnailView, so Feature Rich Scrubber for the document itself.

As you move your finger across that scrubber, it'll activate the appropriate page that your finger is on, and it'll update PDFView with that active page.

But of course, if you drive that page change with PDFView, it'll go ahead and update ThumbnailView as well.

ThumbnailView isn't limited just to the horizontal access.

Of course, you can set it to the vertical axis.

So, we've talked a lot about PDFView, but let's talk about the core data structures themselves, the Document, Page, and Annotations Model.

So, PDFDocument really is a document; no surprise.

And the document is going to be a collection of PDFPages, and PDFPages are pretty cool because they actually own that content from your file.

What you see on screen is owned by PDFPage.

The PDFPage does so much more because it's also an owner of all the annotations that you see on screen.

So, Document has pages, Pages has content and annotations, and to actually create a document, you could either initialize it by itself, and author one from scratch, or you could initialize it with a URL.

But once you have that document, you could modify the collection of pages.

You could add, swap, insert, and remove pages.

PDFDocument is also really powerful because this is where you could decrypt the document and also save it with encryption.

So, we give you that full feature of encryption both on opening and saving.

It also gives you access to check what permissions you have on the document.

So, as you unlock it, it's really important that you check what could you actually do with that document.

This is also where you could get the attributes of a document, so you could figure out who authored it, when it was authored, what tools did they use to author it.

And finally, a really nice feature, if you want to ever search the document, and the document strings, you could do so.

PDFDocument has a series of APIs to do exactly that.

Let me give you a full, an example, something you kind of saw earlier, which is we're going to set a Document on a view, but I wanted to show you the explicit path of, we're going to initialize the document by checking your bundle.

Does it have the PDF file in question?

If it does, then go ahead and try to initialize that PDF document, and on success, just assign it to the View.

We take care of everything else for you.

A couple of more examples, if you want to save the file, it's really straightforward.

Just do write method call with that new file path.

Let's say you want to save width encryption; it's really straightforward too.

Do a writeto with options, and then the options is the dictionary where we map special enumerations in the header file with a password that you care about.

So, here, I'm saving the document with full honor permissions, and you could only open it using my password Apple.

A couple of more examples, if you want to retrieve a page, we use a base 0 index system.

What that means is the first page is an index 0; second page is an index 1, etcetera.

So, retrieving the first page is as simple as doing .page at and give us that index.

The second one is if you want to insert a page, you could do so with a page and an index.

If you want to exchange pages, just give us two indices.

Finally, if you want to remove a page, again, just pass us an index.

We will take care of the full document construction when you mutate it and save it out to disc.

Now, I've mentioned encryption when you save, but let me show you an [inaudible] example when you open the file.

So, here, I'm initializing a PDF document from a given URL, and the very first thing I need to do is I need to check, is this document actually encrypted?

If it is, no problem.

I'm going to try to decrypt it.

I'm going to decrypt it by using the method unlockwithPassword.

And unsuccess.

That's great.

I just need to do one more thing, which is I need to actually check what my role and my permissions are.

What I mean by role is that the PDF document file format has this really cool idea that you could unlock a document with actually two passwords, right.

You've got your owner password, which has full permissions over the document.

You could copy, paste, share it, print it out, or you could have a limited role, like maybe the user role.

And with this limited role, you might have variable permissions.

Maybe you can copy, maybe you can print, but it's not guaranteed, and as app developers, it's critical that when you do unlock a document, check these privileges.

The reason why is PDFKit will enforce these privileges for you.

So, if you open a document, and you can't copy it, right, it's really a bad user experience not to understand why you can't copy it.

So, you could check this and maybe communicate to the user, hey, this document is unlocked, but with the user password.

That means you don't have full permission, and if you do want to give us the older password, we could try to unlock it again, and you'll be able to copy it after all.

So, when you unlock the document, it returns a Boolean, but some of these events on a document and other classes will post notifications and will call optional delegates.

A couple of examples is, again, when you unlock a document, we'll post the document did unlock notification.

This could have been driven by the user because we have a default password unlock a view.

But also, if you start saving the document, maybe you want to catch that in some other observers in code.

You could do so with the notifications.

You could also implement any of these delegates, and they typically match one-to-one.

They have very similar function calls.

All right.

So, we talked a lot about pages, or excuse me, a lot about document; let's talk about Pages.

So, pages can be retrieved from a document, or they can be initialized empty; maybe you're offering a new document, or they could even be initialized with an image.

That's a really helpful feature if you're trying to translate a collection of images into a PDF document.

I'll show you an example later.

But also, what's powerful about PDFPages is that it's that Annotations container.

So, if you interact with Annotations, or if you want to mutate Annotations, PDFPage is where to do that.

Finally, PDFPage has a lot of custom graphics, so if you want to change its size, its orientation, or if you want to do custom graphics in that content, you could do so with the PDFPage class.

It also has a series of a really powerful text selection at API.

I'll give you an example.

So, first and foremost, let's create a PDFPage with an image.

So, I'm going to use UIImage to go ahead and initialize an image asset, and now I'm going to instantiate PDFPage with an image.

What's really cool about this is if the image has special content, like it's a P3 wide gamic color space, we will author that page, and eventually the document, with that P3 color space.

What's also nice is we default the PDFPage to US letter, but you could set that size to whatever you want, but if you initialize it with an image, we'll always try to maintain that image's aspect ratio in the size in the document.

If you want to extract string content from a PDFPage, you could do so by just calling the string method.

But if you want to get rich text out of that document, you could do so with the Attributed string, and this has been really, really powerful with our improvements in accessibility.

There's also, like I mentioned before, these really powerful text selection APIs.

I'll give you two examples.

The first one is let's say you want to select text at a specific character range.

You could do so by a method on the PDFPage called Selection for Range.

So, here, we'll select character 10 and for a span of 5 characters.

We'll extract that, and you could do a lot of geometry conversions with the PDF selection class to figure out where visually that is on Page.

But there's also the means of pulling out text based on geometry.

So, if you give us a rectangle rather than just a span of text, we could pull out that PDF selection for you, and there's a lot you could do with that.

But I want to go back to Attributed string.

I wrote a really simple desktop application where on the left, just like in our previous demo, we have a drag and drop PDFView, and on the right, we've got a Rich Text field.

And here, I'm going to drag and drop a little user manual, and then on the left, I'm pulling out and publishing the Attributed string.

And this is what's really cool is we maintain the full content as best as possible for text positioning, text order, fonts, color, bolds, and all of these other attributes.

And it's critically important because this gets exposed to the accessibility system, which is available on both Mac OS and iOS.

That means just by adopting PDFView, you get all of these accessibility improvements for free on both platforms.

All right.

So, there's another cool feature new to 2017, which is PDFPages Method Thumbnail of size for a display box.

Oftentimes, you might be in a situation where you have a PDF document and all you want to do, nothing more complicated, all you want to do is just show a preview of a page.

With this new method, we do all of that work for you and, again, we'll respect the P3 color space.

So, if your document has any sort of complicated asset that has a wide gamut color, we'll make sure that the UI image or the NS image like gets authored from this is P3 compliance.

What's also nice, this will track size to fit the document in the given bounds while maintaining aspect ratio.

But that last argument display box, what is that?

So, to explain that enumeration, I have to kind of explain how PDF content works in terms of graphics.

So, we're going to start with the Cartesian Coordinate Space.

This is the content coordinate space that all PDFPages draw into.

So, you've got origin at 00.

X positive grows to the right.

Y positive grows up, and I'm going to put a little string, I'm going to say "Hello, San Jose."

And with this string, I want to put it on a page, so it's got a location.

I'm going to put that on the page, and the page itself, of course, has a size.

So, we've got all of these geometric properties, but something really bothers me, and what bothers me is it's not centered.

Right? So, we could redraw this entire content, but that's really unacceptable and expensive.

No one wants to re-author a PDF document by re-issuing and redrawing all of these draw commands.

So, instead, we're going to take advantage of the power of PDF and take advantage of the enumeration I was talking about.

We're going to take this PDFPage, and we're going to set the frame for that specific display box to be slightly offset.

It's offset because we want to keep San Jose dead center.

And I don't really like Landscapes; I want to turn it into Portrait, and I could do so again without reissuing any of my draw commands, I could just set that frame property.

Now, why is it that I could change to use PDFPage Geometric Properties without changing the content?

What's the real benefit other than we're just saving some time when saving the file?

The real benefit is PDF comes from the physical printing world.

That means that there are cases where you actually want to have multiple PDFPages for the same content.

You should think about PDFPages at least when it comes to this content.

When you're drawing it out, you should see them as View portals into your content.

There's one box of content, but then there could be several view portals or pages into that content.

And the reason why we do that is, take a look at the corners, right.

In the physical printing world, when you're trying to print this professionally, oftentimes, they're going to print this through several passes, and each pass is going to lay on a separate layer of ink.

And to do that perfectly, there needs to be some sort of alignment.

So, here, we've authored the page with optical alignment characters at each corner so the physical process can put that ink perfectly.

And that's why we have these enumerations.

In this case, I'm showing the digital version, which is the Prop Box.

That's what PDFKit defaults to, and then we're also showing the physical version, which is what we'll physically print, via the media box.

There's three more that you could play with, and then also don't forget what's really powerful about this is any modifications you make to one box, because you're modifying that content, is going to appear in both Pages.

All right.

So, let's now talk about Custom PDFPage Drawing.

We've talked about changing everything else, but what about actually drawing content?

There are three simple steps.

The first is given a PDF document; we're going to set a PDFDocument delegate.

The reason why we set that delegate is Step 2, that delegate should implement classForPage.

We're going to return a subclass that we've implemented that's going to define how do we want to draw this PDFPage.

So, in this case, I'm going to say please draw with my custom subclass called WatermarkPage.

Third step, third and final, is we need to actually override that PDFPage subclass' draw method.

So, we're going to do draw with box to context.

Let me show you a full example and end.

Perfect.

All right.

So, here I've got iOS simulator running a sample application that implements my three rules, and in this application, I just get to scroll around just like a normal PDFView, but I want something different in this application.

I want to say that we're handling really sensitive documents, and every time one of my users opens these documents, we have to track them.

We have to know that user X has seen this content, and if user X leaks this content, right, we'll know exactly who did it.

So, our goal is to draw some custom identifier atop every single page, and we could do so with those three steps.

So, the very first step, if we go to our ViewController, there's a little bit of overhead to set up the PDFView, but here's the core step.

We are setting the delegate to ourselves.

We are setting the delegate to the ViewController.

The reason why we do that, Step 2, we're going to declare, like you saw previously in the sides, we're going to declare that watermarkPage as this PDFPage subclass, and we want to return that as our customDrawer.

The final step, Step 3, is we're now overriding the Draw Function, draw with Box 2 context, and we're putting all of our custom graphics inside.

So, before coming to stage today, I commented out everything just to see what it would look like before.

And well, let me show you what it looks like after.

So, in the code, I first call Draw super, and then I draw my custom graphics, and there's a reason why I do that.

So, when I execute the code with that modification, all pages have this graphical content added.

And what's really nice about this is if I attempt to save the document or print it, it's guaranteed to have that custom graphics that I've just put in.

But I wanted to mention again, order is critical because we respect the painter's algorithm.

What that means is whatever you draw first, if you draw the original content, followed by drawing your custom content, your custom content will appear above.

If you flip that, if you draw your custom content and then you draw super, right, the original content will be above.

So, just keep that in mind when you're vieing custom drawing.

All right.

So, we have talked a lot about PDFView, PDFDocument, PDFPage; I think it's time we talk about Annotations.

Nicki.

All right, perfect.

[ Audience Applause ]

All right.

Let's talk about my favorite thing, Annotations.

First off, we keep saying this word Annotations a lot.

What are these?

Well, these are interactive graphical elements that we can add to a PDF document.

These are things as simple as a line or a square, but there's a handful of other annotations as well, like highlight, underline, circles, links, and even widgets, which are interactive form elements.

We mentioned earlier that Annotations are owned by PDFPage.

So, whenever you create an Annotation, you add or remove it to and from a PDFPage.

And then, if your page is in a PDFView, whenever you update properties on that Annotation, we will update it for you in real time in the View.

What's fairly new to PDFKit is our universal Annotation support.

Okay. What is this?

Well, all those annotations that I just showed you, those are the ones that PDFKit supports, and when I say that, I mean, we have our own native rendering for those annotation types.

But that doesn't mean you're limited to only creating those types of annotations.

With our universal support, you can create whatever you want because, well, the PDF spec defines an annotation as just a dictionary of keys and values.

So, we've gone ahead and mimicked that within PDFKit.

For example, the PDF spec can have extensions to it that defines all sorts of annotations we've never seen before.

Take the GPS Annotation.

This can have certain properties, coordinates, latitude, longitude, and with their universal support, we can go ahead and set those values on our dictionary, and then what you see is what you get.

Whatever you set, we'll go ahead and author that into the file whenever you write it out.

Even though the PDFKit might be undefined, you'll still be able to save it as part of the PDFDocument.

What's new this year is we've added an extension to this class, which is PDFAnnotation utilities.

This has a handful of category methods that make it just that much easier to be able to set and get the properties for annotations that we do support or just properties that are common amongst the annotations in general, like a color or a border.

We'll take a look at our annotations again, but let's just focus in on one, a line annotation.

We all know that annotations are just a bunch of properties, so whenever I change the properties, here all I've done are change my line ending styles and my border.

We can make a simple line annotation look different six ways.

So, let's take a look at how we actually set those properties.

So, a line, it needs a start and end point, so we'll set that on our dictionary using SetValue forAnnotationKey with our linePointsKey.

We'll set the line ending styles and want a closed arrow on one end and an open arrow on the other, and then we'll change the color.

We'll change it to red.

But remember those category methods I just talked about, let's go ahead and use those to do the same exact thing.

Here we go.

We're still setting the start and end point, the line ending styles, and the color.

And when we compare the two, sure, the category methods might be a few extra lines of code, but it's very clear as to what we're doing.

When we're setting values on the dictionary, we're mimicking the spec, and the spec can be a little confusing.

To set an array of numbers or array of strings for our line points and line ending styles, that's just, again, confusing.

With the category methods, it's explicit that you're setting the start point, the end point, the start and line style, and the color.

Just makes life a little bit easier.

Okay. Let's create an Annotation.

Our [inaudible] method takes three properties, bounds, type, and the dictionary properties.

Only two of these are required; abounds, we need to know where it's supposed to be in the page, and then a type.

For here, we're going to create a square annotation.

The properties parameter, we're going to leave no for now, but I'll get to that in a second.

Okay. Let's set some properties.

We'll set the color on it.

We already know how to do that.

But we'll set the border as well, and I want to touch on this PDFBorder object for just a second.

The annotation dictionary is just a dictionary of keys and values, but some of its values can also be dictionaries, and so you don't have to go and pull up the spec and figure out how you're supposed to construct this border dictionary value.

We've already created a support class for you, and that's PDFBorder.

We'll set the line width, set to our annotation border property, and then when we add it to the page, it looks a little something like this.

Just have a simple square, bottom left corner of our page, and it's red.

Let's take a look at that properties parameter though.

So, what we can do is we can kind of prebake a dictionary to properties.

We're going to create a line annotation again, and we already know it takes line points, line ending styles, color, and now border.

So, then, when we [inaudible] our annotation, we will, instead of using nil for our properties parameter use our line attributes dictionary.

So, when we add it to the page, it looks a little something like this.

It has a line starting at the origin, going up, and to the right, has a closed arrow on one end, and the color is red.

I want to touch on this one property that annotations has which is really powerful.

It's Action property.

Just like PDFBorder was a wrapper for a dictionary, so is PDFAction, so you can do things such as jumping to a URL, and here we create URL, use that to create a PDFAction URL object, and then set it on our Annotation.

For here, it's going to be a Link Annotation.

But we can also do things such as jumping to a specific point in a document.

We can do that by creating a PDFDestination object with the page and a point, using that to create our PDFAction go to action, and then, again, set our Action property.

Okay. Let's talk about widgets.

Widgets are, again, our interactive form elements, but these are a little confusing how to create, so I want to break it down for you guys.

We have very different flavors of widgets.

These are text widgets, buttons, and choice widgets.

And so PDFKit knows what type of widget and interactive element to add to your PDFDocument.

You need to let us know.

And this is driven by our widget field type property to be either a text button or choice.

Even further, some of our subtypes have more flavors.

Like a button, we can have a radio button, a check box, or a push button, and this is driven by our widget control type property.

And same with width or choice widgets.

We have a list box, which is like a TableView, or a combo box, which is like a dropdown, and this is driven by our isListChoice property.

By default, it's going to be a list box, but I want to let you know that there are two different types.

Let's go ahead and create a widget.

We're going to create a text widget.

So, our subtype is going to be widget, but then when we start our properties, we know that we have to set the widget field type, and we're going to set it to be text.

Then, we'll set a few other properties.

We have our background color, font, and a string value, and then when we add it to a page, it'll look a little something like this.

We have our text widget about the middle of the page, the blue background, and our string, WWDC2017.

And if this was in a View, whenever I tapped on it or clicked on it, it would be fully interactive.

Okay. Let's take a look at a real world example using widgets.

So, I've already gone ahead and taken a PDF, which is going to be a survey, and I've added some interactive form elements to it.

Let's take a look at what this looks like.

So, here it is.

I have text fields, which have the blue backgrounds, and then buttons, which have my gray backgrounds, and since we're all widget wizards, we all know that our text fields will have widget field type text; buttons will have widget field type button, and then even further, our buttons will have the proper widget control type set.

So, we have our radio buttons, our check boxes, and then a push button down here, which is supposed to reset my entire form.

There are some things though that aren't really working right with this PDF.

For example, when I go to enter my date, which is June 9, you can see, it doesn't have the spacing as I would expect.

And also, I can enter more characters than what the PDF says that I should be able to enter.

That's not right.

Also, when I select my radio buttons, I can select both at the same time.

That's not really how radio buttons are supposed to behave.

And finally, my button at the very bottom, when I click it, it does nothing.

Well, let's go ahead and change all of that.

Let's go to our date field.

So, remember the things that were wrong.

It was the spacing and the number of characters that we can enter.

Well, we'll start with the easy one, and that's the number of characters.

This is driven by our maximum length property.

So, we'll set it to 5, 2 for the month, 1 for the slash and 1 for the day.

So, now, the spacing.

That seems a little hard to do.

But actually, with PDFKit, it really isn't.

All it is is a property that we have to set, which is our hasComb property.

So, this property, what it does is if it's set, it will divide our text widget into an equal number of partitions or combs to the number that is set on our maximum length field.

So, this property only works in conjunction with maximum length.

All right.

That's one's done.

On to our radio buttons.

The issue here is that they weren't behaving like radio buttons.

So, all our widgets, they already have their own identifier, and this is driven by our field name property.

So, if you want to group widgets together, they need to have the same field name.

Let's do that.

We'll have our yes button be Question 1.

And then, our no button also be Question 1.

I'm going to point out that it doesn't matter what these strings are; all that matters is that the two are the same.

But if I were to run this code and select a radio button, all of them would turn on or all of them would turn off.

That's because we don't know, we don't have a way to differentiate the yes button from the no button.

They're each going to have their own unique kind of sub identifier.

We can do that by setting the button widget state string property.

Let's do that now.

We'll set our yes button to yes and our no button to no.

But again, it doesn't matter what these strings are.

I can have this one be Nicki and this one be Brower.

All that matters is that they're different.

So, now, we've grouped them together with our field name property, but we've also set them apart with our button widget state string.

Last but not least, we have our reset button.

But again, we want to reset our entire form back to its original state.

And just like PDFAction URL and PDFAction go to, we have PDFActionResetForm.

Go ahead and create that now.

Well, how this action works is it takes an array of strings, strings that represent field names like the ones we just set on the radio button, and the default behavior is when this action is performed, it will reset all of the widgets that are associated with my array of string field names.

But I don't want to have to go through and figure out the field names of all my widgets.

That just is too much work for me.

So, I'm going to kind of change the behavior of this action, and I'm going to do that by setting our fields included are clear property to false.

So, with just one line of code, I've kind of done two different things.

I've now changed the behavior to where when I press when I perform this action, all of the widgets not included in my list are going to be cleared, and also, I haven't set any on my list.

So, now, when I perform this, every widget in my document will be cleared, which is exactly what I wanted.

And then, of course, we have to set the action property on our button, and that's it, just three lines of code.

So, let's rerun this and fill out our survey from the very beginning with everything working properly.

Here we go; we have our name, which is Nicki Brower; our date, which is June 9.

We have the spacing that we wanted, and I don't know if you can hear, but I'm pressing a button and I can't enter more characters than what is desired, which is what we wanted.

Have you been to a music festival before?

No. Wait, yes, I have.

Which of the following music festivals have you attended?

I've been to iFestival, Swiftopia, but not Applepalooza.

Give one recommendation to improve a music festival.

All music festivals should be free.

And I want to point out that our text widgets by default are single line, but if you want them to be multiline and have this word wrapping that I have here, all it is, is a simple category method that you have to set.

And so now, with this PDF, I could save it, and it'll have all the values that I want, and I could send it to people, and they can see my values.

Or if I want, I can still go ahead and clear my entire PDF with my Obliviate button at the very bottom.

So, I'll press it, and we're back to the very beginning, which is the behavior that we wanted.

So, I showed you with PDFKit, it really isn't that hard to take a simple PDF, add some widgets, and now you have a fully interactive one that you can share with whoever you want.

And I don't know about you guys, but I think that's pretty cool.

So, we're going to go ahead, oh!

[ Audience Applause ]

So, we're going to go back to Jeremy, and he's going to talk about a few best practices.

Perfect.

Thank you very much, Nicki.

So, this year, Annotations have greatly improved, and we've also fixed a lot of issues in PDFKit and greatly improved other functionality too.

And with our experience, we really want to give you a couple of recommendations that you could take away and improve on your code today.

So, a couple of recommendations straight away.

Use annotations for any sort of interactive elements or complex graphics that you want to do at run time.

We've greatly put effort into optimizing these graphics, so it's a great option to take advantage of.

And of course, if you are going to be mutating PDFAnnotations, take advantage of that PDFAnnotationUtilities category class extension.

The reason why, as Nicki said, is that these are statically typed functions.

You don't have to worry that a line point is an array of points.

Actually, a line point is just a CG point, a pair of CG points.

It's really straightforward.

Finally, if you want to do a custom PDFPage drawing, it is required that your code be thread-safe.

The reason why is, again, we've put so much effort into optimizing PDFKit to draw as fast as possible, we want to take advantage of all the cores that our different platforms have.

And since many of our platforms are multi-core, it's absolutely possible that two threads might be drawing your PDFPage maybe in the main view and PDFPage as a thumbnail at the same time.

So, make sure that your code is thread-safe.

And along these lines, if you are doing custom PDFPage drawing, make sure that you call super at the appropriate time.

If you want to put your graphics on top of the existing content, call super, and then execute your custom drawing code.

Or if you want to put your custom graphics behind that content, do your custom code first, and then execute super last.

Now, what we don't recommend, moving forward, is because PDFView derives from UIView and NSView on each platform respectively, those view classes give you the ability to call a method, call set needs display or some mutation of that method.

And that method is really expensive because refreshing all of the content at once is just expensive.

So, try to avoid it.

Again, if you want to do custom graphics, take a look at Annotations.

Finally, don't mutate PDFPage properties at the same time from different threads.

A good example is don't rotate the PDFPage while you're drawing a thumbnail at the same time.

There might be some undefined visual effects that might happen during those cases.

And finally, this is specific to Mac OS only, but try to stay away from the deprecated drawing methods.

The reason why goes all the way back to multi-threaded drawing.

So, we build these really complicated contexts so you could draw your beautiful graphics into, and we want to give you these contexts explicitly.

That's what the new signatures are for.

The old signatures had an implicit context, and we want you to avoid that as much as possible.

So, these are bits and pieces that we recommend.

But looking at PDFKit again, we want to emphasize it's really easy for app developers to get started with PDFHandling today.

They could use PDFView just to look at a document, or you could totally author a brand new one, or you could take an existing document, add a bunch of new content inside.

It's also really easy to read, modify, and write these files, and you could write it with the latest encryption supported by the PDF specification.

Of course, when you do write these files with that latest PDF specification, you could author these files with these really awesome form field widgets.

So, that means fully interactive forms that you could fill out for your business needs or whatever other needs you have.

Finally, just by adopting PDFView, don't forget, you get rich accessibility improvements that we've worked really hard on this year for both platforms.

So, your users will really take advantage of that if they have any accessibility needs, and you don't have to worry about any sort of custom programming.

Nicki and I want to thank you very, very much for coming to today's session.

If you have any questions at all, [applause] thank you.

Please visit our website, or come down to the podium.

We'll gladly answer any questions.

Have a fantastic rest of WWDC.

Thank you again.

Thank you.

Apple, Inc. AAPL
1 Infinite Loop Cupertino CA 95014 US