类的方法和属性
UIPageViewControll 是苹果自带的带有翻页效果的一个API,它的内部有两种动画效果,一种是类似图书翻页的卷曲效果;
UIPageViewControllerTransitionStylePageCurl
一种是简单的滚动效果
UIPageViewControllerTransitionStyleScroll
这两个动画效果一旦执行了初始化方法后,就不可更改
- (instancetype)initWithTransitionStyle:(UIPageViewControllerTransitionStyle)style navigationOrientation:(UIPageViewControllerNavigationOrientation)navigationOrientation options:(nullable NSDictionary<UIPageViewControllerOptionsKey, id> *)options NS_DESIGNATED_INITIALIZER;
这个初始化方法还包含两个参数,一个是控制器的滚动方向:
typedef NS_ENUM(NSInteger, UIPageViewControllerNavigationOrientation) {
UIPageViewControllerNavigationOrientationHorizontal = 0,
UIPageViewControllerNavigationOrientationVertical = 1
};
一个是控制器可以设置的options key,
// Key for specifying spine location in options dictionary argument to initWithTransitionStyle:navigationOrientation:options:.
// Value should be a 'UIPageViewControllerSpineLocation' wrapped in an NSNumber.
// Only valid for use with page view controllers with transition style 'UIPageViewControllerTransitionStylePageCurl'.
UIKIT_EXTERN UIPageViewControllerOptionsKey const UIPageViewControllerOptionSpineLocationKey;
// Key for specifying spacing between pages in options dictionary argument to initWithTransitionStyle:navigationOrientation:options:.
// Value should be a CGFloat wrapped in an NSNumber. Default is '0'.
// Only valid for use with page view controllers with transition style 'UIPageViewControllerTransitionStyleScroll'.
UIKIT_EXTERN UIPageViewControllerOptionsKey const UIPageViewControllerOptionInterPageSpacingKey NS_AVAILABLE_IOS(6_0);
阅读API文档,这两个key分别只能对应一种滚动样式的控制器,其中
UIPageViewControllerOptionSpineLocationKey
可以控制类似书本的书脊的位置,只可用于书本翻动样式



typedef NS_ENUM(NSInteger, UIPageViewControllerSpineLocation) {
UIPageViewControllerSpineLocationNone = 0, // Returned if 'spineLocation' is queried when 'transitionStyle' is not 'UIPageViewControllerTransitionStylePageCurl'.
UIPageViewControllerSpineLocationMin = 1, // Requires one view controller.
UIPageViewControllerSpineLocationMid = 2, // Requires two view controllers.
UIPageViewControllerSpineLocationMax = 3 // Requires one view controller.
}; // Only pertains to 'UIPageViewControllerTransitionStylePageCurl'.
UIPageViewControllerOptionInterPageSpacingKey
可以控制两个控制器之间的距离,只可用于滚动样式
@property (nonatomic, readonly) UIPageViewControllerTransitionStyle transitionStyle;
@property (nonatomic, readonly) UIPageViewControllerNavigationOrientation navigationOrientation;
@property (nonatomic, readonly) UIPageViewControllerSpineLocation spineLocation; // If transition style is 'UIPageViewControllerTransitionStylePageCurl', default is 'UIPageViewControllerSpineLocationMin', otherwise 'UIPageViewControllerSpineLocationNone'.
这三个只读的参数,可以获取到控制器的一些基本信息
// Whether client content appears on both sides of each page. If 'NO', content on page front will partially show through back.
// If 'UIPageViewControllerSpineLocationMid' is set, 'doubleSided' is set to 'YES'. Setting 'NO' when spine location is mid results in an exception.
@property (nonatomic, getter=isDoubleSided) BOOL doubleSided; // Default is 'NO'.
这个参数,专门用于书本翻页样式,如果为NO,则被翻页的页面背面,会有一个默认的背面展示出来,如果被翻页的页面背景是一个比较暗的色调的话,默认的背面会偏白,会比较难看。当设置了doubleSided为YES后,就可以自己设置被翻页页面的背面,关于如何设置,后面介绍代理方法的时候会进行讨论。

