I'm working on a multi-page reading application backed by TextKit, based off of the "Advanced Text Layouts and Effects with Text Kit" session from WWDC 2013 (but with some code reconstructed from incomplete example). The basic structure is you calculate the number of pages needed for your text upfront, then create an NSTextContainer for each page and add it to the NSLayoutManager. Whenever the UIPageViewController asks for the next or previous page, you create a new UITextView and set its backing text container by selecting the correct one out of the NLayoutManger's array of NSTextContainers.
Unfortunately, I have a problem where the text gets reflowed both on the first page, and the first time that I page back to any given page. Here's what it look like:
It's not the most glaring effect (if you missed it, pay attention to the top of the screen when paging back), but it is a little disorienting, and I'd like to eliminate it if possible. Given that the text containers should be calculated up front, I don't understand why it's reflowing the text, or how to prevent it. Does anyone know what the problem is?
EDIT: Adding a code sample.
@interface ReaderViewController () <UIPageViewControllerDataSource>
@property (nonatomic, assign) NSUInteger numberOfPages;
@property (nonatomic, retain) UIPageViewController *pageViewController;
@property (nonatomic, retain) NSTextStorage *currentDocument;
@property (nonatomic, retain) NSLayoutManager *layoutManager;
@end
@implementation ReaderViewController
- (instancetype)initWithDocument:(NSTextStorage *)document {
if ((self = [super init])) {
_currentDocument = document;
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
_layoutManager = [[NSLayoutManager alloc] init];
[self.layoutManager setTextStorage:self.currentDocument];
self.layoutManager.delegate = self;
_pageViewController = [[UIPageViewController alloc] initWithTransitionStyle:UIPageViewControllerTransitionStyleScroll
navigationOrientation:UIPageViewControllerNavigationOrientationHorizontal
options:nil];
self.pageViewController.view.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
self.pageViewController.dataSource = self;
[self addChildViewController:self.pageViewController];
[self.view addSubview:self.pageViewController.view];
// the numberOfPages accessor lazily calculates the number of pages needed to contain the document
for (int i = 0; i < self.numberOfPages; ++i) {
NSTextContainer *container = [[NSTextContainer alloc] init];
container.size = [self _textFrame].size;
[self.layoutManager addTextContainer:container];
}
[self.pageViewController setViewControllers:@[[self viewControllerForPageNumber:0]] direction:UIPageViewControllerNavigationDirectionForward animated:NO completion:nil];
}
- (UIViewController *)viewControllerForPageNumber:(NSUInteger)pageNumber {
if (pageNumber >= self.numberOfPages) {
return nil;
}
// SinglePageViewController is a lightweight view controller that has a UITextView and a page number
SinglePageViewController *vc = [[SinglePageViewController alloc] init];
UITextView *textView = [[UITextView alloc] initWithFrame:[self _textFrame] textContainer:[self.layoutManager.textContainers objectAtIndex:pageNumber]];
textView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
textView.scrollEnabled = NO;
textView.editable = NO;
[textView setFont:[UIFont fontWithName:@"IowanOldStyle-Roman" size:20.f]];
[vc.view addSubview:textView];
vc.textView = textView;
vc.pageNumber = pageNumber;
return vc;
}
#pragma mark - UIPageViewControllerDataSource
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerAfterViewController:(UIViewController *)viewController {
NSUInteger currentPage = [(SinglePageViewController *)viewController pageNumber];
if (currentPage >= self.numberOfPages) {
return nil;
}
return [self viewControllerForPageNumber:currentPage + 1];
}
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(UIViewController *)viewController {
NSUInteger currentPage = [(SinglePageViewController *)viewController pageNumber];
if (currentPage == 0) {
return nil;
}
return [self viewControllerForPageNumber:currentPage - 1];
}
#pragma mark - Private
- (CGRect)_textFrame {
return CGRectInset(self.view.bounds, 5., 0.);
}
@end