Rendering a Long Document on iPad
Asked Answered
N

4

7

I'm implementing a document viewer with highlighting/annotation capabilities for a custom document format on iPad. The documents are kind of long (100 to 200 pages, if printed on paper) and I've had a hard time finding the right approach. Here are the requirments:

1) Basic rich-text styling: control of left/right margins. Control of font name, size, foreground/background color, and line spacing. Bold, italics, underline, etc.

2) Selection and highlighting of arbitrary text regions (not limited to paragraph boundaries, like in Safari/UIWebView).

3) Customization of the Cut/Copy/Paste popup (UIMenuController) This is one of the essential requirements of the app.

My first implementation was based on UIWebView. I just rendered the document as HTML with CSS for text styling. But I couldn't get the kind of text selection behavior I wanted (across paragraph boundaries) and the UIMenuController can't be customized from within UIWebView.

So I started working on a javascript approach, faking the device text-selection behavior using JQuery to trap touch events and dynamically modifying the DOM to change the background color of selected regions of text. I built a fake UIMenuController control as a hidden DIV, positioning it and unhiding it whenever there was an active selection region.

Not too shabby.

The main problem is that it's SLOOOOOOOW. Scrolling through the document is nice and quick, but dynamically changing the DOM is not very snappy. Plus, I couldn't figure out how to recreate the magnifier loupe, so my fake text-selection GUI doesn't look quite the same as the native implementation. Also, I haven't yet implemented the communication bridge between the javascript layer and the objective-c layer (where the rest of the app lives), but it was shaping up to be a huge hassle.

So I've been looking at CoreText, but there are precious few examples on the web. I spent a little time with this simple little demo:

http://github.com/jonasschnelli/I7CoreTextExample/

It shows how to use CoreText to draw an NSAttributedText string into a UIView. But it has its own problems: It doesn't implement text-selection behavior, and it doesn't present a UIMenuController, so I don't have any idea how to make that happen. And, more importantly, it tries to draw the entire document all at once, with significant performance degradations for long documents. My documents can have thousands of paragraphs, and less than 1% of the document is ever on screen at a time.

On the plus side, these documents already contain precise formatting information. I know the exact page-position of every line of text, so I don't need a layout engine.

Does anyone know how to implement this sort of view using CoreText? I understand that a full-fledged implementation is overkill for a question like this, but I'm looking for a good CoreText example with a few basic requirements:

1) Precise layout & formatting control (using the formatting metrics and text styles I've already calculated).

2) Arbitrary selection of text.

3) Customization of the UIMenuController.

4) Efficient recycling of resources for off-screen objects.

I'd be happy to implement my own recycling when text elements scroll off-screen, but wouldn't that require re-implementing UIScrollView?

