How does the iBooks App format the text on separate pages?
Asked Answered
S

2

11

Looking at the iBooks App, I was wondering how it accomplishes to format text (probably a very simple txt file) so that it is NOT SCROLLABLE but divided on separate pages.

I'd like to achieve the same, but only with editable text.

Where would I need to start? UITextView doesn't work as it scrolls. Even if I set the pagingEnabled property to YES, it won't do the job.

It seems as if I need to LIMIT the amount of text I can put on a certain page. Is there a function to LIMIT the input of a UITextView? I would need to limit the number of LINES (not characters) as they would be shown on a full iPhone screen (I guess you can fit 20 or so lines, depending on the font size).

Any help would be appreciated! As I'm a beginner, I especially appreciate samples in which similar methods are put to use.

Thanks!

Salzburg answered 8/3, 2011 at 17:39 Comment(0)
A
2

What you need is an own layout algorithm that takes the text, measuring its size and cuts it until it fits into a single page text view. Then start with the rest of the text, same thing for a next text view and so on... After that (or inside the algorithm) you arrange all the resulting text views on scroll view (or in an array, you later flip through with paging animations - if you like it cheesy). I did a similar thing, but with UILabels, it should also work with textviews. What you need is: NSString's - (CGSize)sizeWithFont:(UIFont *)font constrainedToSize:(CGSize)size and rangeOfString:@" " options:NSBackwardsSearch (looking for word spaces) and substringFromIndex: resp. substringToIndex:

If you need more information, just post a comment.

EDIT:

Hi following code is not tested but contains most of what you need (hopefully), but may hold some bugs, especially when it comes to the recursion... I corrected the BackWardsSearch idea, because it could take a looong time to cut a long text. What I totally ignored - and that could be really tricky - is re-rendering while editing. But anyway, here's the code. It's a view controller assumed to old 4 members (header file not posted):

  UIView *editableBook;
  NSMutableArray *content;
  int currentPage;
  UIFont *font;

And this is the Controller itself:

//
//  EditableBookController.m
//
//  Created by Kai on 09.03.11.
//

#import "EditableBookController.h"


@implementation EditableBookController

-(id)initWithText:(NSString *)text
{
  if (self=[super init]) 
  {
    font = [UIFont fontWithName:@"SomeFont" size:12];
    content = [[NSMutableArray alloc]init];
    [self cutInPages:text];
    currentPage = 0;  
  }
  return self;
}

- (void)loadView 
{
  self.view = [[UIView alloc] initWithFrame:CGRectMake(.0, .0, 768., 1024.)];//assuming portrait only ...
  editableBook = [[UIView alloc]initWithFrame:self.view.bounds];//could be a scroll view in alternate approach
  UISwipeGestureRecognizer *flipper = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(nextOrPrevious:)];
  [editableBook addGestureRecognizer:flipper];
  [flipper release];
  [self.view addSubview:editableBook];
  UITextView *textView = [[UITextView alloc]initWithText:[content objectAtIndex:currentPage]];
  textView.frame = editableBook.bounds;
  textView.font = font;
  textView.tag = 23;
  [editableBook addSubview:textView];
  [textView release];
}

-(void)nextOrPrevious:(id)sender
{
  UISwipeGestureRecognizer *flipper = (UISwipeGestureRecognizer*)sender;
  if(flipper.direction == UISwipeGestureRecognizerDirectionLeft)
  {
    [self next];
  }
  else if(flipper.direction == UISwipeGestureRecognizerDirectionRight)
  {
    [self previous];
  }
}

-(void)next
{
  if(currentPage == content.count - 1)
  {
    return;
  }
  currentPage++;
  UIView *fromView = [editableBook viewWithTag:23];
  UIView *toView =  [[UITextView alloc]initWithText:[content objectAtIndex:currentPage]];
  toView.frame = editableBook.bounds;
  toView.tag = 23;
  [UIView transitionWithView:editableBook duration:0.2 options:UIViewAnimationTransitionFlipFromRight
   animations:^
   {
     [fromView removeFromSuperview];
     [editableBook addSubview:toView];
   }
   completion:NULL];
}

-(void)previous
{
  if(currentPage == 0)
  {
    return;
  }
  currentPage--;
  UIView *fromView = [editableBook viewWithTag:23];
  UIView *toView =  [[UITextView alloc]initWithText:[content objectAtIndex:currentPage]];
  toView.frame = editableBook.bounds;
  toView.tag = 23;
  [UIView transitionWithView:editableBook duration:0.2 options:UIViewAnimationTransitionFlipFromLeft
   animations:^
   {
     [fromView removeFromSuperview];
     [editableBook addSubview:toView];
   }
   completion:NULL];
}