// Set visible view controllers, optionally with animation. Array should only include view controllers that will be visible after the animation has completed.
// For transition style 'UIPageViewControllerTransitionStylePageCurl', if 'doubleSided' is 'YES' and the spine location is not 'UIPageViewControllerSpineLocationMid', two view controllers must be included, as the latter view controller is used as the back.
- (void)setViewControllers:(nullable NSArray<UIViewController *> *)viewControllers direction:(UIPageViewControllerNavigationDirection)direction animated:(BOOL)animated completion:(void (^ __nullable)(BOOL finished))completion;
这个方法可以设置要展示的控制器,这边有个要注意的地方,这个viewControllers并不是要把所有的控制器传入,只用于传入可见的控制器,也就是说,如果是一般的场景的话,这个viewControllers只用传入一个控制器,当spineLocation这个属性为UIPageViewControllerSpineLocationMid,也就是书脊在页面中间的时候,传入两个才有意义,这个时候传入的两个页面就是书页的左右两页。如果spineLocation这个属性不是UIPageViewControllerSpineLocationMid,且doubleSided为YES的话,传入的第二个控制器为第一个控制器的背面(文档中写着如果doubleSided这个值设置为YES的话,要传入两个控制器,但是我试了下,传入一个也没有什么关系,只用后面数据源代理的时候保证有另外的控制器就可以)。
UIPageViewControllerNavigationDirection
这个枚举用来控制页面进入的方向,也就是向左或向右(向上或向下)。
typedef NS_ENUM(NSInteger, UIPageViewControllerNavigationDirection) {
UIPageViewControllerNavigationDirectionForward,
UIPageViewControllerNavigationDirectionReverse
}; // For 'UIPageViewControllerNavigationOrientationHorizontal', 'forward' is right-to-left, like pages in a book. For 'UIPageViewControllerNavigationOrientationVertical', bottom-to-top, like pages in a wall calendar.
最后还有两个只读属性
// An array of UIGestureRecognizers pre-configured to handle user interaction. Initially attached to a view in the UIPageViewController's hierarchy, they can be placed on an arbitrary view to change the region in which the page view controller will respond to user gestures.
// Only populated if transition style is 'UIPageViewControllerTransitionStylePageCurl'.
@property(nonatomic, readonly) NSArray<__kindof UIGestureRecognizer *> *gestureRecognizers;
@property (nullable, nonatomic, readonly) NSArray<__kindof UIViewController *> *viewControllers;
一个是可以拿到pageViewController的所有手势(只有书页翻动样式才能拿到),在数组里面有一个Tap手势和一个Pan手势,可以通过循环的方法,为手势添加代理,从而实现代理方法。
另一个是可以拿到pageViewController的所有可见控制器,和上面setViewController方法的viewControllers是同一个意思。
比较常用的代理方法和数据源方法
数据源方法
// In terms of navigation direction. For example, for 'UIPageViewControllerNavigationOrientationHorizontal', view controllers coming 'before' would be to the left of the argument view controller, those coming 'after' would be to the right.
// Return 'nil' to indicate that no more progress can be made in the given direction.
// For gesture-initiated transitions, the page view controller obtains view controllers via these methods, so use of setViewControllers:direction:animated:completion: is not required.
- (nullable UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(UIViewController *)viewController;
- (nullable UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerAfterViewController:(UIViewController *)viewController;
这两个代理方法一个是获取当前页面的上一个页面,一个是获取当前页面的下一个页面。要注意,如果设置了doubleSlide为YES的话,那么每翻动一页就会调两次方法,一次是当前页面的背面,第二次才是下一页。
上面说了,默认的背面会比较偏白,那么我们可以通过截图->翻转->调整透明度的方法,自己去获取一个背面的样式
//view为上一个viewController.view
- (UIImage *)captureView:(UIView *)view {
CGRect rect = view.bounds;
UIGraphicsBeginImageContextWithOptions(rect.size, YES, 0.0f);
CGContextRef context = UIGraphicsGetCurrentContext();
CGAffineTransform transform = CGAffineTransformMake(-1.0, 0.0, 0.0, 1.0, rect.size.width, 0.0);
CGContextConcatCTM(context,transform);
[view.layer renderInContext:context];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
- (instancetype)initWithViewController:(__kindof UIViewController *)viewController {
if (self = [super init]) {
UIImageView *imageView = [[UIImageView alloc] init];
imageView.frame = self.view.bounds;
imageView.image = [self captureView:viewController.view];
imageView.alpha = 0.8;
[self.view addSubview:imageView];
}
return self;
}

代理方法
// Sent when a gesture-initiated transition begins.
- (void)pageViewController:(UIPageViewController *)pageViewController willTransitionToViewControllers:(NSArray<UIViewController *> *)pendingViewControllers NS_AVAILABLE_IOS(6_0);
当页面即将开始翻页动画的时候,这个方法会调用。pendingViewControllers:将要出现的ViewControllers
// Sent when a gesture-initiated transition ends. The 'finished' parameter indicates whether the animation finished, while the 'completed' parameter indicates whether the transition completed or bailed out (if the user let go early).
- (void)pageViewController:(UIPageViewController *)pageViewController didFinishAnimating:(BOOL)finished previousViewControllers:(NSArray<UIViewController *> *)previousViewControllers transitionCompleted:(BOOL)completed;
当页面做完动画的时候,这个方法会调用。API文档也写明了finished和completed的区别:finished为动画结束的判断,completed为是否翻页成功的判断。
网友评论