iOSのドロップダウン・リフレッシュSVpullToRefresh


ドロップダウン・リフレッシュはジェスチャーでユーザー・インタフェースをリフレッシュする機能で、Twitterで特許が申請されているが、広範なApp開発者が自分のアプリケーションにこの機能を追加することを阻止することはできない.アップルはiOS 6のsdkにUIrefreshControlを追加し、システムレベルのドロップダウン・リフレッシュを実現した.しかしUIrefreshControlはUITableViewControllerにバインドされているため柔軟性は高くない.
ネット検索のドロップダウン・リフレッシュが実現すれば、議論が最も多かったのはEGOTAbleView PullRefreshだろう.これは比較的歴史的なオープンソース・ライブラリであり、githubが最近提出したのは2年前のことだ.EGOTAbleView PullRefreshはドロップダウン・リフレッシュ機能をよく実現していますが、arc、blockなどのOCの新しい特性がサポートされていないため、ドロップダウン・リフレッシュを追加するには多くのコードを書く必要があります.
そこでEGOTAbleView PullRefreshの代替者が出てきました.SVpullToRefreshです.arcをサポートし、blockをサポートし、1行でドロップダウン・リフレッシュとアップロードを実現できるように簡潔です.
SVpullToRefreshのドロップダウン・リフレッシュの使い方はかなり簡単です.
1、ドロップダウン・リフレッシュ・コントロールを上部に置く
[tableView addPullToRefreshWithActionHandler:^{
    // prepend data to dataSource, insert cells at top of table view
    // call [tableView.pullToRefreshView stopAnimating] when done
}];

2、ドロップダウン・リフレッシュ・コントロールを下に置く
[tableView addPullToRefreshWithActionHandler:^{
    // prepend data to dataSource, insert cells at top of table view
    // call [tableView.pullToRefreshView stopAnimating] when done
} position:SVPullToRefreshPositionBottom];

3、プログラム自動呼び出しプルダウンリフレッシュ
[tableView triggerPullToRefresh];

4、一時的にドロップダウン・リフレッシュを無効にする
tableView.showsPullToRefresh = NO;
SVpullToRefreshのUIサポートカスタム
ドロップダウン・リフレッシュに対応するviewはpullToRefreshViewと呼ばれ、次のプロパティとメソッドで表示を変更します.
@property (nonatomic, strong) UIColor *arrowColor;
@property (nonatomic, strong) UIColor *textColor;
@property (nonatomic, readwrite) UIActivityIndicatorViewStyle activityIndicatorViewStyle;

- (void)setTitle:(NSString *)title forState:(SVPullToRefreshState)state;
- (void)setSubtitle:(NSString *)subtitle forState:(SVPullToRefreshState)state;
- (void)setCustomView:(UIView *)view forState:(SVPullToRefreshState)state;
簡単な使い方です.たとえば、次の行のコードでドロップダウン矢印の色が変更されます.
tableView.pullToRefreshView.arrowColor = [UIColor whiteColor];

上ではプルダウンリフレッシュの基本的な使い方を紹介していますが、プルダウンロードの使い方も悪くないので、もう紹介しません.公式資料を参考にすることができます.https://github.com/samvermette/SVPullToRefresh
-------------------------------------------------------------------------------------------------------------------------------------------------------
以下、SVpullToRefreshの背後にある原理を検討します.SVpullToRefreshはインタフェースをこんなに簡単に設計することができます.きっといくつかの優れたところがあります.私たちは後でコントロールを設計するときにもいくつかの啓発を得ることができるかもしれません.
SVpullToRefreshのライブラリは実は2つのクラスで、それぞれドロップダウンリフレッシュとアップロードに対応しています.両者の原理の差は多くないので、ドロップダウンリフレッシュの実現を見るだけでいいです.
@interface UIScrollView (SVPullToRefresh)

typedef NS_ENUM(NSUInteger, SVPullToRefreshPosition) {
    SVPullToRefreshPositionTop = 0,
    SVPullToRefreshPositionBottom,
};

- (void)addPullToRefreshWithActionHandler:(void (^)(void))actionHandler;
- (void)addPullToRefreshWithActionHandler:(void (^)(void))actionHandler position:(SVPullToRefreshPosition)position;
- (void)triggerPullToRefresh;

@property (nonatomic, strong, readonly) SVPullToRefreshView *pullToRefreshView;
@property (nonatomic, assign) BOOL showsPullToRefresh;

@end
@interface SVPullToRefreshView : UIView

@property (nonatomic, strong) UIColor *arrowColor;
@property (nonatomic, strong) UIColor *textColor;
@property (nonatomic, strong, readonly) UILabel *titleLabel;
@property (nonatomic, strong, readonly) UILabel *subtitleLabel;
@property (nonatomic, strong, readwrite) UIColor *activityIndicatorViewColor NS_AVAILABLE_IOS(5_0);
@property (nonatomic, readwrite) UIActivityIndicatorViewStyle activityIndicatorViewStyle;

@property (nonatomic, readonly) SVPullToRefreshState state;
@property (nonatomic, readonly) SVPullToRefreshPosition position;

- (void)setTitle:(NSString *)title forState:(SVPullToRefreshState)state;
- (void)setSubtitle:(NSString *)subtitle forState:(SVPullToRefreshState)state;
- (void)setCustomView:(UIView *)view forState:(SVPullToRefreshState)state;

- (void)startAnimating;
- (void)stopAnimating;

