添加FunctionalReactivePixels

    这个方法没有任何特殊的,只是些一般的OC方法。当然别忘了在当前实现文件里加载视图控制器(FRPFullSizePhotoViewControler)的头文件.现在让我们来创建这个视图控制器(FRPFullSizePhotoViewControler).

    创建一个UIViewController的子类FRPFullSizePhotoViewControler,这不会是一个特别的‘Reactive’的视图控制器,实际上大部分只是子视图控制器的模版。

    1. @class FRPFullSizePhotoViewController;
    2. @protocol FRPFullSizePhotoViewControllerDelegate <NSOject>
    3. - (void)userDidScroll:(FRPFullSizePhotoViewController *)viewController toPhotoAtIndex:(NSInteger)index;
    4. @end
    5. @interface FRPFullSizePhotoViewController : UIViewController
    6. - (instancetype)initWithPhotoModels:(NSArray *)photoModelArray currentPhotoIndex:(NSInteger)photoIndex;
    7. @property (nonatomic , readonly) NSArray *photoModelArray;
    8. @property (nonatomic, weak) id<FRPFullSizePhotoViewControllerDelegate> delegate;
    9. @end

    回到画廊视图控制器实现必要的代理方法:

    1. - (void)userDidScroll:(FRPFullSizePhotoViewController *)viewController toPhotoAtIndex:(NSInteger)index{
    2. [self.collectionView scrollToItemAtIndexPath:[NSIndexPath indexPathForItem:index inSection:0]
    3. atScrollPosition:UICollectionViewScrollPositionCenteredVertically
    4. animated:NO];
    5. }

    当我们滑到一个新的图像去查看其高清图片时,这个方法将更新collectionView滑动的位置。这样一来,当用户查看完高清图回到这个界面的时候,高清图所对应的缩略图将会显示在界面上,方便用户获知自己浏览的位置以及继续往下浏览。

    #import这些必要的数据模型的头文件并追加一下两个私有属性:

    1. @interface FRPFullSizePhotoViewController () <UIPageViewControllerDataSource, UIPageViewControllerDelegate>
    2. //Private assignment
    3. @property (nonatomic, strong) NSArray *photoModeArray;
    4. //Private properties
    5. @property (nonatomic, strong) UIPageViewController *pageViewController;
    6. @end

    photoModelArray是共有的只读属性,但是内部可读写。第二个属性是我们的子视图控制器。我们这样来初始化:

    1. - (void)viewDidLoad{
    2. [super viewDidLoad];
    3. self.view,backGroundColor = [UIColor blackColor];
    4. self.pageViewController.view.frame = self.view.bounds;
    5. [self.view addSubView:self.pageViewController.view];
    6. }

    我要指出的是,简便起见,在我的应用里我禁用了横向展示,因为这不是一本关于autoresizingMask或者autoLayout的书。你可以通过Eria Sadun的书了解更多关于autoLayout方面的细节。

    下面我们来了解一下UIPageViewController的数据源协议和代理协议。

    1. - (void)pageViewController:(UIPageViewController *)pageViewController
    2. didFinishAnimating: (BOOL)finished
    3. previousViewControllers:(NSArray *)previousViewControllers
    4. transitionCompleted:(BOOL)completed{
    5. self.title = [[self.pageViewController.viewControllers.firstObject photoModel] photoName];
    6. [self.delegate userDidScroll:self toPhotoAtIndex:[self.pageViewController.viewControllers.firstObject photoIndex]];
    7. }
    8. - (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(FRPPhotoViewController *)viewController{
    9. return [self photoViewControllerForIndex:viewController.photoIndex - 1];
    10. }
    11. - (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerAfterViewController:(FRPPhotoViewController *)viewController {
    12. return [self photoViewControllerForIndex:viewController.photoIndex + 1];
    13. }

    虽然这些方法没有技术上的reactive,却体现出一定意义上的实用性。我很佩服这种在特殊类型的视图控制器上的抽像,干得漂亮,Apple!

    我们的视图控制器创建方法,类似下面这样:

    1. - (FRPPhotoViewController *)photoViewControllerForIndex:(NSInteger)index{
    2. FRPPhotoModel *photoModel = self.photoModelArray[index];
    3. return photoViewController;
    4. }
    5. //Index was out of bounds, return nil
    6. return nil;
    7. }

    它基本上创建比配置了一个我们将要使用的UIViewController的子视图控制器FRPPhotoViewController。下面是他的头文件:

    这个视图控制器非常简单:显示一个photoModel下的高清图片,并提示photoImporter(单例对象)下载这个图片。它是如此简单,我现在就告诉你它的全部实现。

    1. //Model
    2. #import "FRPPhotoModel.h"
    3. //Utilities
    4. #import "FRPPhotoImporter.h"
    5. #import <SVProgressHUD.h>
    6. @interface FRPPhotoViewController ()
    7. //Private assignment
    8. @property (nonatomic, assign) NSInteger photoIndex;
    9. @property (nonatomic, strong) FRPPhotoModel *photoModel;
    10. //Private properties
    11. @property (nonatomic, weak) UIImageView * imageView;
    12. @end
    13. @implementation FRPPhotoViewController
    14. - (instancetype)initWithPhotoModel:(FRPPhotoModel *)photoModel index:(NSInteger)photoIndex{
    15. self = [self init];
    16. if (!self) return nil;
    17. self.photoModel = photoModel;
    18. self.photoIndex = photoIndex;
    19. return self;
    20. }
    21. - (void)viewDidLoad{
    22. [super viewDidLoad];
    23. //Configure self's view
    24. self.view.backGroundColor = [UIColor blackColor];
    25. //Configure subViews
    26. UIImageView *imageView = [[UIImageView alloc] initWithFrame:self.view.bounds];
    27. RAC(imageView, image) = [RACObserve(self.photoModel, fullsizeData) map:^id (id value){
    28. return [UIImage imageWithData:value];
    29. }];
    30. imageView.contentMode = UIViewContentModeScaleAspectFit;
    31. [self.view addSubView:imageView];
    32. self.imageView = imageView;
    33. }
    34. [SVProgressHUD show];
    35. //Fetch data
    36. [[FRPPhotoImporter fetchPhotoDetails:self.photoModel]
    37. subscribeError:^(NSError *error){
    38. [SVProgressHUD showErrorWithStatus:@"Error"];
    39. }
    40. completed:^{
    41. [SVProgressHUD dismiss];
    42. }];
    43. }
    44. @end

    这个实现里面另一个有趣的部分在viewWillAppear:里:

    1. [SVProgressHUD show];
    2. //Fetch data
    3. [[FRPPhotoImporter fetchPhotoDetails:self.photoModel]
    4. subscribeError:^(NSError * error){
    5. [SVProgressHUD showErrorWithStatus:@"Error"];
    6. }
    7. completed:^{
    8. [SVProgressHUD dismiss];
    9. }];

    没有收到错误或者完成信息之前,我们必须给用户展示网络请求的状态。你看,500px的受欢迎的照片的API接口只返回了一个照片的大概信息,但我们需要这个照片更详细的信息,所以我们必须调用第二个API接口来获取每一个照片的详细信息(包括全尺寸照片的URL)。

    1. + (NSURLRequest *)photoURLRequest:(FRPPhotoModel *)photoModel{
    2. return [AppDelegate.apiHelper urlRequestForPhotoID:photoModel.identifier.integerValue];
    3. }

    我们还没有实现fetchPhotoDetails:方法,所以现在我们回到FRPPhotoImporter中,在头文件中定义这个方法,在实现文件中实现它。

    这种方法跟前面我们看到的importPhotos方法模式一样,我们的downloadFullsizedImageForPhotoModel:方法跟downloadThumbnailForPhotoModel:方法也是一样的。除了这两者之外,还有什么重要的抽象方法呢?让我们来完成我们的缩略图方法。

    1. + (void)downloadThumbnailForPhotoModel:(FRPPhotoModel *)photoModel {
    2. [self download:photoModel.thumbnailURL withCompletion:^(NSData *data){
    3. photoModel.thumbnailData = data;
    4. }];
    5. }
    6. + (void)downloadFullsizedImageForPhotoModel:(FRPPhotoModel *)photoModel {
    7. [self download:photoModel.fullsizedURL withCompletion:^(NSData * data){
    8. photoModel.fullsizedData = data;
    9. }];
    10. }
    11. + (void)downloadFullsizedImageForPhotoModel:(FRPPhotoModel *)photoModel {
    12. [self download:photoModel.fullsizedURL withCompletion:^(NSData *data){
    13. photoModel.fullsizedData = data;
    14. }];
    15. }
    16. + (void)download:(NSString *)urlString withCompletion:(void(^)(NSData * data))completion{
    17. NSAssert(urlString, @"URL must not be nil" );
    18. NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:urlString]];
    19. [NSURLConnnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError){
    20. if (completion){
    21. completion(data);
    22. }
    23. }];

    我曾经与这样一位客户工作过,他认为如果你某行一样的代码重复写两次,这代码就应该得到某种程度的抽象。虽然我认为这有点偏激,但我喜欢这种态度。

    好了。我们现在可以运行这个应用,点击一个图片去查看它的高清图片。我们也可以向前或者向后滑动来查看前一个或后一个高清图片。非常棒!