I'm brand-new to iPhone development, and still getting used to Objective-C, but I've been working in other languages (Java, C#, flex/actionscript, etc) for more than ten years, so I feel confident in my ability to get the work done, if only I had a better feel for the iPhone SDK and the common coding patterns for stuff like this. Is it just me, or does the SDK documentation really suck?

Anyhow, thanks for your help!

Nerland answered 13/6, 2010 at 18:35 Comment(6)
I find the SDK documentation very nice. You just seem to be working in an area where the SDK is a bit weak at the moment.Nickynico
The SDK documentation is pretty good for building apps by connecting together a bunch of pre-existing components. But documentation of the techniques required for developing custom components seems severely lacking to me.Nerland
@Nerland Hello, i am also working on an app like this. I need to parse an epub file. Do you have any idea regarding this? How you had done paging? I have multiple Xhtml pages i am unable to figure out that how do i do paging? any help from you will be appreciated. ThanksOrwin
@Rahul - To do pagination, I've actually made pixel-precise measurements of all my fonts, with all their different formatting options. Using those metrics, first I calculate exactly where all the line-wraps will occur (with some logic to prevent line-wrapping in the middle of a word or between a word character and certain punctuation characters). Then I figure out how many lines will fit on each page. Some lines are taller than others, based on the font size and paragraph spacing, so I consider all those factors. There's nothing really fancy about it. But the prep work is certainly tedious.Nerland
@Nerland Hello,Thanks for the comment. Can you provide code to calculate all those things you had mentioned. Or tell me a way to parse epub files. I know how to extract epub file's contents. Now i'm stuck at parsing the xhtml pages and also how do i show all the chapters one by one?Orwin
I can't really provide code for you, since my stuff is so different. First of all, I don't work with HTML. I'm working with a proprietary file format. Secondly, there's not as much magic as you might think... I know that each page needs to be exactly 792 pixels tall. Each of my lines is 12 pixels tall (sometimes 24 or 36, depending on paragraph style). After I calculate line-wraps, I just keep drawing lines to the page until the page is full. Then I start a new page. Line wrapping is basically the same, though it's a little bit of extra work to avoid breaking a line in the middle of a word.Nerland
T
2

Does your document have any semantic components other than each paragraph? If you already have some concept of sections or pages, I would recommend you render each one of those as an independent tablecell. It's pretty simple to create a tablecell that makes you forget you're actually looking at a UITableView. All you would need to do is override drawRect: and setSelected: and setHighlighted: and tah dah! No More cell dividers unless you want them. Furthermore you could do some nifty things by using a tableview as your base. If you defined sections in the UITableView then you could have a nifty header that scrolls along as you're paging through your document. Another thing you could do is add a "jump to section" bar / a bookmarks menu, and that way you don't have to provide selection across the boundaries of sections.

Massive copy paste blocks would be pretty painful on the system as well. Further, if you went through the trouble to provide this content you might not want to make it too easy for someone to copy it all at once... (Can't follow this line of thought more without more specifics on your project).

If you really do want to provide the copy paste options you could add buttons to each logical page or section that immediately selects and copies the whole section for the user's convenience. (Maybe with citation associated?)

I recommend you lookup the UITableViewCell UITableViewDelegate and UITableViewDataSource in the SDK docs as those pages will significantly help if you choose to use this suggestion.

Terence answered 14/6, 2010 at 7:40 Comment(3)
I forgot to mention that using tableviews and fully diving in to the idea of delegates and datasources, you could make a pretty awesome outline view that would animate the appearance of sections in the document in a pretty spectacular way.Terence
One thing that's very important for you to familiarize yourself with if you go this route is the tableviewcell reuse model which is primarily accessed through dequeueReusableCellWithIdentifier: and the corresponding init method. Given that your document is so large you probably don't want to prerender everything. UITableViews help solve that problem by caching a few cell views and simply swapping out the content of a cell that has been offscreen for a while with the content for a cell about to scroll on screen.Terence
If you use NSTextField subclasses to host the text you'll be able to get the selection behavior you're looking for.Terence
N
1

Just two random observations:

  • Can you afford to create a paging interface? (As opposed to “endless scrolling”.) It looks like a paging interface would be a lot easier on system resources.

  • The UIActionBar is actually the UIMenuController class. The interface is a bit weird, as the menu is a singleton (wtf?), but I’m sure you’ll have no trouble figuring it out.

Hope that helps.

Nickynico answered 13/6, 2010 at 19:4 Comment(2)
For various reasons, I don't think a paging interface will work well for my app. But conceptually, a paging interface is almost identical to what I'm looking for: render only what's currently onscreen, recycle elements as they flow offscreen, and provide a visual indicator (e.g., a scrollbar) indicating the user's current location in the context of the document. If I model my document as a linked-list of paragraphs (with known dimensions), it should be trivial to do piecemeal rendering. But how do I hook into the platform-standard scrolling functionality? That's the question.Nerland
Thanks for the correction about the UIMenuController name. I've updated my original question accordingly.Nerland
N
1

Here's a potential solution, but I don't know if it's crazy. Since I'm still so new to iPhone development, this might be a big no-no.

Anyhow, I had the idea to render each paragraph of the document (whose dimensions I've already precisely calculated) as a cell in a UITableView. Since UITableView already has mechanisms for cell recycling, I wouldn't have to implement that from scratch, and the document could be arbitrarily long without causing resource consumption problems.

Of course, I'd want to get rid of the line separators between cells, since I want the UI to look like a document instead of a table.

Or maybe I could render each page of the document (like a typical PDF, this is a paged-document format) as a table cell, and override the cell-separator graphic to look like a page boundary...

But would it be possible to get rid of the default touch behavior within the table, and instead implement text-selection on the table cell contents? Would it be completely impossible to implement text selection that crosses paragraph boundaries (between multiple table cells)?

Nerland answered 13/6, 2010 at 22:33 Comment(0)
E
0

The UIWebView is a good choise, but we need another application to pre render the pages percisely using each font and each style sheet and store the rendring information into a database table:

chapter_id int primary key,
startlocation int,
end location int,
fontsize int (or stylesheetname string)

Using JavaScript we can calculate how many words fit in a div with out scrolling.

UIWebView is good as it provide rich content and it has selection and highlighting behavior.

Hope this helps.

Edmondo answered 24/8, 2010 at 19:56 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.