添加FunctionalReactivePixels

2018-08-01 16:17 更新

當我們弄好了一個簡單的畫廊之后,就會想要在畫廊中查看高清圖片了。當用戶點擊畫廊中的某一個單元格時,我們創(chuàng)建一個新的視圖控制器并將其推入到導航堆棧中。

- (void)collectionView:(UICollectionView *)collectionView
    didSelectItemAtIndexPath:(NSIndexPath *)indexPath{
    FRPFullSizePhotoViewController * viewController = [[FRPFullSizePhotoViewController alloc] initWithPhotoModels:self.photosArray currentPhotoIndex:indexPath.item];

    viewController.delegate = self;
    [self.navigationController pushViewController:viewController animated:YES];

}

這個方法沒有任何特殊的,只是些一般的OC方法。當然別忘了在當前實現(xiàn)文件里加載視圖控制器(FRPFullSizePhotoViewControler)的頭文件.現(xiàn)在讓我們來創(chuàng)建這個視圖控制器(FRPFullSizePhotoViewControler).

創(chuàng)建一個UIViewController的子類FRPFullSizePhotoViewControler,這不會是一個特別的‘Reactive’的視圖控制器,實際上大部分只是UIPageViewController子視圖控制器的模版。

@class FRPFullSizePhotoViewController;

@protocol FRPFullSizePhotoViewControllerDelegate <NSOject>
- (void)userDidScroll:(FRPFullSizePhotoViewController *)viewController toPhotoAtIndex:(NSInteger)index;

@end

@interface FRPFullSizePhotoViewController : UIViewController

- (instancetype)initWithPhotoModels:(NSArray *)photoModelArray currentPhotoIndex:(NSInteger)photoIndex;

@property (nonatomic , readonly) NSArray *photoModelArray;
@property (nonatomic, weak) id<FRPFullSizePhotoViewControllerDelegate> delegate;

@end

回到畫廊視圖控制器實現(xiàn)必要的代理方法:

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

當我們滑到一個新的圖像去查看其高清圖片時,這個方法將更新collectionView滑動的位置。這樣一來,當用戶查看完高清圖回到這個界面的時候,高清圖所對應(yīng)的縮略圖將會顯示在界面上,方便用戶獲知自己瀏覽的位置以及繼續(xù)往下瀏覽。

#import這些必要的數(shù)據(jù)模型的頭文件并追加一下兩個私有屬性:

@interface FRPFullSizePhotoViewController () <UIPageViewControllerDataSource, UIPageViewControllerDelegate>
//Private assignment
@property (nonatomic, strong) NSArray *photoModeArray;
//Private properties
@property (nonatomic, strong) UIPageViewController *pageViewController;
@end

photoModelArray是共有的只讀屬性,但是內(nèi)部可讀寫。第二個屬性是我們的子視圖控制器。我們這樣來初始化:

- (instancetype)initWithPhotoModels:(NSArray *)photoModelArray currentPhotoIndex:(NSInteger)photoIndex{
    self = [self init];
    if (!self) return nil;

    //Initialized, read-only properties
    self.photoModelArray = photoModelArray;

    //Configure self
    self.title = [self.photoModelArray[photoIndex] photoName];

    //ViewControllers
    self.pageViewController = [UIPageViewController alloc]
                                    initWithTransitionStyle:UIPageViewControlerTransitionStyleScroll
                                    navigationOrientation:UIPageViewControllerNavigationOrientationHorizontal
                                    options:@{ UIPageViewControllerInterPageSpacingKey: @(30)};
    self.pageViewController.dataSource = self;
    self.pageViewController.delegate = self;
    [self addchildViewController:self.pageViewController];

    [self.pageViewController setViewController:@[[self photoViewControllerForIndex:photoIndex]]
                            direction:UIPageViewControllerNavigationDirectionForward
                            animated:NO completion:nil ];

    return self;
}

賦值屬性、設(shè)置標題、配置我們的pageViewController,一切都非常無聊,我們的viewDidLoad方法也同樣簡單。

- (void)viewDidLoad{
    [super viewDidLoad];

    self.view,backGroundColor = [UIColor blackColor];

    self.pageViewController.view.frame = self.view.bounds;

    [self.view addSubView:self.pageViewController.view];
}

