添加FunctionalReactivePixels
一个简单的画廊弄好了,但是我们是不是想看一下高清图呢?当用户点击画廊中的某一个单元格时,我们创建一个新的视图控制器并将其推入到导航堆栈中。
1. - (void)collectionView:(UICollectionView *)collectionView
2. didSelectItemAtIndexPath:(NSIndexPath *)indexPath{
3. FRPFullSizePhotoViewController * viewController = [[FRPFullSizePhotoViewController alloc] initWithPhotoModels:self.photosArray currentPhotoIndex:indexPath.item];
5. viewController.delegate = self;
6. [self.navigationController pushViewController:viewController animated:YES];
8. }
这个方法没有任何特殊的,只是些一般的OC方法。当然别忘了在当前实现文件里加载视图控制器(FRPFullSizePhotoViewControler)的头文件.现在让我们来创建这个视图控制器(FRPFullSizePhotoViewControler).
创建一个UIViewController的子类FRPFullSizePhotoViewControler,这不会是一个特别的‘Reactive’的视图控制器,实际上大部分只是UIPageViewController子视图控制器的模版。
1. @class FRPFullSizePhotoViewController;
3. @protocol FRPFullSizePhotoViewControllerDelegate <NSOject>
4. - (void)userDidScroll:(FRPFullSizePhotoViewController *)viewController toPhotoAtIndex:(NSInteger)index;
6. @end
8. @interface FRPFullSizePhotoViewController : UIViewController
10. - (instancetype)initWithPhotoModels:(NSArray *)photoModelArray currentPhotoIndex:(NSInteger)photoIndex;
12. @property (nonatomic , readonly) NSArray *photoModelArray;
13. @property (nonatomic, weak) id<FRPFullSizePhotoViewControllerDelegate> delegate;
15. @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. - (instancetype)initWithPhotoModels:(NSArray *)photoModelArray currentPhotoIndex:(NSInteger)photoIndex{
2. self = [self init];
3. if (!self) return nil;
5. //Initialized, read-only properties
6. self.photoModelArray = photoModelArray;
8. //Configure self
9. self.title = [self.photoModelArray[photoIndex] photoName];
11. //ViewControllers
12. self.pageViewController = [UIPageViewController alloc]
13. initWithTransitionStyle:UIPageViewControlerTransitionStyleScroll
14. navigationOrientation:UIPageViewControllerNavigationOrientationHorizontal
15. options:@{ UIPageViewControllerInterPageSpacingKey: @(30)};
16. self.pageViewController.dataSource = self;
17. self.pageViewController.delegate = self;
18. [self addchildViewController:self.pageViewController];
20. [self.pageViewController setViewController:@[[self photoViewControllerForIndex:photoIndex]]
21. direction:UIPageViewControllerNavigationDirectionForward
22. animated:NO completion:nil ];
24. return self;
25. }
赋值属性、设置标题、配置我们的pageViewController,一切都非常无聊,我们的viewDidLoad方法也同样简单。
1. - (void)viewDidLoad{
2. [super viewDidLoad];
4. self.view,backGroundColor = [UIColor blackColor];
6. self.pageViewController.view.frame = self.view.bounds;
8. [self.view addSubView:self.pageViewController.view];
9. }
我要指出的是,简便起见,在我的应用里我禁用了横向展示,因为这不是一本关于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. }
9. - (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(FRPPhotoViewController *)viewController{
10. return [self photoViewControllerForIndex:viewController.photoIndex - 1];
11. }
13. - (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerAfterViewController:(FRPPhotoViewController *)viewController {
14. return [self photoViewControllerForIndex:viewController.photoIndex + 1];
15. }
虽然这些方法没有技术上的reactive,却体现出一定意义上的实用性。我很佩服这种在特殊类型的视图控制器上的抽像,干得漂亮,Apple!
我们的视图控制器创建方法,类似下面这样:
1. - (FRPPhotoViewController *)photoViewControllerForIndex:(NSInteger)index{
2. if (index >= 0 && index < self.photoModelArray.count){
3. FRPPhotoModel *photoModel = self.photoModelArray[index];
5. FRPPhotoViewController *photoViewController = [[FRPPhotoViewController alloc] initWithPhotoModel:photoModel index:index];
7. return photoViewController;
8. }
10. //Index was out of bounds, return nil
11. return nil;
12. }
它基本上创建比配置了一个我们将要使用的UIViewController的子视图控制器FRPPhotoViewController。下面是他的头文件:
1. @class FRPPhotoModel;
3. @interface FRPPhotoViewController : UIViewController
4. - (instancetype)initWithPhotoModel:(FRPPhotoModel *)photoModel index:(NSInteger)photoIndex;
6. @property (nonatomic, readonly) NSInteger photoIndex;
7. @property (nonatomic, readonly) FRPPhotoModel * photoModel;
9. @end
这个视图控制器非常简单:显示一个photoModel下的高清图片,并提示photoImporter(单例对象)下载这个图片。它是如此简单,我现在就告诉你它的全部实现。
1. //Model
2. #import "FRPPhotoModel.h"
4. //Utilities
5. #import "FRPPhotoImporter.h"
6. #import <SVProgressHUD.h>
8. @interface FRPPhotoViewController ()
9. //Private assignment
10. @property (nonatomic, assign) NSInteger photoIndex;
11. @property (nonatomic, strong) FRPPhotoModel *photoModel;
13. //Private properties
14. @property (nonatomic, weak) UIImageView * imageView;
16. @end
18. @implementation FRPPhotoViewController
20. - (instancetype)initWithPhotoModel:(FRPPhotoModel *)photoModel index:(NSInteger)photoIndex{
21. self = [self init];
22. if (!self) return nil;
24. self.photoModel = photoModel;
25. self.photoIndex = photoIndex;
27. return self;
28. }
30. - (void)viewDidLoad{
31. [super viewDidLoad];
33. //Configure self's view
34. self.view.backGroundColor = [UIColor blackColor];
36. //Configure subViews
37. UIImageView *imageView = [[UIImageView alloc] initWithFrame:self.view.bounds];
39. RAC(imageView, image) = [RACObserve(self.photoModel, fullsizeData) map:^id (id value){
40. return [UIImage imageWithData:value];
41. }];
43. imageView.contentMode = UIViewContentModeScaleAspectFit;
44. [self.view addSubView:imageView];
45. self.imageView = imageView;
46. }
48. - (void)viewWillAppear:(BOOL)animated{
49. [super viewWillAppear:animated];
50. [SVProgressHUD show];
52. //Fetch data
53. [[FRPPhotoImporter fetchPhotoDetails:self.photoModel]
54. subscribeError:^(NSError *error){
55. [SVProgressHUD showErrorWithStatus:@"Error"];
56. }
57. completed:^{
58. [SVProgressHUD dismiss];
59. }];
60. }
62. @end
就像我们的collectionViewCell中那样,我们将UIImageView的image属性和数据模型的某个属性映射后的值绑定,所不同的是ViewController不需要考虑复用,所以我们不必计较怎么取消imageView的订阅—-当imageView对象解除分配的时候,订阅将会被取消。
这个实现里面另一个有趣的部分在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中,在头文件中定义这个方法,在实现文件中实现它。
1. + (RACReplaySubject *)fetchPhotoDetails:(FRPPhotoModel *)photoModel {
2. RACReplaySubject * subject = [RACReplaySubject subject];
3. NSURLRequest *request = [self photoURLRequest:photoModel];
5. [NSURLConnection sendAsynchronousRequest:request
6. queue:[NSOperationQueue mainQueue]
7. completionHandler:^ (NSURLResponse *response, NSData * data, NSError *connectionError){
8. if(data){
9. id results = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil][ @"photo" ];
11. [self configurePhotoModel:photoModel withDictionary:results];
12. [self downloadFullsizedImageForPhotoModel:photoModel];
14. [subject sendNext:photoModel];
15. [subject sendCompleted];
16. }
17. else{
18. [subject sendError:connectionError];
19. }
20. }];
22. return subject;
23. }
这种方法跟前面我们看到的importPhotos方法模式一样,我们的downloadFullsizedImageForPhotoModel:方法跟downloadThumbnailForPhotoModel:方法也是一样的。除了这两者之外,还有什么重要的抽象方法呢?让我们来完成我们的缩略图方法。
1. + (void)downloadThumbnailForPhotoModel:(FRPPhotoModel *)photoModel {
2. [self download:photoModel.thumbnailURL withCompletion:^(NSData *data){
3. photoModel.thumbnailData = data;
4. }];
5. }
7. + (void)downloadFullsizedImageForPhotoModel:(FRPPhotoModel *)photoModel {
8. [self download:photoModel.fullsizedURL withCompletion:^(NSData * data){
9. photoModel.fullsizedData = data;
10. }];
11. }
13. + (void)downloadFullsizedImageForPhotoModel:(FRPPhotoModel *)photoModel {
14. [self download:photoModel.fullsizedURL withCompletion:^(NSData *data){
15. photoModel.fullsizedData = data;
16. }];
17. }
19. + (void)download:(NSString *)urlString withCompletion:(void(^)(NSData * data))completion{
20. NSAssert(urlString, @"URL must not be nil" );
22. NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:urlString]];
23. [NSURLConnnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError){
24. if (completion){
25. completion(data);
26. }
27. }];
28. }
我曾经与这样一位客户工作过,他认为如果你某行一样的代码重复写两次,这代码就应该得到某种程度的抽象。虽然我认为这有点偏激,但我喜欢这种态度。
好了。我们现在可以运行这个应用,点击一个图片去查看它的高清图片。我们也可以向前或者向后滑动来查看前一个或后一个高清图片。非常棒!

