UIPageViewController/TextKit Reflowing Text on pag

2020-06-23 07:21发布

问题:

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

回答1:

I faced the same issue. My solution is to put the scrollEnabled after addSubview will force the textContainer re-calculate the size. See below code:

vc.view.addSubview(textView)
textView.scrollEnabled = false;

Update. Finally, I think I found the right answer... We need to reset the container size in the didChangeGeometryFromSize callback. Please correct me if it is not true :)

func layoutManager(layoutManager: NSLayoutManager, 
                   textContainer: NSTextContainer, 
                   didChangeGeometryFromSize oldSize: CGSize) {

    textContainer.size = self.textFrame().size;
    println(textContainer.size.width)
    println(textContainer.size.height)
}


回答2:

Try to set automaticallyAdjustsScrollViewInsets of your view controller to NO.