我要指出的是,簡便起見,在我的應(yīng)用里我禁用了橫向展示,因為這不是一本關(guān)于autoresizingMask或者autoLayout的書。你可以通過Eria Sadun的書了解更多關(guān)于autoLayout方面的細節(jié)。

下面我們來了解一下UIPageViewController的數(shù)據(jù)源協(xié)議和代理協(xié)議。

- (void)pageViewController:(UIPageViewController *)pageViewController
    didFinishAnimating: (BOOL)finished
    previousViewControllers:(NSArray *)previousViewControllers
    transitionCompleted:(BOOL)completed{
        self.title = [[self.pageViewController.viewControllers.firstObject photoModel] photoName];
        [self.delegate userDidScroll:self toPhotoAtIndex:[self.pageViewController.viewControllers.firstObject photoIndex]];
    }

- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(FRPPhotoViewController *)viewController{
    return [self photoViewControllerForIndex:viewController.photoIndex - 1];
}

- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerAfterViewController:(FRPPhotoViewController *)viewController {
    return [self photoViewControllerForIndex:viewController.photoIndex + 1];
}

雖然這些方法沒有技術(shù)上的reactive,卻體現(xiàn)出一定意義上的實用性。我很佩服這種在特殊類型的視圖控制器上的抽像,干得漂亮,Apple!

我們的視圖控制器創(chuàng)建方法,類似下面這樣:

- (FRPPhotoViewController *)photoViewControllerForIndex:(NSInteger)index{
    if (index >= 0 && index < self.photoModelArray.count){
        FRPPhotoModel *photoModel = self.photoModelArray[index];

        FRPPhotoViewController *photoViewController = [[FRPPhotoViewController alloc] initWithPhotoModel:photoModel index:index];

        return photoViewController;
    }

    //Index was out of bounds, return nil
    return nil;
 }

它基本上創(chuàng)建比配置了一個我們將要使用的UIViewController的子視圖控制器FRPPhotoViewController。下面是他的頭文件:

@class FRPPhotoModel;

@interface FRPPhotoViewController : UIViewController
- (instancetype)initWithPhotoModel:(FRPPhotoModel *)photoModel index:(NSInteger)photoIndex;

@property (nonatomic, readonly) NSInteger photoIndex;
@property (nonatomic, readonly) FRPPhotoModel * photoModel;

@end

這個視圖控制器非常簡單:顯示一個photoModel下的高清圖片,并提示photoImporter(單例對象)下載這個圖片。它是如此簡單,我現(xiàn)在就告訴你它的全部實現(xiàn)。

//Model
#import "FRPPhotoModel.h"

//Utilities
#import "FRPPhotoImporter.h"
#import <SVProgressHUD.h>

@interface FRPPhotoViewController ()
//Private assignment
@property (nonatomic, assign) NSInteger photoIndex;
@property (nonatomic, strong) FRPPhotoModel *photoModel;

//Private properties
@property (nonatomic, weak) UIImageView * imageView;

@end

@implementation FRPPhotoViewController

- (instancetype)initWithPhotoModel:(FRPPhotoModel *)photoModel index:(NSInteger)photoIndex{
    self = [self init];
    if (!self) return nil;

    self.photoModel = photoModel;
    self.photoIndex = photoIndex;

    return self;
}

- (void)viewDidLoad{
    [super viewDidLoad];

    //Configure self's view
    self.view.backGroundColor = [UIColor blackColor];

    //Configure subViews
    UIImageView *imageView = [[UIImageView alloc] initWithFrame:self.view.bounds];

    RAC(imageView, image) = [RACObserve(self.photoModel, fullsizeData) map:^id (id value){
                                        return [UIImage imageWithData:value];
                                    }];

    imageView.contentMode = UIViewContentModeScaleAspectFit;
    [self.view addSubView:imageView];
    self.imageView = imageView;
}