// deprecated; use setSubtitle:forState: instead
@property (nonatomic, strong, readonly) UILabel *dateLabel DEPRECATED_ATTRIBUTE;
@property (nonatomic, strong) NSDate *lastUpdatedDate DEPRECATED_ATTRIBUTE;
@property (nonatomic, strong) NSDateFormatter *dateFormatter DEPRECATED_ATTRIBUTE;

// deprecated; use [self.scrollView triggerPullToRefresh] instead
- (void)triggerRefresh DEPRECATED_ATTRIBUTE;

@end

SVpullToRefreshの実装には多くのOC Runtimeの特性が用いられている.以前EGOTAbleViewPullRefreshを使っていた時、私たちは
現在のViewControllerにEGOrefreshTable HeaderViewのメンバーを追加し、対応するprotocolを実装することで、ViewControllerが肥大化します.一方、SVpullToRefreshは、UIscrollViewのCategoryを追加することによって、プルダウンが必要なscrollviewにpullToRefreshViewの属性を追加し、使用者は明示的に作成する必要がなく、pullToRefreshView状態に関する論理にも関心がなく、リフレッシュ終了後のいくつかの操作を完了するためにactionHandlerを提供する必要があります.
ただしcategoryが宣言するpropertyは自動@synthesizeではないので、getterメソッドとsetterメソッドを手動で実装する必要があります.runtime.h中objc_getAssociatedObject/objc_setAssociatedObjectは、関連オブジェクトにアクセスして生成し、生成プロパティをシミュレートします.既存のクラスを変更せずにプロパティを追加するには、これは良い方法です.
- (void)setPullToRefreshView:(SVPullToRefreshView *)pullToRefreshView {
    [self willChangeValueForKey:@"SVPullToRefreshView"];
    objc_setAssociatedObject(self, &UIScrollViewPullToRefreshView,
                             pullToRefreshView,
                             OBJC_ASSOCIATION_ASSIGN);
    [self didChangeValueForKey:@"SVPullToRefreshView"];
}

- (SVPullToRefreshView *)pullToRefreshView {
    return objc_getAssociatedObject(self, &UIScrollViewPullToRefreshView);
}

動的に属性を追加するだけでなく、SVpullToRefreshはKVOを使用して自身の属性の変化を観察し、対応するUI調整を行う(EGOTAbleView PullRefreshを使用すると、これらのコードは呼び出し元によって実現される.
UIScrollViewDelegate
).UIscroollViewは、スライド時にcontentOffset、contentSize、frameが監視対象となります.
- (void)setShowsPullToRefresh:(BOOL)showsPullToRefresh {
    self.pullToRefreshView.hidden = !showsPullToRefresh;
    
    if(!showsPullToRefresh) {
        if (self.pullToRefreshView.isObserving) {
            [self removeObserver:self.pullToRefreshView forKeyPath:@"contentOffset"];
            [self removeObserver:self.pullToRefreshView forKeyPath:@"contentSize"];
            [self removeObserver:self.pullToRefreshView forKeyPath:@"frame"];
            [self.pullToRefreshView resetScrollViewContentInset];
            self.pullToRefreshView.isObserving = NO;
        }
    }
    else {
        if (!self.pullToRefreshView.isObserving) {
            [self addObserver:self.pullToRefreshView forKeyPath:@"contentOffset" options:NSKeyValueObservingOptionNew context:nil];
            [self addObserver:self.pullToRefreshView forKeyPath:@"contentSize" options:NSKeyValueObservingOptionNew context:nil];
            [self addObserver:self.pullToRefreshView forKeyPath:@"frame" options:NSKeyValueObservingOptionNew context:nil];
            self.pullToRefreshView.isObserving = YES;
            
            CGFloat yOrigin = 0;
            switch (self.pullToRefreshView.position) {
                case SVPullToRefreshPositionTop:
                    yOrigin = -SVPullToRefreshViewHeight;
                    break;
                case SVPullToRefreshPositionBottom:
                    yOrigin = self.contentSize.height;
                    break;
            }
            
            self.pullToRefreshView.frame = CGRectMake(0, yOrigin, self.bounds.size.width, SVPullToRefreshViewHeight);
        }
    }
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
    if([keyPath isEqualToString:@"contentOffset"])
        [self scrollViewDidScroll:[[change valueForKey:NSKeyValueChangeNewKey] CGPointValue]];
    else if([keyPath isEqualToString:@"contentSize"]) {
        [self layoutSubviews];
        
        CGFloat yOrigin;
        switch (self.position) {
            case SVPullToRefreshPositionTop:
                yOrigin = -SVPullToRefreshViewHeight;
                break;
            case SVPullToRefreshPositionBottom:
                yOrigin = MAX(self.scrollView.contentSize.height, self.scrollView.bounds.size.height);
                break;
        }
        self.frame = CGRectMake(0, yOrigin, self.bounds.size.width, SVPullToRefreshViewHeight);
    }
    else if([keyPath isEqualToString:@"frame"])
        [self layoutSubviews];

}

まとめ:SVpullToRefreshはすべてのロジックを自分で実現し、外部にはBlockインタフェースが1つしか提供されず、呼び出し者を便利にする.原理的にはEGOTAbleView PullRefreshと差は少ないが,Runtime特性とKVOの運用により,従来のクライアントで実現されていた論理を自身に置いて実現し,最終的には簡単なインタフェースを提示する.