-(void)cutInPages:(NSString *)text
{
  NSRange whereToCut = whereToCut = [text rangeOfString:@" "];
  NSString *pageText = [text substringToIndex:whereToCut.location];
  NSString *rest = [text substringFromIndex:whereToCut.location];;
  CGFloat height = 0;
  while (height<1024.) 
  {
    NSRange whereToCut = [rest rangeOfString:@" "];
    NSString *wordOfRest = [rest substringToIndex:whereToCut.location];
    pageText = [NSString stringWithFormat:@"%@%@", pageText, wordOfRest];
    rest = [rest substringFromIndex:whereToCut.location];;
    CGSize size = [pageText sizeWithFont:font
                      constrainedToSize:CGSizeMake(768., 10000) 
                          lineBreakMode:UILineBreakModeWordWrap];
    height = size.height;
  }
  if(height>1024.)
  {
    //TODO cut the last word of pageText and prepend to the eest
  }
  [content addObject:pageText];
  if([rest length] > 0)
  {
    [self cutInPages:rest];
  }
}

- (void)dealloc 
{
  [editableBook release];
  [content release];
  [super dealloc];
}


@end
Ainsley answered 8/3, 2011 at 17:58 Comment(6)
Thanks for the quick answer. I understand what you mean (especially the algorithm to slice the text into little chunks), but I am totally lost when I start thinking about where to start.Salzburg
(1) Let's assume I have the algorith in place. Some kind of methods to which I pass an Array and the methods slices it up and saves it into another array.Salzburg
(2) What would be my next step? Let's consider that I want to have a separate algorithm/method for that. I put the array into an UITextView?Salzburg
Do you have some kind of sample code you'd be willing to share? Doesn't have to be the full thing, I'm just totally lost when it gets to applying the theory. Thanks!Salzburg
Thanks so much, that is really kind of you! This will hopefully get me started into the right direction. Once I made some progress, are you interested to see the results? If so, let me know how I should keep you posted! Thanks again, I really really appreciate your help!Salzburg
Yes, please post some comments here with your experiences. Especially I'm interested if/how you manage re-rendering during editing.Ainsley
D
0

I haven't seen the source code, but I'd be willing to guarantee that they're using the CoreText framework to do their pagination and rendering. Be warned, however, that when it says "low level", it means that. This is a framework that deals with rendering characters directly. Stuff like character selection and insertion (which you'd probably want for editable text) is quite difficult with CoreText.

I'd recommend going to with the NSString drawing and sizing methods that are part of the UIKit framework. But even then, you're going to have to do quite a bit of work to get this functional. I don't think UIKit offers a text-editing control that doesn't scroll. We assume that if you want to edit text, it's going to be of an arbitrary length, which means we put it inside a scrollable container (so that it can be any length).

In a nutshell, if you're a beginner, I would recommend working on something else. What you're asking for is not an easy thing to do. :)

Dorathydorca answered 8/3, 2011 at 20:35 Comment(5)
Hmm... so you're suggesting I should give up... Why is this so difficult? There must be some easy way to accomplish this. Let's forget about animation and all that. Suppose we have, for argument's sake, a text file which would fill 2 1/2 iphone screens (or simply 'pages'). There must be an easy way (with PageControl?) to program an app which fills the first page, then realises it hasn't any space any more, opens up page 2, again realises that it hasn't any space, and finally fills half of page 3.Salzburg
It surely can't be that difficult ... can it?Salzburg
@DanielNicolae: it's not impossible, and for an experienced programmer, it might not even be that difficult. but i also wouldn't call it a beginner-level problem. the hard hard hard part is the editable nature of your text. Doing this for static text should be fairly easy. Doing this for editable text is much harder, because suddenly you have the keyboard to worry about getting in the way. What happens when the keyboard comes up? do you resize everything? do you suddenly allow scrolling? do you shrink the page size? it's not a simple problemDorathydorca
Well then, let me get it sorted for a static text first. And even after that, I don't see a problem with the keyboard. I already programmed it so that it can't hide the text. The plan was to then allow scrolling (actually I didn't ban it before - it just wasn't necessary as I only had a limited amount of lines on the screen). So, yes, if the keyboard comes up, we'll resize UITextView (or simply push it upwards without resizing it, but that ca. 50% will be hidden).Salzburg
In a way, it's like having (1) a iBooks App with STATIC text which (2) suddenly changes into a notes app (with all the scrollable niceties) when the user wants to edit a page. The input will, however, influence how the rest is formatted (e.g. if he inserts another page of text on page 2, for instance, we'll need to push that content to pages 3 and 4 etc.Salzburg

© 2022 - 2024 — McMap. All rights reserved.