- (void)viewWillAppear:(BOOL)animated{
    [super viewWillAppear:animated];
    [SVProgressHUD show];

    //Fetch data
    [[FRPPhotoImporter fetchPhotoDetails:self.photoModel]
            subscribeError:^(NSError *error){
                [SVProgressHUD showErrorWithStatus:@"Error"];
            }
            completed:^{
                [SVProgressHUD dismiss];
            }];
}

@end

就像我們的collectionViewCell中那樣,我們將UIImageView的image屬性和數(shù)據(jù)模型的某個屬性映射后的值綁定,所不同的是ViewController不需要考慮復用,所以我們不必計較怎么取消imageView的訂閱---當imageView對象解除分配的時候,訂閱將會被取消。

這個實現(xiàn)里面另一個有趣的部分在viewWillAppear:里:

[SVProgressHUD show];
//Fetch data
[[FRPPhotoImporter fetchPhotoDetails:self.photoModel]
        subscribeError:^(NSError * error){
            [SVProgressHUD showErrorWithStatus:@"Error"];
        }
        completed:^{
            [SVProgressHUD dismiss];
        }];

沒有收到錯誤或者完成信息之前,我們必須給用戶展示網(wǎng)絡(luò)請求的狀態(tài)。你看,500px的受歡迎的照片的API接口只返回了一個照片的大概信息,但我們需要這個照片更詳細的信息,所以我們必須調(diào)用第二個API接口來獲取每一個照片的詳細信息(包括全尺寸照片的URL)。

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

我們還沒有實現(xiàn)fetchPhotoDetails:方法,所以現(xiàn)在我們回到FRPPhotoImporter中,在頭文件中定義這個方法,在實現(xiàn)文件中實現(xiàn)它。

+ (RACReplaySubject *)fetchPhotoDetails:(FRPPhotoModel *)photoModel {
    RACReplaySubject * subject = [RACReplaySubject subject];
    NSURLRequest *request = [self photoURLRequest:photoModel];

    [NSURLConnection sendAsynchronousRequest:request
        queue:[NSOperationQueue mainQueue]
        completionHandler:^ (NSURLResponse *response, NSData * data, NSError *connectionError){
            if(data){
                id results = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil][ @"photo" ];

                [self configurePhotoModel:photoModel withDictionary:results];
                [self downloadFullsizedImageForPhotoModel:photoModel];

                [subject sendNext:photoModel];
                [subject sendCompleted];
            }
            else{
                [subject sendError:connectionError];
            }
        }];

    return subject;
}

這種方法跟前面我們看到的importPhotos方法模式一樣,我們的downloadFullsizedImageForPhotoModel:方法跟downloadThumbnailForPhotoModel:方法也是一樣的。除了這兩者之外,還有什么重要的抽象方法呢?讓我們來完成我們的縮略圖方法。

+ (void)downloadThumbnailForPhotoModel:(FRPPhotoModel *)photoModel {
    [self download:photoModel.thumbnailURL withCompletion:^(NSData *data){
        photoModel.thumbnailData = data;
    }];
}

+ (void)downloadFullsizedImageForPhotoModel:(FRPPhotoModel *)photoModel {
    [self download:photoModel.fullsizedURL withCompletion:^(NSData * data){
        photoModel.fullsizedData = data;
    }];
}

+ (void)downloadFullsizedImageForPhotoModel:(FRPPhotoModel *)photoModel {
    [self download:photoModel.fullsizedURL withCompletion:^(NSData *data){
        photoModel.fullsizedData = data;
    }];
}

+ (void)download:(NSString *)urlString withCompletion:(void(^)(NSData * data))completion{
    NSAssert(urlString, @"URL must not be nil" );

    NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:urlString]];
    [NSURLConnnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError){
        if (completion){
            completion(data);
        }
    }];
}

我曾經(jīng)與這樣一位客戶工作過,他認為如果你某行一樣的代碼重復寫兩次,這代碼就應(yīng)該得到某種程度的抽象。雖然我認為這有點偏激,但我喜歡這種態(tài)度。

好了。我們現(xiàn)在可以運行這個應(yīng)用,點擊一個圖片去查看它的高清圖片。我們也可以向前或者向后滑動來查看前一個或后一個高清圖片。非常棒!

fullsize_gallerypictures


以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號