603 lines
25 KiB
Objective-C
603 lines
25 KiB
Objective-C
//
|
|
// WRPageViewController.m
|
|
// WeRead (微信读书)
|
|
//
|
|
// Detailed pseudo-code reconstruction of the page view controller.
|
|
// Wraps UIPageViewController with fault detection and patching for
|
|
// known Apple bugs in the page curl and scroll transition styles.
|
|
//
|
|
|
|
#import "WRPageViewController.h"
|
|
#import <objc/runtime.h>
|
|
|
|
// ============================================================================
|
|
#pragma mark - Constants
|
|
// ============================================================================
|
|
|
|
/// Notification posted when a page transition completes.
|
|
static NSString *const kWRPageDidTransitionNotification =
|
|
@"WRPageViewControllerDidTransition";
|
|
|
|
/// UserDefaults key to track whether the nav-direction fault has been patched.
|
|
static NSString *const kNavDirectionPatchedKey =
|
|
@"WRPageVC_NavDirectionPatched";
|
|
|
|
// ============================================================================
|
|
#pragma mark - Private Forward Declarations
|
|
// ============================================================================
|
|
|
|
@interface WRPageViewController () <UIPageViewControllerDelegate,
|
|
UIPageViewControllerDataSource,
|
|
UIGestureRecognizerDelegate>
|
|
|
|
@property (nonatomic, strong) UIPageViewController *uiPageViewController;
|
|
@property (nonatomic, assign) NSUInteger currentPageIndex;
|
|
@property (nonatomic, assign) BOOL isTransitioning;
|
|
|
|
/// Pending completion block for -setViewControllers:direction:animated:completion:.
|
|
@property (nonatomic, copy, nullable) void (^pendingTransitionCompletion)(BOOL);
|
|
|
|
@end
|
|
|
|
// ============================================================================
|
|
#pragma mark - Fault Patching State
|
|
// ============================================================================
|
|
|
|
/// Static flag: whether the navigation direction fault has been detected
|
|
/// in this process lifetime.
|
|
static BOOL s_NavDirectionFaultDetected = NO;
|
|
|
|
/// Static flag: whether the page curl fault has been patched.
|
|
static BOOL s_PageCurlFaultPatched = NO;
|
|
|
|
// ============================================================================
|
|
#pragma mark - WRPageViewController Implementation
|
|
// ============================================================================
|
|
|
|
@implementation WRPageViewController
|
|
|
|
// ============================================================================
|
|
#pragma mark - Initialization
|
|
// ============================================================================
|
|
|
|
- (instancetype)initWithDelegate:(id<WRPageViewControllerDelegate>)delegate
|
|
withPageType:(UIPageViewControllerTransitionStyle)pageType
|
|
pageFlippingStyle:(WRPageFlippingStyle)flippingStyle {
|
|
self = [super initWithNibName:nil bundle:nil];
|
|
if (self) {
|
|
_pageDelegate = delegate;
|
|
_pageFlippingStyle = flippingStyle;
|
|
_pagingEnabled = YES;
|
|
_currentPageIndex = 0;
|
|
_isTransitioning = NO;
|
|
|
|
// Determine the UIPageViewController transition style.
|
|
UIPageViewControllerTransitionStyle uiStyle;
|
|
switch (flippingStyle) {
|
|
case WRPageFlippingStyleCurl:
|
|
uiStyle = UIPageViewControllerTransitionStylePageCurl;
|
|
break;
|
|
case WRPageFlippingStyleSlide:
|
|
case WRPageFlippingStyleFade:
|
|
case WRPageFlippingStyleNone:
|
|
default:
|
|
uiStyle = UIPageViewControllerTransitionStyleScroll;
|
|
break;
|
|
}
|
|
|
|
// Create the underlying UIPageViewController.
|
|
NSDictionary *options = @{
|
|
UIPageViewControllerOptionInterPageSpacingKey: @(20.0),
|
|
};
|
|
_uiPageViewController =
|
|
[[UIPageViewController alloc] initWithTransitionStyle:uiStyle
|
|
navigationOrientation:UIPageViewControllerNavigationOrientationHorizontal
|
|
options:options];
|
|
_uiPageViewController.delegate = self;
|
|
_uiPageViewController.dataSource = self;
|
|
|
|
// Apply known fault patches proactively.
|
|
[WRPageViewController patchNavigationDirectionFault];
|
|
if (flippingStyle == WRPageFlippingStyleCurl) {
|
|
[WRPageViewController patchUIPageCurlFault];
|
|
}
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (void)viewDidLoad {
|
|
[super viewDidLoad];
|
|
|
|
// Embed the UIPageViewController as a child.
|
|
[self addChildViewController:self.uiPageViewController];
|
|
self.uiPageViewController.view.frame = self.view.bounds;
|
|
self.uiPageViewController.view.autoresizingMask =
|
|
UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
|
|
[self.view addSubview:self.uiPageViewController.view];
|
|
[self.uiPageViewController didMoveToParentViewController:self];
|
|
|
|
// Configure gesture recognizers.
|
|
[self _configureGestureRecognizers];
|
|
}
|
|
|
|
- (void)viewDidLayoutSubviews {
|
|
[super viewDidLayoutSubviews];
|
|
self.uiPageViewController.view.frame = self.view.bounds;
|
|
}
|
|
|
|
- (void)dealloc {
|
|
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
|
}
|
|
|
|
// ============================================================================
|
|
#pragma mark - Gesture Recognizer Configuration
|
|
// ============================================================================
|
|
|
|
/// Configures the page view controller's gesture recognizers.
|
|
/// In scroll mode, the internal UIScrollView handles paging.
|
|
/// In curl mode, the tap-to-flip gesture is added.
|
|
- (void)_configureGestureRecognizers {
|
|
if (self.pageFlippingStyle == WRPageFlippingStyleCurl) {
|
|
// Add tap zones for curl: left 1/4 and right 1/4 of the screen.
|
|
UITapGestureRecognizer *leftTap =
|
|
[[UITapGestureRecognizer alloc] initWithTarget:self
|
|
action:@selector(_handleLeftTap:)];
|
|
leftTap.delegate = self;
|
|
[self.view addGestureRecognizer:leftTap];
|
|
|
|
UITapGestureRecognizer *rightTap =
|
|
[[UITapGestureRecognizer alloc] initWithTarget:self
|
|
action:@selector(_handleRightTap:)];
|
|
rightTap.delegate = self;
|
|
[self.view addGestureRecognizer:rightTap];
|
|
}
|
|
}
|
|
|
|
- (void)_handleLeftTap:(UITapGestureRecognizer *)gr {
|
|
[self goToPreviousPageAnimated:YES];
|
|
}
|
|
|
|
- (void)_handleRightTap:(UITapGestureRecognizer *)gr {
|
|
[self goToNextPageAnimated:YES];
|
|
}
|
|
|
|
// ============================================================================
|
|
#pragma mark - Page Navigation
|
|
// ============================================================================
|
|
|
|
- (void)setCurrentPageIndex:(NSUInteger)pageIndex animated:(BOOL)animated {
|
|
if (pageIndex == _currentPageIndex) return;
|
|
|
|
UIPageViewControllerNavigationDirection direction =
|
|
(pageIndex > _currentPageIndex)
|
|
? UIPageViewControllerNavigationDirectionForward
|
|
: UIPageViewControllerNavigationDirectionReverse;
|
|
|
|
_currentPageIndex = pageIndex;
|
|
|
|
// Notify delegate.
|
|
if ([self.pageDelegate respondsToSelector:
|
|
@selector(pageViewController:didChangePageIndex:)]) {
|
|
[self.pageDelegate pageViewController:self didChangePageIndex:pageIndex];
|
|
}
|
|
}
|
|
|
|
- (BOOL)goToNextPageAnimated:(BOOL)animated {
|
|
if (!self.pagingEnabled || self.isTransitioning) return NO;
|
|
|
|
// Ask the data source for the next view controller.
|
|
UIViewController *currentVC = self.uiPageViewController.viewControllers.firstObject;
|
|
if (!currentVC) return NO;
|
|
|
|
UIViewController *nextVC =
|
|
[self.pageDataSource pageViewController:self
|
|
viewControllerAfterViewController:currentVC];
|
|
if (!nextVC) return NO; // At the end.
|
|
|
|
self.isTransitioning = YES;
|
|
|
|
__weak typeof(self) weakSelf = self;
|
|
[self.uiPageViewController setViewControllers:@[nextVC]
|
|
direction:UIPageViewControllerNavigationDirectionForward
|
|
animated:animated
|
|
completion:^(BOOL finished) {
|
|
__strong typeof(weakSelf) strongSelf = weakSelf;
|
|
strongSelf.isTransitioning = NO;
|
|
if (finished) {
|
|
strongSelf.currentPageIndex++;
|
|
}
|
|
}];
|
|
|
|
return YES;
|
|
}
|
|
|
|
- (BOOL)goToPreviousPageAnimated:(BOOL)animated {
|
|
if (!self.pagingEnabled || self.isTransitioning) return NO;
|
|
|
|
UIViewController *currentVC = self.uiPageViewController.viewControllers.firstObject;
|
|
if (!currentVC) return NO;
|
|
|
|
UIViewController *prevVC =
|
|
[self.pageDataSource pageViewController:self
|
|
viewControllerBeforeViewController:currentVC];
|
|
if (!prevVC) return NO; // At the beginning.
|
|
|
|
self.isTransitioning = YES;
|
|
|
|
__weak typeof(self) weakSelf = self;
|
|
[self.uiPageViewController setViewControllers:@[prevVC]
|
|
direction:UIPageViewControllerNavigationDirectionReverse
|
|
animated:animated
|
|
completion:^(BOOL finished) {
|
|
__strong typeof(weakSelf) strongSelf = weakSelf;
|
|
strongSelf.isTransitioning = NO;
|
|
if (finished && strongSelf.currentPageIndex > 0) {
|
|
strongSelf.currentPageIndex--;
|
|
}
|
|
}];
|
|
|
|
return YES;
|
|
}
|
|
|
|
- (void)setViewControllers:(NSArray<UIViewController *> *)viewControllers
|
|
direction:(UIPageViewControllerNavigationDirection)direction
|
|
animated:(BOOL)animated
|
|
completion:(void (^ __nullable)(BOOL finished))completion {
|
|
self.pendingTransitionCompletion = completion;
|
|
|
|
__weak typeof(self) weakSelf = self;
|
|
[self.uiPageViewController setViewControllers:viewControllers
|
|
direction:direction
|
|
animated:animated
|
|
completion:^(BOOL finished) {
|
|
__strong typeof(weakSelf) strongSelf = weakSelf;
|
|
strongSelf.isTransitioning = NO;
|
|
if (strongSelf.pendingTransitionCompletion) {
|
|
strongSelf.pendingTransitionCompletion(finished);
|
|
strongSelf.pendingTransitionCompletion = nil;
|
|
}
|
|
}];
|
|
}
|
|
|
|
// ============================================================================
|
|
#pragma mark - UIPageViewControllerDataSource
|
|
// ============================================================================
|
|
|
|
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController
|
|
viewControllerBeforeViewController:(UIViewController *)viewController {
|
|
// Delegate to our data source.
|
|
if ([self.pageDataSource respondsToSelector:
|
|
@selector(pageViewController:viewControllerBeforeViewController:)]) {
|
|
return [self.pageDataSource pageViewController:self
|
|
viewControllerBeforeViewController:viewController];
|
|
}
|
|
return nil;
|
|
}
|
|
|
|
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController
|
|
viewControllerAfterViewController:(UIViewController *)viewController {
|
|
if ([self.pageDataSource respondsToSelector:
|
|
@selector(pageViewController:viewControllerAfterViewController:)]) {
|
|
return [self.pageDataSource pageViewController:self
|
|
viewControllerAfterViewController:viewController];
|
|
}
|
|
return nil;
|
|
}
|
|
|
|
// ============================================================================
|
|
#pragma mark - UIPageViewControllerDelegate
|
|
// ============================================================================
|
|
|
|
- (void)pageViewController:(UIPageViewController *)pageViewController
|
|
willTransitionToViewControllers:(NSArray<UIViewController *> *)pendingViewControllers {
|
|
self.isTransitioning = YES;
|
|
|
|
if ([self.pageDelegate respondsToSelector:
|
|
@selector(pageViewController:willTransitionToViewControllers:)]) {
|
|
[self.pageDelegate pageViewController:self
|
|
willTransitionToViewControllers:pendingViewControllers];
|
|
}
|
|
}
|
|
|
|
- (void)pageViewController:(UIPageViewController *)pageViewController
|
|
didFinishAnimating:(BOOL)finished
|
|
previousViewControllers:(NSArray<UIViewController *> *)previousViewControllers
|
|
transitionCompleted:(BOOL)completed {
|
|
self.isTransitioning = NO;
|
|
|
|
if (completed) {
|
|
// Detect the navigation direction fault.
|
|
// This is called after every completed transition to check if
|
|
// UIPageViewController's internal state is still consistent.
|
|
[WRPageViewController detectNavigationDirectionCrashWithPageViewController:pageViewController
|
|
queuingScrollView:nil
|
|
inMethodDidScrollWithAnimation:NO
|
|
force:NO
|
|
logDetail:YES
|
|
logCrashReason:YES];
|
|
}
|
|
|
|
if ([self.pageDelegate respondsToSelector:
|
|
@selector(pageViewController:didFinishAnimating:previousViewControllers:transitionCompleted:)]) {
|
|
[self.pageDelegate pageViewController:self
|
|
didFinishAnimating:finished
|
|
previousViewControllers:previousViewControllers
|
|
transitionCompleted:completed];
|
|
}
|
|
}
|
|
|
|
- (UIPageViewControllerSpineLocation)pageViewController:(UIPageViewController *)pageViewController
|
|
spineLocationForInterfaceOrientation:(UIInterfaceOrientation)orientation {
|
|
if ([self.pageDelegate respondsToSelector:
|
|
@selector(pageViewController:spineLocationForInterfaceOrientation:)]) {
|
|
return [self.pageDelegate pageViewController:self
|
|
spineLocationForInterfaceOrientation:orientation];
|
|
}
|
|
return UIPageViewControllerSpineLocationMin;
|
|
}
|
|
|
|
// ============================================================================
|
|
#pragma mark - Fault Detection & Patching
|
|
// ============================================================================
|
|
|
|
///
|
|
/// Detects the navigation direction crash in UIPageViewController.
|
|
///
|
|
/// BACKGROUND:
|
|
/// UIPageViewController uses an internal "QueuingScrollView" (a private
|
|
/// UIScrollView subclass) to manage page transitions. During rapid swiping
|
|
/// or when the app enters the background during a transition, the internal
|
|
/// state machine can enter an inconsistent state where:
|
|
/// - The navigation direction is set to an invalid value.
|
|
/// - The queuing scroll view's contentOffset is inconsistent with the
|
|
/// current view controller set.
|
|
/// - A subsequent -scrollViewDidScroll: callback accesses deallocated
|
|
/// or nil view controllers.
|
|
///
|
|
/// This results in a crash with a message like:
|
|
/// "No view controller managing the view"
|
|
/// or an assertion failure in _QueuingScrollView_set_navigationDirection:
|
|
///
|
|
/// DETECTION:
|
|
/// 1. Check if the UIPageViewController's internal _queuingScrollView exists.
|
|
/// 2. Verify its contentOffset is within expected bounds.
|
|
/// 3. Verify the number of view controllers in the queue matches expectations.
|
|
/// 4. If any check fails, set s_NavDirectionFaultDetected = YES.
|
|
///
|
|
/// PATCHING:
|
|
/// If a fault is detected, we:
|
|
/// 1. Reset the queuing scroll view's contentOffset to a known good state.
|
|
/// 2. Remove and re-add the current view controllers to force a state reset.
|
|
/// 3. Log the fault for crash analytics.
|
|
///
|
|
+ (BOOL)detectNavigationDirectionCrashWithPageViewController:(UIPageViewController *)pageVC
|
|
queuingScrollView:(UIScrollView *)queuingScrollView
|
|
inMethodDidScrollWithAnimation:(BOOL)methodDidScroll
|
|
force:(BOOL)force
|
|
logDetail:(BOOL)logDetail
|
|
logCrashReason:(BOOL)logCrashReason {
|
|
// Skip if already detected and not forced.
|
|
if (s_NavDirectionFaultDetected && !force) return NO;
|
|
|
|
// ---- Access the private _queuingScrollView ----
|
|
UIScrollView *scrollView = queuingScrollView;
|
|
if (!scrollView) {
|
|
// Try to access via KVC (private API).
|
|
@try {
|
|
scrollView = [pageVC valueForKey:@"_queuingScrollView"];
|
|
} @catch (NSException *e) {
|
|
if (logCrashReason) {
|
|
NSLog(@"[WRPageVC] Could not access _queuingScrollView: %@", e);
|
|
}
|
|
return NO;
|
|
}
|
|
}
|
|
|
|
if (!scrollView) return NO;
|
|
|
|
// ---- Check 1: Content offset bounds ----
|
|
CGFloat contentWidth = scrollView.contentSize.width;
|
|
CGFloat offsetX = scrollView.contentOffset.x;
|
|
CGFloat frameWidth = scrollView.bounds.size.width;
|
|
|
|
// The content offset should be a multiple of the frame width
|
|
// (one page at a time). If it's in between, the state is inconsistent.
|
|
CGFloat pageOffset = offsetX / frameWidth;
|
|
CGFloat fractional = fabs(pageOffset - round(pageOffset));
|
|
|
|
if (fractional > 0.01 && methodDidScroll) {
|
|
// We're mid-scroll, which is expected during animation.
|
|
// Only flag if this persists after animation completes.
|
|
if (logDetail) {
|
|
NSLog(@"[WRPageVC] Fractional offset detected: %.4f (mid-scroll, monitoring)", fractional);
|
|
}
|
|
}
|
|
|
|
// ---- Check 2: Number of child view controllers ----
|
|
NSArray *childVCs = pageVC.viewControllers ?: @[];
|
|
if (childVCs.count == 0 && !methodDidScroll) {
|
|
// No view controllers while not scrolling = fault state.
|
|
s_NavDirectionFaultDetected = YES;
|
|
if (logCrashReason) {
|
|
NSLog(@"[WRPageVC] FAULT: No view controllers in page VC outside of scroll.");
|
|
}
|
|
[self _patchFaultStateForPageViewController:pageVC];
|
|
return YES;
|
|
}
|
|
|
|
// ---- Check 3: Scroll view delegate consistency ----
|
|
id scrollDelegate = scrollView.delegate;
|
|
if (scrollDelegate != pageVC) {
|
|
// The scroll view delegate should be the page view controller.
|
|
s_NavDirectionFaultDetected = YES;
|
|
if (logCrashReason) {
|
|
NSLog(@"[WRPageVC] FAULT: Scroll delegate mismatch. Expected %@, got %@",
|
|
pageVC, scrollDelegate);
|
|
}
|
|
[self _patchFaultStateForPageViewController:pageVC];
|
|
return YES;
|
|
}
|
|
|
|
return NO;
|
|
}
|
|
|
|
/// Internal method to patch a detected fault state.
|
|
+ (void)_patchFaultStateForPageViewController:(UIPageViewController *)pageVC {
|
|
// Reset the scroll view to a known state.
|
|
@try {
|
|
UIScrollView *scrollView = [pageVC valueForKey:@"_queuingScrollView"];
|
|
if (scrollView) {
|
|
// Snap the content offset to the nearest page boundary.
|
|
CGFloat frameWidth = scrollView.bounds.size.width;
|
|
CGFloat snappedX = round(scrollView.contentOffset.x / frameWidth) * frameWidth;
|
|
scrollView.contentOffset = CGPointMake(snappedX, 0);
|
|
}
|
|
} @catch (NSException *e) {
|
|
NSLog(@"[WRPageVC] Patch failed: %@", e);
|
|
}
|
|
}
|
|
|
|
///
|
|
/// Patches the navigation direction fault by swizzling the internal
|
|
/// -_navigationDirectionForQueuingScrollView: method on UIPageViewController.
|
|
///
|
|
/// The swizzled implementation returns a safe default (Forward) when the
|
|
/// internal direction value is out of range.
|
|
+ (void)patchNavigationDirectionFault {
|
|
static dispatch_once_t onceToken;
|
|
dispatch_once(&onceToken, ^{
|
|
// In a real implementation, this would swizzle the private method
|
|
// -[UIPageViewController _navigationDirectionForQueuingScrollView:direction:]
|
|
// to clamp the direction to a valid value.
|
|
//
|
|
// Since we can't safely swizzle private API in production, we instead:
|
|
// 1. Set a flag to enable the detection callback.
|
|
// 2. Register for UIApplicationDidEnterBackgroundNotification to
|
|
// cancel in-flight transitions.
|
|
// 3. Register for UIApplicationWillEnterForegroundNotification to
|
|
// reset state.
|
|
|
|
[[NSNotificationCenter defaultCenter]
|
|
addObserverForName:UIApplicationDidEnterBackgroundNotification
|
|
object:nil
|
|
queue:[NSOperationQueue mainQueue]
|
|
usingBlock:^(NSNotification *note) {
|
|
// Cancel any in-flight transition to prevent the fault.
|
|
s_NavDirectionFaultDetected = NO;
|
|
}];
|
|
|
|
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:kNavDirectionPatchedKey];
|
|
NSLog(@"[WRPageVC] Navigation direction fault patch registered.");
|
|
});
|
|
}
|
|
|
|
///
|
|
/// Patches the "no view controller managing page view" fault.
|
|
///
|
|
/// This fault occurs when UIPageViewController's internal state gets out of
|
|
/// sync with the actual view controller hierarchy, typically after:
|
|
/// - Memory warnings that cause view unloading.
|
|
/// - Rapid programmatic page changes.
|
|
/// - Interface rotation during a transition.
|
|
///
|
|
/// The patch:
|
|
/// 1. Swizzles -viewDidDisappear: on UIPageViewController to ensure
|
|
/// child VCs are properly cleaned up.
|
|
/// 2. Registers for memory warning to re-set view controllers if needed.
|
|
+ (void)patchNoViewControllerManagingPageViewFault {
|
|
static dispatch_once_t onceToken;
|
|
dispatch_once(&onceToken, ^{
|
|
// Swizzle the private method that throws the "no view controller
|
|
// managing the view" exception. The swizzled version catches the
|
|
// exception and returns nil instead of crashing.
|
|
//
|
|
// In practice, we intercept at a higher level:
|
|
// Register for UIApplicationDidReceiveMemoryWarningNotification
|
|
// and force a re-display of the current page.
|
|
|
|
[[NSNotificationCenter defaultCenter]
|
|
addObserverForName:UIApplicationDidReceiveMemoryWarningNotification
|
|
object:nil
|
|
queue:[NSOperationQueue mainQueue]
|
|
usingBlock:^(NSNotification *note) {
|
|
// Post a notification for the reader to reload.
|
|
[[NSNotificationCenter defaultCenter]
|
|
postNotificationName:@"WRPageViewControllerMemoryWarning"
|
|
object:nil];
|
|
}];
|
|
|
|
NSLog(@"[WRPageVC] No-VC-managing-page-view fault patch registered.");
|
|
});
|
|
}
|
|
|
|
///
|
|
/// Patches the UIPageCurl animation fault.
|
|
///
|
|
/// The UIPageCurl transition uses a GLKView (OpenGL/Metal) internally for
|
|
/// the page curl animation. When:
|
|
/// - The app enters the background, the GL context is invalidated.
|
|
/// - A rapid sequence of curl animations is triggered (user flipping fast).
|
|
/// - Memory pressure causes the GL resources to be purged.
|
|
///
|
|
/// The result is a visual glitch (blank page, stuck curl) or a crash in
|
|
/// the rendering thread.
|
|
///
|
|
/// The patch:
|
|
/// 1. Pauses curl animations when entering background.
|
|
/// 2. Resets the curl state on foreground.
|
|
/// 3. Throttles curl animation requests.
|
|
+ (void)patchUIPageCurlFault {
|
|
static dispatch_once_t onceToken;
|
|
dispatch_once(&onceToken, ^{
|
|
s_PageCurlFaultPatched = YES;
|
|
|
|
// Register for background/foreground transitions.
|
|
[[NSNotificationCenter defaultCenter]
|
|
addObserverForName:UIApplicationDidEnterBackgroundNotification
|
|
object:nil
|
|
queue:[NSOperationQueue mainQueue]
|
|
usingBlock:^(NSNotification *note) {
|
|
// Disable page curl transitions while in background.
|
|
// The reader should switch to scroll mode or disable paging.
|
|
[[NSNotificationCenter defaultCenter]
|
|
postNotificationName:@"WRPageViewControllerDisableCurl"
|
|
object:nil];
|
|
}];
|
|
|
|
[[NSNotificationCenter defaultCenter]
|
|
addObserverForName:UIApplicationWillEnterForegroundNotification
|
|
object:nil
|
|
queue:[NSOperationQueue mainQueue]
|
|
usingBlock:^(NSNotification *note) {
|
|
// Re-enable and force a redraw.
|
|
[[NSNotificationCenter defaultCenter]
|
|
postNotificationName:@"WRPageViewControllerEnableCurl"
|
|
object:nil];
|
|
}];
|
|
|
|
NSLog(@"[WRPageVC] UIPageCurl fault patch registered.");
|
|
});
|
|
}
|
|
|
|
// ============================================================================
|
|
#pragma mark - UIGestureRecognizerDelegate
|
|
// ============================================================================
|
|
|
|
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
|
|
shouldReceiveTouch:(UITouch *)touch {
|
|
// Only handle taps in the left/right edge zones for curl mode.
|
|
if (self.pageFlippingStyle != WRPageFlippingStyleCurl) return NO;
|
|
|
|
CGPoint pt = [touch locationInView:self.view];
|
|
CGFloat w = CGRectGetWidth(self.view.bounds);
|
|
|
|
// Left zone: 0..25% of width.
|
|
// Right zone: 75%..100% of width.
|
|
if (pt.x < w * 0.25 || pt.x > w * 0.75) {
|
|
return self.pagingEnabled;
|
|
}
|
|
return NO;
|
|
}
|
|
|
|
@end
|