Commit 31ea2f290e79e4663123e321285d629d93ff618a
1 parent
7b9f1a72
提交预览库放模块中
Showing
75 changed files
with
6323 additions
and
8 deletions
CNLiveOrganizationValidationModule.podspec
| @@ -77,8 +77,8 @@ Pod::Spec.new do |s| | @@ -77,8 +77,8 @@ Pod::Spec.new do |s| | ||
| 77 | ### 所有github三方管理库(非频繁更新) | 77 | ### 所有github三方管理库(非频繁更新) |
| 78 | ######################################## | 78 | ######################################## |
| 79 | s.dependency 'CNLiveTripartiteManagement' | 79 | s.dependency 'CNLiveTripartiteManagement' |
| 80 | - #相册展示 | ||
| 81 | - s.dependency 'YBImageBrowser' | 80 | +# #相册展示 |
| 81 | +# s.dependency 'YBImageBrowser' | ||
| 82 | 82 | ||
| 83 | 83 | ||
| 84 | 84 |
CNLiveOrganizationValidationModule/Assets/YBImageBrowser.bundle/ybib_loading@2x.png
0 → 100644
2.44 KB
CNLiveOrganizationValidationModule/Assets/YBImageBrowser.bundle/ybib_loading@3x.png
0 → 100644
3.97 KB
CNLiveOrganizationValidationModule/Assets/YBImageBrowser.bundle/ybib_more@2x.png
0 → 100644
844 Bytes
CNLiveOrganizationValidationModule/Assets/YBImageBrowser.bundle/ybib_more@3x.png
0 → 100644
1.13 KB
CNLiveOrganizationValidationModule/Assets/YBImageBrowser.bundle/ybib_save@2x.png
0 → 100644
862 Bytes
CNLiveOrganizationValidationModule/Assets/YBImageBrowser.bundle/ybib_save@3x.png
0 → 100644
960 Bytes
CNLiveOrganizationValidationModule/Classes/CNLiveOrganizationValidationModule/Controller/CNLiveBOraganizationDetailController.m
| @@ -20,8 +20,8 @@ | @@ -20,8 +20,8 @@ | ||
| 20 | #import <QMUIKit/QMUIKit.h> | 20 | #import <QMUIKit/QMUIKit.h> |
| 21 | #import <YYKit/YYKit.h> | 21 | #import <YYKit/YYKit.h> |
| 22 | #import <objc/runtime.h> | 22 | #import <objc/runtime.h> |
| 23 | -#import <YBImageBrowser/YBImageBrowser.h> | ||
| 24 | -#import <YBImageBrowser/YBIBImageData.h> | 23 | +#import "YBImageBrowser.h" |
| 24 | +#import "YBIBImageData.h" | ||
| 25 | #import "UIImage+OrganizationValidation.h" | 25 | #import "UIImage+OrganizationValidation.h" |
| 26 | 26 | ||
| 27 | 27 |
CNLiveOrganizationValidationModule/Classes/CNLiveOrganizationValidationModule/YBImageBrowser/AuxiliaryView/YBIBAuxiliaryViewHandler.h
0 → 100644
| 1 | +// | ||
| 2 | +// YBIBAuxiliaryViewHandler.h | ||
| 3 | +// YBImageBrowserDemo | ||
| 4 | +// | ||
| 5 | +// Created by 波儿菜 on 2019/6/27. | ||
| 6 | +// Copyright © 2019 波儿菜. All rights reserved. | ||
| 7 | +// | ||
| 8 | + | ||
| 9 | +#import <UIKit/UIKit.h> | ||
| 10 | + | ||
| 11 | +NS_ASSUME_NONNULL_BEGIN | ||
| 12 | + | ||
| 13 | +@protocol YBIBAuxiliaryViewHandler <NSObject> | ||
| 14 | + | ||
| 15 | +@required | ||
| 16 | + | ||
| 17 | +/// 展示正确情况的提示 | ||
| 18 | +- (void)yb_showCorrectToastWithContainer:(UIView *)container text:(NSString *)text; | ||
| 19 | +/// 展示错误情况的提示 | ||
| 20 | +- (void)yb_showIncorrectToastWithContainer:(UIView *)container text:(NSString *)text; | ||
| 21 | +/// 隐藏所有提示 | ||
| 22 | +- (void)yb_hideToastWithContainer:(UIView *)container; | ||
| 23 | + | ||
| 24 | +/// 展示加载视图 | ||
| 25 | +- (void)yb_showLoadingWithContainer:(UIView *)container; | ||
| 26 | +/// 展示带进度的加载视图 | ||
| 27 | +- (void)yb_showLoadingWithContainer:(UIView *)container progress:(CGFloat)progress; | ||
| 28 | +/// 展示带文字的视图 | ||
| 29 | +- (void)yb_showLoadingWithContainer:(UIView *)container text:(NSString *)text; | ||
| 30 | +/// 隐藏所有视图 | ||
| 31 | +- (void)yb_hideLoadingWithContainer:(UIView *)container; | ||
| 32 | + | ||
| 33 | +@end | ||
| 34 | + | ||
| 35 | +@interface YBIBAuxiliaryViewHandler : NSObject <YBIBAuxiliaryViewHandler> | ||
| 36 | + | ||
| 37 | +@end | ||
| 38 | + | ||
| 39 | +NS_ASSUME_NONNULL_END |
CNLiveOrganizationValidationModule/Classes/CNLiveOrganizationValidationModule/YBImageBrowser/AuxiliaryView/YBIBAuxiliaryViewHandler.m
0 → 100644
| 1 | +// | ||
| 2 | +// YBIBAuxiliaryViewHandler.m | ||
| 3 | +// YBImageBrowserDemo | ||
| 4 | +// | ||
| 5 | +// Created by 波儿菜 on 2019/6/27. | ||
| 6 | +// Copyright © 2019 波儿菜. All rights reserved. | ||
| 7 | +// | ||
| 8 | + | ||
| 9 | +#import "YBIBAuxiliaryViewHandler.h" | ||
| 10 | +#import "YBIBToastView.h" | ||
| 11 | +#import "YBIBLoadingView.h" | ||
| 12 | + | ||
| 13 | +@implementation YBIBAuxiliaryViewHandler | ||
| 14 | + | ||
| 15 | +#pragma mark - <YBIBAuxiliaryViewHandler> | ||
| 16 | + | ||
| 17 | +- (void)yb_showCorrectToastWithContainer:(UIView *)container text:(NSString *)text { | ||
| 18 | + [container ybib_showHookToast:text]; | ||
| 19 | +} | ||
| 20 | + | ||
| 21 | +- (void)yb_showIncorrectToastWithContainer:(UIView *)container text:(NSString *)text { | ||
| 22 | + [container ybib_showForkToast:text]; | ||
| 23 | +} | ||
| 24 | + | ||
| 25 | +- (void)yb_hideToastWithContainer:(UIView *)container { | ||
| 26 | + [container ybib_hideToast]; | ||
| 27 | +} | ||
| 28 | + | ||
| 29 | +- (void)yb_showLoadingWithContainer:(UIView *)container { | ||
| 30 | + [container ybib_showLoading]; | ||
| 31 | +} | ||
| 32 | + | ||
| 33 | +- (void)yb_showLoadingWithContainer:(UIView *)container progress:(CGFloat)progress { | ||
| 34 | + [container ybib_showLoadingWithProgress:progress]; | ||
| 35 | +} | ||
| 36 | + | ||
| 37 | +- (void)yb_showLoadingWithContainer:(UIView *)container text:(NSString *)text { | ||
| 38 | + [container ybib_showLoadingWithText:text click:nil]; | ||
| 39 | +} | ||
| 40 | + | ||
| 41 | +- (void)yb_hideLoadingWithContainer:(UIView *)container { | ||
| 42 | + [container ybib_hideLoading]; | ||
| 43 | +} | ||
| 44 | + | ||
| 45 | +@end |
CNLiveOrganizationValidationModule/Classes/CNLiveOrganizationValidationModule/YBImageBrowser/AuxiliaryView/YBIBLoadingView.h
0 → 100644
| 1 | +// | ||
| 2 | +// YBIBLoadingView.h | ||
| 3 | +// YBImageBrowserDemo | ||
| 4 | +// | ||
| 5 | +// Created by 波儿菜 on 2018/9/1. | ||
| 6 | +// Copyright © 2018年 波儿菜. All rights reserved. | ||
| 7 | +// | ||
| 8 | + | ||
| 9 | +#import <UIKit/UIKit.h> | ||
| 10 | + | ||
| 11 | +NS_ASSUME_NONNULL_BEGIN | ||
| 12 | + | ||
| 13 | +@interface UIView (YBIBLoading) | ||
| 14 | + | ||
| 15 | +- (void)ybib_showLoading; | ||
| 16 | + | ||
| 17 | +- (void)ybib_showLoadingWithProgress:(CGFloat)progress; | ||
| 18 | + | ||
| 19 | +- (void)ybib_showLoadingWithText:(NSString *)text click:(nullable void(^)(void))click; | ||
| 20 | + | ||
| 21 | +- (void)ybib_hideLoading; | ||
| 22 | + | ||
| 23 | +@end | ||
| 24 | + | ||
| 25 | + | ||
| 26 | +@interface YBIBLoadingView : UIView | ||
| 27 | + | ||
| 28 | +- (void)show; | ||
| 29 | + | ||
| 30 | +- (void)showProgress:(CGFloat)progress; | ||
| 31 | + | ||
| 32 | +- (void)showText:(NSString *)text click:(void(^)(void))click; | ||
| 33 | + | ||
| 34 | +@end | ||
| 35 | + | ||
| 36 | +NS_ASSUME_NONNULL_END |
CNLiveOrganizationValidationModule/Classes/CNLiveOrganizationValidationModule/YBImageBrowser/AuxiliaryView/YBIBLoadingView.m
0 → 100644
| 1 | +// | ||
| 2 | +// YBIBLoadingView.m | ||
| 3 | +// YBImageBrowserDemo | ||
| 4 | +// | ||
| 5 | +// Created by 波儿菜 on 2018/9/1. | ||
| 6 | +// Copyright © 2018年 波儿菜. All rights reserved. | ||
| 7 | +// | ||
| 8 | + | ||
| 9 | +#import "YBIBLoadingView.h" | ||
| 10 | +#import "YBIBIconManager.h" | ||
| 11 | +#import <objc/runtime.h> | ||
| 12 | + | ||
| 13 | +@interface UIView () | ||
| 14 | +@property (nonatomic, strong, readonly) YBIBLoadingView *ybib_loading; | ||
| 15 | +@end | ||
| 16 | + | ||
| 17 | +@implementation UIView (YBIBLoading) | ||
| 18 | + | ||
| 19 | +- (void)ybib_showLoadingWithProgress:(CGFloat)progress { | ||
| 20 | + [self ybib_addLoadingView]; | ||
| 21 | + [self.ybib_loading showProgress:progress]; | ||
| 22 | +} | ||
| 23 | + | ||
| 24 | +- (void)ybib_showLoading { | ||
| 25 | + [self ybib_addLoadingView]; | ||
| 26 | + [self.ybib_loading show]; | ||
| 27 | +} | ||
| 28 | + | ||
| 29 | +- (void)ybib_showLoadingWithText:(NSString *)text click:(void (^)(void))click { | ||
| 30 | + [self ybib_addLoadingView]; | ||
| 31 | + [self.ybib_loading showText:text click:click]; | ||
| 32 | +} | ||
| 33 | + | ||
| 34 | +- (void)ybib_addLoadingView { | ||
| 35 | + YBIBLoadingView *loading = self.ybib_loading; | ||
| 36 | + if (!loading.superview) { | ||
| 37 | + [self addSubview:loading]; | ||
| 38 | + loading.translatesAutoresizingMaskIntoConstraints = NO; | ||
| 39 | + NSLayoutConstraint *layA = [NSLayoutConstraint constraintWithItem:loading attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeLeft multiplier:1 constant:0]; | ||
| 40 | + NSLayoutConstraint *layB = [NSLayoutConstraint constraintWithItem:loading attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeRight multiplier:1 constant:0]; | ||
| 41 | + NSLayoutConstraint *layC = [NSLayoutConstraint constraintWithItem:loading attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeBottom multiplier:1 constant:0]; | ||
| 42 | + NSLayoutConstraint *layD = [NSLayoutConstraint constraintWithItem:loading attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeTop multiplier:1 constant:0]; | ||
| 43 | + [self addConstraints:@[layA, layB, layC, layD]]; | ||
| 44 | + } | ||
| 45 | +} | ||
| 46 | + | ||
| 47 | +- (void)ybib_hideLoading { | ||
| 48 | + YBIBLoadingView *loading = self.ybib_loading; | ||
| 49 | + if (loading && loading.superview) { | ||
| 50 | + [loading removeFromSuperview]; | ||
| 51 | + } | ||
| 52 | +} | ||
| 53 | + | ||
| 54 | +static void *YBIBLoadingKey = &YBIBLoadingKey; | ||
| 55 | +- (void)setYbib_loading:(YBIBLoadingView *)ybib_loading { | ||
| 56 | + objc_setAssociatedObject(self, YBIBLoadingKey, ybib_loading, OBJC_ASSOCIATION_RETAIN_NONATOMIC); | ||
| 57 | +} | ||
| 58 | +- (YBIBLoadingView *)ybib_loading { | ||
| 59 | + YBIBLoadingView *loading = objc_getAssociatedObject(self, YBIBLoadingKey); | ||
| 60 | + if (!loading) { | ||
| 61 | + loading = [YBIBLoadingView new]; | ||
| 62 | + self.ybib_loading = loading; | ||
| 63 | + } | ||
| 64 | + return loading; | ||
| 65 | +} | ||
| 66 | + | ||
| 67 | +@end | ||
| 68 | + | ||
| 69 | + | ||
| 70 | +@interface YBIBProgressDrawView : UIView | ||
| 71 | +@property (nonatomic, assign) CGFloat progress; | ||
| 72 | +@end | ||
| 73 | +@implementation YBIBProgressDrawView | ||
| 74 | +- (void)drawRect:(CGRect)rect { | ||
| 75 | + if (self.isHidden) return; | ||
| 76 | + | ||
| 77 | + CGFloat progress = (isnan(_progress) || isinf(_progress) || _progress < 0) ? 0 : _progress; | ||
| 78 | + | ||
| 79 | + CGFloat radius = 17; | ||
| 80 | + CGFloat strokeWidth = 3; | ||
| 81 | + CGPoint center = CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds)); | ||
| 82 | + | ||
| 83 | + [[UIColor lightGrayColor] setStroke]; | ||
| 84 | + UIBezierPath *bottomPath = [UIBezierPath bezierPathWithArcCenter:center radius:radius startAngle:0 endAngle:M_PI * 2 clockwise:YES]; | ||
| 85 | + bottomPath.lineWidth = 4.0; | ||
| 86 | + bottomPath.lineCapStyle = kCGLineCapRound; | ||
| 87 | + bottomPath.lineJoinStyle = kCGLineCapRound; | ||
| 88 | + [bottomPath stroke]; | ||
| 89 | + | ||
| 90 | + [[UIColor whiteColor] setStroke]; | ||
| 91 | + UIBezierPath *activePath = [UIBezierPath bezierPathWithArcCenter:center radius:radius startAngle:-M_PI / 2.0 endAngle:M_PI * 2 * progress - M_PI / 2.0 clockwise:true]; | ||
| 92 | + activePath.lineWidth = strokeWidth; | ||
| 93 | + activePath.lineCapStyle = kCGLineCapRound; | ||
| 94 | + activePath.lineJoinStyle = kCGLineCapRound; | ||
| 95 | + [activePath stroke]; | ||
| 96 | + | ||
| 97 | + NSShadow *shadow = [NSShadow new]; | ||
| 98 | + shadow.shadowBlurRadius = 4; | ||
| 99 | + shadow.shadowOffset = CGSizeMake(0, 1); | ||
| 100 | + shadow.shadowColor = UIColor.darkGrayColor; | ||
| 101 | + NSString *string = [NSString stringWithFormat:@"%.0lf%@", progress * 100, @"%"]; | ||
| 102 | + NSMutableAttributedString *atts = [[NSMutableAttributedString alloc] initWithString:string attributes:@{NSFontAttributeName:[UIFont boldSystemFontOfSize:10], NSForegroundColorAttributeName:[UIColor whiteColor], NSShadowAttributeName:shadow}]; | ||
| 103 | + CGSize size = atts.size; | ||
| 104 | + [atts drawAtPoint:CGPointMake(center.x - size.width / 2.0, center.y - size.height / 2.0)]; | ||
| 105 | +} | ||
| 106 | +@end | ||
| 107 | + | ||
| 108 | + | ||
| 109 | +typedef NS_ENUM(NSUInteger, YBImageBrowserProgressType) { | ||
| 110 | + YBImageBrowserProgressTypeProgress, | ||
| 111 | + YBImageBrowserProgressTypeLoad, | ||
| 112 | + YBImageBrowserProgressTypeText | ||
| 113 | +}; | ||
| 114 | + | ||
| 115 | +@interface YBIBLoadingView () { | ||
| 116 | + YBImageBrowserProgressType _type; | ||
| 117 | +} | ||
| 118 | +@property (nonatomic, strong) UILabel *textLabel; | ||
| 119 | +@property (nonatomic, strong) UIImageView *imageView; | ||
| 120 | +@property (nonatomic, strong) YBIBProgressDrawView *drawView; | ||
| 121 | +@property (nonatomic, copy) void(^clickTextLabelBlock)(void); | ||
| 122 | +@end | ||
| 123 | + | ||
| 124 | +@implementation YBIBLoadingView | ||
| 125 | + | ||
| 126 | +#pragma mark life cycle | ||
| 127 | + | ||
| 128 | +- (instancetype)initWithFrame:(CGRect)frame { | ||
| 129 | + self = [super initWithFrame:frame]; | ||
| 130 | + if (self) { | ||
| 131 | + self.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0]; | ||
| 132 | + self.userInteractionEnabled = NO; | ||
| 133 | + | ||
| 134 | + [self addSubview:self.drawView]; | ||
| 135 | + [self addSubview:self.textLabel]; | ||
| 136 | + [self addSubview:self.imageView]; | ||
| 137 | + } | ||
| 138 | + return self; | ||
| 139 | +} | ||
| 140 | + | ||
| 141 | +- (void)updateConstraints { | ||
| 142 | + self.textLabel.translatesAutoresizingMaskIntoConstraints = NO; | ||
| 143 | + NSLayoutConstraint *layA = [NSLayoutConstraint constraintWithItem:self.textLabel attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeLeft multiplier:1 constant:20]; | ||
| 144 | + NSLayoutConstraint *layB = [NSLayoutConstraint constraintWithItem:self.textLabel attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeRight multiplier:1 constant:-20]; | ||
| 145 | + NSLayoutConstraint *layC = [NSLayoutConstraint constraintWithItem:self.textLabel attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeCenterY multiplier:1 constant:0]; | ||
| 146 | + | ||
| 147 | + self.imageView.translatesAutoresizingMaskIntoConstraints = NO; | ||
| 148 | + NSLayoutConstraint *layE = [NSLayoutConstraint constraintWithItem:self.imageView attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeCenterX multiplier:1 constant:0]; | ||
| 149 | + NSLayoutConstraint *layF = [NSLayoutConstraint constraintWithItem:self.imageView attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeCenterY multiplier:1 constant:0]; | ||
| 150 | + | ||
| 151 | + self.drawView.translatesAutoresizingMaskIntoConstraints = NO; | ||
| 152 | + NSLayoutConstraint *layG = [NSLayoutConstraint constraintWithItem:self.drawView attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeCenterX multiplier:1 constant:0]; | ||
| 153 | + NSLayoutConstraint *layH = [NSLayoutConstraint constraintWithItem:self.drawView attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeCenterY multiplier:1 constant:0]; | ||
| 154 | + NSLayoutConstraint *layI = [NSLayoutConstraint constraintWithItem:self.drawView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1 constant:50]; | ||
| 155 | + NSLayoutConstraint *layJ = [NSLayoutConstraint constraintWithItem:self.drawView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1 constant:50]; | ||
| 156 | + | ||
| 157 | + [self addConstraints:@[layA, layB, layC, layE, layF, layG, layH, layI, layJ]]; | ||
| 158 | + [super updateConstraints]; | ||
| 159 | +} | ||
| 160 | + | ||
| 161 | +#pragma mark public | ||
| 162 | + | ||
| 163 | +- (void)showProgress:(CGFloat)progress { | ||
| 164 | + self.userInteractionEnabled = NO; | ||
| 165 | + _type = YBImageBrowserProgressTypeProgress; | ||
| 166 | + self.drawView.hidden = NO; | ||
| 167 | + self.textLabel.hidden = YES; | ||
| 168 | + self.imageView.hidden = YES; | ||
| 169 | + [self stopImageViewAnimation]; | ||
| 170 | + | ||
| 171 | + self.drawView.progress = progress; | ||
| 172 | + [self.drawView setNeedsDisplay]; | ||
| 173 | +} | ||
| 174 | + | ||
| 175 | +- (void)show { | ||
| 176 | + self.userInteractionEnabled = NO; | ||
| 177 | + _type = YBImageBrowserProgressTypeLoad; | ||
| 178 | + self.drawView.hidden = YES; | ||
| 179 | + self.textLabel.hidden = YES; | ||
| 180 | + self.imageView.hidden = NO; | ||
| 181 | + | ||
| 182 | + [self startImageViewAnimation]; | ||
| 183 | + [self.drawView setNeedsDisplay]; | ||
| 184 | +} | ||
| 185 | + | ||
| 186 | +- (void)startImageViewAnimation { | ||
| 187 | + CABasicAnimation *ra = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"]; | ||
| 188 | + ra.toValue = [NSNumber numberWithFloat:M_PI * 2]; | ||
| 189 | + ra.duration = 1; | ||
| 190 | + ra.cumulative = YES; | ||
| 191 | + ra.repeatCount = HUGE_VALF; | ||
| 192 | + ra.removedOnCompletion = NO; | ||
| 193 | + ra.fillMode = kCAFillModeForwards; | ||
| 194 | + [self.imageView.layer addAnimation:ra forKey:@"ra"]; | ||
| 195 | +} | ||
| 196 | + | ||
| 197 | +- (void)stopImageViewAnimation { | ||
| 198 | + [self.imageView.layer removeAllAnimations]; | ||
| 199 | +} | ||
| 200 | + | ||
| 201 | +- (void)showText:(NSString *)text click:(void (^)(void))click { | ||
| 202 | + self.userInteractionEnabled = click ? YES : NO; | ||
| 203 | + _type = YBImageBrowserProgressTypeText; | ||
| 204 | + self.drawView.hidden = YES; | ||
| 205 | + self.textLabel.hidden = NO; | ||
| 206 | + self.imageView.hidden = YES; | ||
| 207 | + [self stopImageViewAnimation]; | ||
| 208 | + | ||
| 209 | + self.textLabel.text = text; | ||
| 210 | + self.clickTextLabelBlock = click; | ||
| 211 | + [self.drawView setNeedsDisplay]; | ||
| 212 | +} | ||
| 213 | + | ||
| 214 | +#pragma mark - touch event | ||
| 215 | + | ||
| 216 | +- (void)respondsToTapTextlabel { | ||
| 217 | + if (self.clickTextLabelBlock) { | ||
| 218 | + self.clickTextLabelBlock(); | ||
| 219 | + } | ||
| 220 | +} | ||
| 221 | + | ||
| 222 | +#pragma mark - getter | ||
| 223 | + | ||
| 224 | +- (YBIBProgressDrawView *)drawView { | ||
| 225 | + if (!_drawView) { | ||
| 226 | + _drawView = [YBIBProgressDrawView new]; | ||
| 227 | + _drawView.backgroundColor = [UIColor clearColor]; | ||
| 228 | + } | ||
| 229 | + return _drawView; | ||
| 230 | +} | ||
| 231 | + | ||
| 232 | +- (UILabel *)textLabel { | ||
| 233 | + if (!_textLabel) { | ||
| 234 | + _textLabel = [UILabel new]; | ||
| 235 | + _textLabel.textColor = [UIColor whiteColor]; | ||
| 236 | + _textLabel.numberOfLines = 0; | ||
| 237 | + _textLabel.font = [UIFont systemFontOfSize:14]; | ||
| 238 | + _textLabel.textAlignment = NSTextAlignmentCenter; | ||
| 239 | + UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(respondsToTapTextlabel)]; | ||
| 240 | + [_textLabel addGestureRecognizer:tapGesture]; | ||
| 241 | + _textLabel.userInteractionEnabled = YES; | ||
| 242 | + } | ||
| 243 | + return _textLabel; | ||
| 244 | +} | ||
| 245 | + | ||
| 246 | +- (UIImageView *)imageView { | ||
| 247 | + if (!_imageView) { | ||
| 248 | + _imageView = [UIImageView new]; | ||
| 249 | + _imageView.image = [YBIBIconManager sharedManager].loadingImage(); | ||
| 250 | + _imageView.layer.shadowColor = UIColor.darkGrayColor.CGColor; | ||
| 251 | + _imageView.layer.shadowOffset = CGSizeMake(0, 1); | ||
| 252 | + _imageView.layer.shadowOpacity = 1; | ||
| 253 | + _imageView.layer.shadowRadius = 4; | ||
| 254 | + } | ||
| 255 | + return _imageView; | ||
| 256 | +} | ||
| 257 | + | ||
| 258 | +@end |
CNLiveOrganizationValidationModule/Classes/CNLiveOrganizationValidationModule/YBImageBrowser/AuxiliaryView/YBIBToastView.h
0 → 100644
| 1 | +// | ||
| 2 | +// YBIBToastView.h | ||
| 3 | +// YBImageBrowserDemo | ||
| 4 | +// | ||
| 5 | +// Created by 波儿菜 on 2019/6/20. | ||
| 6 | +// Copyright © 2019 波儿菜. All rights reserved. | ||
| 7 | +// | ||
| 8 | + | ||
| 9 | +#import <UIKit/UIKit.h> | ||
| 10 | + | ||
| 11 | +NS_ASSUME_NONNULL_BEGIN | ||
| 12 | + | ||
| 13 | +@interface UIView (YBIBToast) | ||
| 14 | + | ||
| 15 | +- (void)ybib_showHookToast:(NSString *)text; | ||
| 16 | + | ||
| 17 | +- (void)ybib_showForkToast:(NSString *)text; | ||
| 18 | + | ||
| 19 | +- (void)ybib_hideToast; | ||
| 20 | + | ||
| 21 | +@end | ||
| 22 | + | ||
| 23 | + | ||
| 24 | +typedef NS_ENUM(NSInteger, YBIBToastType) { | ||
| 25 | + YBIBToastTypeNone, | ||
| 26 | + YBIBToastTypeHook, | ||
| 27 | + YBIBToastTypeFork | ||
| 28 | +}; | ||
| 29 | + | ||
| 30 | +@interface YBIBToastView : UIView | ||
| 31 | + | ||
| 32 | +- (void)showWithText:(NSString *)text type:(YBIBToastType)type; | ||
| 33 | + | ||
| 34 | +@end | ||
| 35 | + | ||
| 36 | +NS_ASSUME_NONNULL_END |
CNLiveOrganizationValidationModule/Classes/CNLiveOrganizationValidationModule/YBImageBrowser/AuxiliaryView/YBIBToastView.m
0 → 100644
| 1 | +// | ||
| 2 | +// YBIBToastView.m | ||
| 3 | +// YBImageBrowserDemo | ||
| 4 | +// | ||
| 5 | +// Created by 波儿菜 on 2019/6/20. | ||
| 6 | +// Copyright © 2019 波儿菜. All rights reserved. | ||
| 7 | +// | ||
| 8 | + | ||
| 9 | +#import "YBIBToastView.h" | ||
| 10 | +#import <objc/runtime.h> | ||
| 11 | + | ||
| 12 | +@interface UIView () | ||
| 13 | +@property (nonatomic, strong, readonly) YBIBToastView *ybib_toast; | ||
| 14 | +@end | ||
| 15 | + | ||
| 16 | +@implementation UIView (YBIBToast) | ||
| 17 | + | ||
| 18 | +- (void)ybib_showHookToast:(NSString *)text { | ||
| 19 | + [self ybib_showToastWithText:text type:YBIBToastTypeHook hideAfterDelay:1.7]; | ||
| 20 | +} | ||
| 21 | + | ||
| 22 | +- (void)ybib_showForkToast:(NSString *)text { | ||
| 23 | + [self ybib_showToastWithText:text type:YBIBToastTypeFork hideAfterDelay:1.7]; | ||
| 24 | +} | ||
| 25 | + | ||
| 26 | +- (void)ybib_showToastWithText:(NSString *)text type:(YBIBToastType)type hideAfterDelay:(NSTimeInterval)delay { | ||
| 27 | + [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(ybib_hideToast) object:nil]; | ||
| 28 | + | ||
| 29 | + YBIBToastView *toast = self.ybib_toast; | ||
| 30 | + if (!toast.superview) { | ||
| 31 | + [self addSubview:toast]; | ||
| 32 | + toast.translatesAutoresizingMaskIntoConstraints = NO; | ||
| 33 | + NSLayoutConstraint *layA = [NSLayoutConstraint constraintWithItem:toast attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeCenterX multiplier:1 constant:0]; | ||
| 34 | + NSLayoutConstraint *layB = [NSLayoutConstraint constraintWithItem:toast attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeCenterY multiplier:1 constant:0]; | ||
| 35 | + NSLayoutConstraint *layC = [NSLayoutConstraint constraintWithItem:toast attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationGreaterThanOrEqual toItem:self attribute:NSLayoutAttributeLeft multiplier:1 constant:40]; | ||
| 36 | + NSLayoutConstraint *layD = [NSLayoutConstraint constraintWithItem:toast attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationLessThanOrEqual toItem:self attribute:NSLayoutAttributeRight multiplier:1 constant:-40]; | ||
| 37 | + [self addConstraints:@[layA, layB, layC, layD]]; | ||
| 38 | + } | ||
| 39 | + | ||
| 40 | + [toast showWithText:text type:type]; | ||
| 41 | + [self performSelector:@selector(ybib_hideToast) withObject:nil afterDelay:delay]; | ||
| 42 | +} | ||
| 43 | + | ||
| 44 | +- (void)ybib_hideToast { | ||
| 45 | + YBIBToastView *toast = self.ybib_toast; | ||
| 46 | + if (toast && toast.superview) { | ||
| 47 | + [UIView animateWithDuration:0.25 animations:^{ | ||
| 48 | + toast.alpha = 0; | ||
| 49 | + } completion:^(BOOL finished) { | ||
| 50 | + [toast removeFromSuperview]; | ||
| 51 | + toast.alpha = 1; | ||
| 52 | + }]; | ||
| 53 | + } | ||
| 54 | +} | ||
| 55 | + | ||
| 56 | +static void *YBIBToastKey = &YBIBToastKey; | ||
| 57 | +- (void)setYbib_toast:(YBIBToastView *)ybib_toast { | ||
| 58 | + objc_setAssociatedObject(self, YBIBToastKey, ybib_toast, OBJC_ASSOCIATION_RETAIN_NONATOMIC); | ||
| 59 | +} | ||
| 60 | +- (YBIBToastView *)ybib_toast { | ||
| 61 | + YBIBToastView *toast = objc_getAssociatedObject(self, YBIBToastKey); | ||
| 62 | + if (!toast) { | ||
| 63 | + toast = [YBIBToastView new]; | ||
| 64 | + self.ybib_toast = toast; | ||
| 65 | + } | ||
| 66 | + return toast; | ||
| 67 | +} | ||
| 68 | + | ||
| 69 | +@end | ||
| 70 | + | ||
| 71 | + | ||
| 72 | +@interface YBIBToastView () { | ||
| 73 | + YBIBToastType _type; | ||
| 74 | + CAShapeLayer *_shapeLayer; | ||
| 75 | +} | ||
| 76 | +@property (nonatomic, strong) UILabel *textLabel; | ||
| 77 | +@end | ||
| 78 | + | ||
| 79 | +@implementation YBIBToastView | ||
| 80 | + | ||
| 81 | +#pragma mark - life cycle | ||
| 82 | + | ||
| 83 | +- (instancetype)initWithFrame:(CGRect)frame { | ||
| 84 | + self = [super initWithFrame:frame]; | ||
| 85 | + if (self) { | ||
| 86 | + self.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.8]; | ||
| 87 | + self.userInteractionEnabled = NO; | ||
| 88 | + self.layer.cornerRadius = 7; | ||
| 89 | + | ||
| 90 | + [self addSubview:self.textLabel]; | ||
| 91 | + } | ||
| 92 | + return self; | ||
| 93 | +} | ||
| 94 | + | ||
| 95 | +- (void)updateConstraints { | ||
| 96 | + self.textLabel.translatesAutoresizingMaskIntoConstraints = NO; | ||
| 97 | + NSLayoutConstraint *layA = [NSLayoutConstraint constraintWithItem:self.textLabel attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeLeft multiplier:1 constant:20]; | ||
| 98 | + NSLayoutConstraint *layB = [NSLayoutConstraint constraintWithItem:self.textLabel attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeRight multiplier:1 constant:-20]; | ||
| 99 | + NSLayoutConstraint *layC = [NSLayoutConstraint constraintWithItem:self.textLabel attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeBottom multiplier:1 constant:-15]; | ||
| 100 | + NSLayoutConstraint *layD = [NSLayoutConstraint constraintWithItem:self.textLabel attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeTop multiplier:1 constant:70]; | ||
| 101 | + NSLayoutConstraint *layE = [NSLayoutConstraint constraintWithItem:self.textLabel attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationGreaterThanOrEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1 constant:60]; | ||
| 102 | + [self addConstraints:@[layA, layB, layC, layD, layE]]; | ||
| 103 | + [super updateConstraints]; | ||
| 104 | +} | ||
| 105 | + | ||
| 106 | +- (void)layoutSubviews { | ||
| 107 | + [super layoutSubviews]; | ||
| 108 | + [self startAnimation]; | ||
| 109 | +} | ||
| 110 | + | ||
| 111 | +#pragma mark - animation | ||
| 112 | + | ||
| 113 | +- (void)showWithText:(NSString *)text type:(YBIBToastType)type { | ||
| 114 | + self.textLabel.text = text; | ||
| 115 | + _type = type; | ||
| 116 | + [self setNeedsLayout]; | ||
| 117 | +} | ||
| 118 | + | ||
| 119 | +- (void)startAnimation { | ||
| 120 | + if (_shapeLayer && _shapeLayer.superlayer) { | ||
| 121 | + [_shapeLayer removeFromSuperlayer]; | ||
| 122 | + } | ||
| 123 | + _shapeLayer = [CAShapeLayer layer]; | ||
| 124 | + _shapeLayer.strokeColor = [UIColor whiteColor].CGColor; | ||
| 125 | + _shapeLayer.fillColor = [UIColor clearColor].CGColor; | ||
| 126 | + _shapeLayer.lineWidth = 5.0; | ||
| 127 | + _shapeLayer.lineCap = @"round"; | ||
| 128 | + _shapeLayer.lineJoin = @"round"; | ||
| 129 | + _shapeLayer.strokeStart = 0.0; | ||
| 130 | + _shapeLayer.strokeEnd = 0.0; | ||
| 131 | + | ||
| 132 | + UIBezierPath *bezierPath = [UIBezierPath bezierPath]; | ||
| 133 | + CGFloat r = 13.0; | ||
| 134 | + CGFloat x = self.bounds.size.width / 2.0; | ||
| 135 | + CGFloat y = 38.0; | ||
| 136 | + switch (_type) { | ||
| 137 | + case YBIBToastTypeHook: { | ||
| 138 | + [bezierPath moveToPoint:CGPointMake(x - r - r / 2, y)]; | ||
| 139 | + [bezierPath addLineToPoint:CGPointMake(x - r / 2, y + r)]; | ||
| 140 | + [bezierPath addLineToPoint:CGPointMake(x + r * 2 - r / 2, y - r)]; | ||
| 141 | + } | ||
| 142 | + break; | ||
| 143 | + case YBIBToastTypeFork: { | ||
| 144 | + [bezierPath moveToPoint:CGPointMake(x - r, y - r)]; | ||
| 145 | + [bezierPath addLineToPoint:CGPointMake(x + r, y + r)]; | ||
| 146 | + [bezierPath moveToPoint:CGPointMake(x - r, y + r)]; | ||
| 147 | + [bezierPath addLineToPoint:CGPointMake(x + r, y - r)]; | ||
| 148 | + } | ||
| 149 | + break; | ||
| 150 | + default:break; | ||
| 151 | + } | ||
| 152 | + | ||
| 153 | + CABasicAnimation *baseAnimation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"]; | ||
| 154 | + [baseAnimation setFromValue:@0.0]; | ||
| 155 | + [baseAnimation setToValue:@1.0]; | ||
| 156 | + [baseAnimation setDuration:0.3]; | ||
| 157 | + baseAnimation.removedOnCompletion = NO; | ||
| 158 | + baseAnimation.fillMode = kCAFillModeBoth; | ||
| 159 | + | ||
| 160 | + _shapeLayer.path = bezierPath.CGPath; | ||
| 161 | + [self.layer addSublayer:_shapeLayer]; | ||
| 162 | + [_shapeLayer addAnimation:baseAnimation forKey:@"strokeEnd"]; | ||
| 163 | +} | ||
| 164 | + | ||
| 165 | +#pragma mark - getter | ||
| 166 | + | ||
| 167 | +- (UILabel *)textLabel { | ||
| 168 | + if (!_textLabel) { | ||
| 169 | + _textLabel = [UILabel new]; | ||
| 170 | + _textLabel.textColor = [UIColor whiteColor]; | ||
| 171 | + _textLabel.font = [UIFont systemFontOfSize:14]; | ||
| 172 | + _textLabel.textAlignment = NSTextAlignmentCenter; | ||
| 173 | + _textLabel.numberOfLines = 0; | ||
| 174 | + } | ||
| 175 | + return _textLabel; | ||
| 176 | +} | ||
| 177 | + | ||
| 178 | +@end | ||
| 179 | + |
CNLiveOrganizationValidationModule/Classes/CNLiveOrganizationValidationModule/YBImageBrowser/Base/NSObject+YBImageBrowser.h
0 → 100644
| 1 | +// | ||
| 2 | +// NSObject+YBImageBrowser.h | ||
| 3 | +// YBImageBrowserDemo | ||
| 4 | +// | ||
| 5 | +// Created by 波儿菜 on 2019/9/26. | ||
| 6 | +// Copyright © 2019 杨波. All rights reserved. | ||
| 7 | +// | ||
| 8 | + | ||
| 9 | +#import <UIKit/UIKit.h> | ||
| 10 | + | ||
| 11 | +NS_ASSUME_NONNULL_BEGIN | ||
| 12 | + | ||
| 13 | +@interface NSObject (YBImageBrowser) | ||
| 14 | + | ||
| 15 | +@property (nonatomic, assign) CGFloat ybib_originAlpha; | ||
| 16 | + | ||
| 17 | +@end | ||
| 18 | + | ||
| 19 | +NS_ASSUME_NONNULL_END |
CNLiveOrganizationValidationModule/Classes/CNLiveOrganizationValidationModule/YBImageBrowser/Base/NSObject+YBImageBrowser.m
0 → 100644
| 1 | +// | ||
| 2 | +// NSObject+YBImageBrowser.m | ||
| 3 | +// YBImageBrowserDemo | ||
| 4 | +// | ||
| 5 | +// Created by 波儿菜 on 2019/9/26. | ||
| 6 | +// Copyright © 2019 杨波. All rights reserved. | ||
| 7 | +// | ||
| 8 | + | ||
| 9 | +#import "NSObject+YBImageBrowser.h" | ||
| 10 | +#import <objc/runtime.h> | ||
| 11 | + | ||
| 12 | +@implementation NSObject (YBImageBrowser) | ||
| 13 | + | ||
| 14 | +static void *YBIBOriginAlphaKey = &YBIBOriginAlphaKey; | ||
| 15 | +- (void)setYbib_originAlpha:(CGFloat)ybib_originAlpha { | ||
| 16 | + objc_setAssociatedObject(self, YBIBOriginAlphaKey, @(ybib_originAlpha), OBJC_ASSOCIATION_RETAIN_NONATOMIC); | ||
| 17 | +} | ||
| 18 | +- (CGFloat)ybib_originAlpha { | ||
| 19 | + NSNumber *alpha = objc_getAssociatedObject(self, YBIBOriginAlphaKey); | ||
| 20 | + return alpha ? alpha.floatValue : 1; | ||
| 21 | +} | ||
| 22 | + | ||
| 23 | +@end |
CNLiveOrganizationValidationModule/Classes/CNLiveOrganizationValidationModule/YBImageBrowser/Base/YBIBAnimatedTransition.h
0 → 100644
| 1 | +// | ||
| 2 | +// YBIBAnimatedTransition.h | ||
| 3 | +// YBImageBrowserDemo | ||
| 4 | +// | ||
| 5 | +// Created by 波儿菜 on 2019/6/6. | ||
| 6 | +// Copyright © 2019 波儿菜. All rights reserved. | ||
| 7 | +// | ||
| 8 | + | ||
| 9 | +#import <UIKit/UIKit.h> | ||
| 10 | + | ||
| 11 | +NS_ASSUME_NONNULL_BEGIN | ||
| 12 | + | ||
| 13 | +@protocol YBIBAnimatedTransition <NSObject> | ||
| 14 | +@required | ||
| 15 | + | ||
| 16 | +- (void)yb_showTransitioningWithContainer:(UIView *)container startView:(nullable __kindof UIView *)startView startImage:(nullable UIImage *)startImage endFrame:(CGRect)endFrame orientation:(UIDeviceOrientation)orientation completion:(void(^)(void))completion; | ||
| 17 | + | ||
| 18 | +- (void)yb_hideTransitioningWithContainer:(UIView *)container startView:(nullable __kindof UIView *)startView endView:(UIView *)endView orientation:(UIDeviceOrientation)orientation completion:(void(^)(void))completion; | ||
| 19 | + | ||
| 20 | +@end | ||
| 21 | + | ||
| 22 | + | ||
| 23 | +typedef NS_ENUM(NSInteger, YBIBTransitionType) { | ||
| 24 | + /// 无动效 | ||
| 25 | + YBIBTransitionTypeNone, | ||
| 26 | + /// 渐隐 | ||
| 27 | + YBIBTransitionTypeFade, | ||
| 28 | + /// 连贯移动 | ||
| 29 | + YBIBTransitionTypeCoherent | ||
| 30 | +}; | ||
| 31 | + | ||
| 32 | +@interface YBIBAnimatedTransition : NSObject <YBIBAnimatedTransition> | ||
| 33 | + | ||
| 34 | +/// 入场动效类型 | ||
| 35 | +@property (nonatomic, assign) YBIBTransitionType showType; | ||
| 36 | +/// 出场动效类型 | ||
| 37 | +@property (nonatomic, assign) YBIBTransitionType hideType; | ||
| 38 | + | ||
| 39 | +/// 入场动效持续时间 | ||
| 40 | +@property (nonatomic, assign) NSTimeInterval showDuration; | ||
| 41 | +/// 出场动效持续时间 | ||
| 42 | +@property (nonatomic, assign) NSTimeInterval hideDuration; | ||
| 43 | + | ||
| 44 | +@end | ||
| 45 | + | ||
| 46 | +NS_ASSUME_NONNULL_END |
CNLiveOrganizationValidationModule/Classes/CNLiveOrganizationValidationModule/YBImageBrowser/Base/YBIBAnimatedTransition.m
0 → 100644
| 1 | +// | ||
| 2 | +// YBIBAnimatedTransition.m | ||
| 3 | +// YBImageBrowserDemo | ||
| 4 | +// | ||
| 5 | +// Created by 波儿菜 on 2019/6/6. | ||
| 6 | +// Copyright © 2019 波儿菜. All rights reserved. | ||
| 7 | +// | ||
| 8 | + | ||
| 9 | +#import "YBIBAnimatedTransition.h" | ||
| 10 | + | ||
| 11 | +extern CGFloat YBIBRotationAngle(UIDeviceOrientation startOrientation, UIDeviceOrientation endOrientation); | ||
| 12 | + | ||
| 13 | +@implementation YBIBAnimatedTransition | ||
| 14 | + | ||
| 15 | +#pragma mark - life cycle | ||
| 16 | + | ||
| 17 | +- (instancetype)init { | ||
| 18 | + self = [super init]; | ||
| 19 | + if (self) { | ||
| 20 | + _showType = _hideType = YBIBTransitionTypeCoherent; | ||
| 21 | + _showDuration = _hideDuration = 0.25; | ||
| 22 | + } | ||
| 23 | + return self; | ||
| 24 | +} | ||
| 25 | + | ||
| 26 | +#pragma mark - <YBIBAnimationHandler> | ||
| 27 | + | ||
| 28 | +- (void)yb_showTransitioningWithContainer:(UIView *)container startView:(__kindof UIView *)startView startImage:(UIImage *)startImage endFrame:(CGRect)endFrame orientation:(UIDeviceOrientation)orientation completion:(void (^)(void))completion { | ||
| 29 | + YBIBTransitionType type = self.showType; | ||
| 30 | + if (type == YBIBTransitionTypeCoherent) { | ||
| 31 | + if (CGRectIsEmpty(endFrame) || !startView || orientation != (UIDeviceOrientation)UIApplication.sharedApplication.statusBarOrientation) { | ||
| 32 | + type = YBIBTransitionTypeFade; | ||
| 33 | + } | ||
| 34 | + } | ||
| 35 | + | ||
| 36 | + switch (type) { | ||
| 37 | + case YBIBTransitionTypeNone: { | ||
| 38 | + completion(); | ||
| 39 | + } | ||
| 40 | + break; | ||
| 41 | + case YBIBTransitionTypeFade: { | ||
| 42 | + | ||
| 43 | + BOOL animateValid = !CGRectIsEmpty(endFrame) && startView; | ||
| 44 | + | ||
| 45 | + UIImageView *animateImageView; | ||
| 46 | + if (animateValid) { | ||
| 47 | + animateImageView = [self imageViewAssimilateToView:startView]; | ||
| 48 | + animateImageView.frame = endFrame; | ||
| 49 | + animateImageView.image = startImage; | ||
| 50 | + [container addSubview:animateImageView]; | ||
| 51 | + } | ||
| 52 | + | ||
| 53 | + CGFloat rawAlpha = container.alpha; | ||
| 54 | + container.alpha = 0; | ||
| 55 | + | ||
| 56 | + if (!animateValid) completion(); | ||
| 57 | + | ||
| 58 | + [UIView animateWithDuration:self.showDuration animations:^{ | ||
| 59 | + container.alpha = rawAlpha; | ||
| 60 | + } completion:^(BOOL finished) { | ||
| 61 | + if (animateValid) { | ||
| 62 | + [animateImageView removeFromSuperview]; | ||
| 63 | + completion(); | ||
| 64 | + } | ||
| 65 | + }]; | ||
| 66 | + | ||
| 67 | + } | ||
| 68 | + break; | ||
| 69 | + case YBIBTransitionTypeCoherent: { | ||
| 70 | + | ||
| 71 | + UIImageView *animateImageView = [self imageViewAssimilateToView:startView]; | ||
| 72 | + animateImageView.frame = [startView convertRect:startView.bounds toView:container]; | ||
| 73 | + animateImageView.image = startImage; | ||
| 74 | + | ||
| 75 | + [container addSubview:animateImageView]; | ||
| 76 | + | ||
| 77 | + UIColor *rawBackgroundColor = container.backgroundColor; | ||
| 78 | + container.backgroundColor = [rawBackgroundColor colorWithAlphaComponent:0]; | ||
| 79 | + | ||
| 80 | + [UIView animateWithDuration:self.showDuration animations:^{ | ||
| 81 | + animateImageView.frame = endFrame; | ||
| 82 | + container.backgroundColor = rawBackgroundColor; | ||
| 83 | + } completion:^(BOOL finished) { | ||
| 84 | + completion(); | ||
| 85 | + // Disappear smoothly. | ||
| 86 | + [UIView animateWithDuration:0.2 animations:^{ | ||
| 87 | + animateImageView.alpha = 0; | ||
| 88 | + } completion:^(BOOL finished) { | ||
| 89 | + [animateImageView removeFromSuperview]; | ||
| 90 | + }]; | ||
| 91 | + }]; | ||
| 92 | + | ||
| 93 | + } | ||
| 94 | + break; | ||
| 95 | + } | ||
| 96 | +} | ||
| 97 | + | ||
| 98 | +- (void)yb_hideTransitioningWithContainer:(UIView *)container startView:(__kindof UIView *)startView endView:(UIView *)endView orientation:(UIDeviceOrientation)orientation completion:(void (^)(void))completion { | ||
| 99 | + YBIBTransitionType type = self.hideType; | ||
| 100 | + if (type == YBIBTransitionTypeCoherent && (!startView || !endView)) { | ||
| 101 | + type = YBIBTransitionTypeFade; | ||
| 102 | + } | ||
| 103 | + | ||
| 104 | + switch (type) { | ||
| 105 | + case YBIBTransitionTypeNone: { | ||
| 106 | + completion(); | ||
| 107 | + } | ||
| 108 | + break; | ||
| 109 | + case YBIBTransitionTypeFade: { | ||
| 110 | + | ||
| 111 | + CGFloat rawAlpha = container.alpha; | ||
| 112 | + | ||
| 113 | + [UIView animateWithDuration:self.hideDuration animations:^{ | ||
| 114 | + container.alpha = 0; | ||
| 115 | + } completion:^(BOOL finished) { | ||
| 116 | + completion(); | ||
| 117 | + container.alpha = rawAlpha; | ||
| 118 | + }]; | ||
| 119 | + | ||
| 120 | + } | ||
| 121 | + break; | ||
| 122 | + case YBIBTransitionTypeCoherent: { | ||
| 123 | + | ||
| 124 | + CGRect startFrame = startView.frame; | ||
| 125 | + CGRect endFrame = [endView convertRect:endView.bounds toView:startView.superview]; | ||
| 126 | + | ||
| 127 | + UIColor *rawBackgroundColor = container.backgroundColor; | ||
| 128 | + | ||
| 129 | + [UIView animateWithDuration:self.hideDuration animations:^{ | ||
| 130 | + | ||
| 131 | + container.backgroundColor = [rawBackgroundColor colorWithAlphaComponent:0]; | ||
| 132 | + | ||
| 133 | + startView.contentMode = endView.contentMode; | ||
| 134 | + | ||
| 135 | + CGAffineTransform transform = startView.transform; | ||
| 136 | + UIDeviceOrientation statusBarOrientation = (UIDeviceOrientation)UIApplication.sharedApplication.statusBarOrientation; | ||
| 137 | + if (orientation != statusBarOrientation) { | ||
| 138 | + transform = CGAffineTransformRotate(transform, YBIBRotationAngle(orientation, statusBarOrientation)); | ||
| 139 | + } | ||
| 140 | + | ||
| 141 | + if ([startView isKindOfClass:UIImageView.self]) { | ||
| 142 | + startView.frame = endFrame; | ||
| 143 | + startView.transform = transform; | ||
| 144 | + } else { | ||
| 145 | + CGFloat scale = MAX(endFrame.size.width / startFrame.size.width, endFrame.size.height / startFrame.size.height); | ||
| 146 | + startView.center = CGPointMake(endFrame.size.width * startView.layer.anchorPoint.x + endFrame.origin.x, endFrame.size.height * startView.layer.anchorPoint.y + endFrame.origin.y); | ||
| 147 | + startView.transform = CGAffineTransformScale(transform, scale, scale); | ||
| 148 | + } | ||
| 149 | + | ||
| 150 | + } completion:^(BOOL finished) { | ||
| 151 | + completion(); | ||
| 152 | + container.backgroundColor = rawBackgroundColor; | ||
| 153 | + }]; | ||
| 154 | + | ||
| 155 | + } | ||
| 156 | + break; | ||
| 157 | + } | ||
| 158 | +} | ||
| 159 | + | ||
| 160 | +#pragma mark - private | ||
| 161 | + | ||
| 162 | +- (UIImageView *)imageViewAssimilateToView:(nullable __kindof UIView *)view { | ||
| 163 | + UIImageView *animateImageView = [UIImageView new]; | ||
| 164 | + if ([view isKindOfClass:UIImageView.self]) { | ||
| 165 | + animateImageView.contentMode = view.contentMode; | ||
| 166 | + } else { | ||
| 167 | + animateImageView.contentMode = UIViewContentModeScaleAspectFill; | ||
| 168 | + } | ||
| 169 | + animateImageView.layer.masksToBounds = view.layer.masksToBounds; | ||
| 170 | + animateImageView.layer.cornerRadius = view.layer.cornerRadius; | ||
| 171 | + animateImageView.layer.backgroundColor = view.layer.backgroundColor; | ||
| 172 | + return animateImageView; | ||
| 173 | +} | ||
| 174 | + | ||
| 175 | +@end |
CNLiveOrganizationValidationModule/Classes/CNLiveOrganizationValidationModule/YBImageBrowser/Base/YBIBCollectionView.h
0 → 100644
| 1 | +// | ||
| 2 | +// YBIBCollectionView.h | ||
| 3 | +// YBImageBrowserDemo | ||
| 4 | +// | ||
| 5 | +// Created by 波儿菜 on 2019/6/6. | ||
| 6 | +// Copyright © 2019 波儿菜. All rights reserved. | ||
| 7 | +// | ||
| 8 | + | ||
| 9 | +#import "YBIBCollectionViewLayout.h" | ||
| 10 | + | ||
| 11 | +NS_ASSUME_NONNULL_BEGIN | ||
| 12 | + | ||
| 13 | +@interface YBIBCollectionView : UICollectionView | ||
| 14 | + | ||
| 15 | +@property (nonatomic, strong, readonly) YBIBCollectionViewLayout *layout; | ||
| 16 | + | ||
| 17 | +- (NSString *)reuseIdentifierForCellClass:(Class)cellClass; | ||
| 18 | + | ||
| 19 | +- (nullable UICollectionViewCell *)centerCell; | ||
| 20 | + | ||
| 21 | +- (void)scrollToPage:(NSInteger)page; | ||
| 22 | + | ||
| 23 | +@end | ||
| 24 | + | ||
| 25 | +NS_ASSUME_NONNULL_END |
CNLiveOrganizationValidationModule/Classes/CNLiveOrganizationValidationModule/YBImageBrowser/Base/YBIBCollectionView.m
0 → 100644
| 1 | +// | ||
| 2 | +// YBIBCollectionView.m | ||
| 3 | +// YBImageBrowserDemo | ||
| 4 | +// | ||
| 5 | +// Created by 波儿菜 on 2019/6/6. | ||
| 6 | +// Copyright © 2019 波儿菜. All rights reserved. | ||
| 7 | +// | ||
| 8 | + | ||
| 9 | +#import "YBIBCollectionView.h" | ||
| 10 | + | ||
| 11 | +@implementation YBIBCollectionView { | ||
| 12 | + NSMutableSet *_reuseSet; | ||
| 13 | +} | ||
| 14 | + | ||
| 15 | +#pragma mark - life cycle | ||
| 16 | + | ||
| 17 | +- (instancetype)initWithFrame:(CGRect)frame { | ||
| 18 | + _layout = [YBIBCollectionViewLayout new]; | ||
| 19 | + return [self initWithFrame:frame collectionViewLayout:_layout]; | ||
| 20 | +} | ||
| 21 | + | ||
| 22 | +- (instancetype)initWithFrame:(CGRect)frame collectionViewLayout:(nonnull UICollectionViewLayout *)layout { | ||
| 23 | + self = [super initWithFrame:frame collectionViewLayout:layout]; | ||
| 24 | + if (self) { | ||
| 25 | + self.backgroundColor = [UIColor clearColor]; | ||
| 26 | + self.pagingEnabled = YES; | ||
| 27 | + self.showsHorizontalScrollIndicator = NO; | ||
| 28 | + self.showsVerticalScrollIndicator = NO; | ||
| 29 | + self.alwaysBounceVertical = NO; | ||
| 30 | + self.alwaysBounceHorizontal = NO; | ||
| 31 | + self.decelerationRate = UIScrollViewDecelerationRateFast; | ||
| 32 | + if (@available(iOS 11.0, *)) { | ||
| 33 | + self.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever; | ||
| 34 | + } | ||
| 35 | + _reuseSet = [NSMutableSet set]; | ||
| 36 | + } | ||
| 37 | + return self; | ||
| 38 | +} | ||
| 39 | + | ||
| 40 | +#pragma mark - public | ||
| 41 | + | ||
| 42 | +- (NSString *)reuseIdentifierForCellClass:(Class)cellClass { | ||
| 43 | + NSString *identifier = NSStringFromClass(cellClass); | ||
| 44 | + if (![_reuseSet containsObject:identifier]) { | ||
| 45 | + NSString *path = [[NSBundle mainBundle] pathForResource:identifier ofType:@"nib"]; | ||
| 46 | + if (path) { | ||
| 47 | + [self registerNib:[UINib nibWithNibName:identifier bundle:nil] forCellWithReuseIdentifier:identifier]; | ||
| 48 | + } else { | ||
| 49 | + [self registerClass:cellClass forCellWithReuseIdentifier:identifier]; | ||
| 50 | + } | ||
| 51 | + [_reuseSet addObject:identifier]; | ||
| 52 | + } | ||
| 53 | + return identifier; | ||
| 54 | +} | ||
| 55 | + | ||
| 56 | +- (UICollectionViewCell *)centerCell { | ||
| 57 | + NSArray<UICollectionViewCell *> *cells = [self visibleCells]; | ||
| 58 | + if (cells.count == 0) return nil; | ||
| 59 | + | ||
| 60 | + UICollectionViewCell *res = cells[0]; | ||
| 61 | + CGFloat centerX = self.contentOffset.x + (self.bounds.size.width / 2.0); | ||
| 62 | + for (int i = 1; i < cells.count; ++i) { | ||
| 63 | + if (ABS(cells[i].center.x - centerX) < ABS(res.center.x - centerX)) { | ||
| 64 | + res = cells[i]; | ||
| 65 | + } | ||
| 66 | + } | ||
| 67 | + return res; | ||
| 68 | +} | ||
| 69 | + | ||
| 70 | +- (void)scrollToPage:(NSInteger)page { | ||
| 71 | + [self setContentOffset:CGPointMake(self.bounds.size.width * page, 0)]; | ||
| 72 | +} | ||
| 73 | + | ||
| 74 | +#pragma mark - hit test | ||
| 75 | + | ||
| 76 | +- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event { | ||
| 77 | + UIView *view = [super hitTest:point withEvent:event]; | ||
| 78 | + // When the hit-test view is 'UISlider', set '_scrollEnabled' to 'NO', avoid gesture conflicts. | ||
| 79 | + self.scrollEnabled = ![view isKindOfClass:UISlider.class]; | ||
| 80 | + return view; | ||
| 81 | +} | ||
| 82 | + | ||
| 83 | +@end |
CNLiveOrganizationValidationModule/Classes/CNLiveOrganizationValidationModule/YBImageBrowser/Base/YBIBCollectionViewLayout.h
0 → 100644
| 1 | +// | ||
| 2 | +// YBIBCollectionViewLayout.h | ||
| 3 | +// YBImageBrowserDemo | ||
| 4 | +// | ||
| 5 | +// Created by 杨少 on 2018/4/17. | ||
| 6 | +// Copyright © 2018年 波儿菜. All rights reserved. | ||
| 7 | +// | ||
| 8 | + | ||
| 9 | +#import <UIKit/UIKit.h> | ||
| 10 | + | ||
| 11 | +@interface YBIBCollectionViewLayout : UICollectionViewFlowLayout | ||
| 12 | + | ||
| 13 | +@property (nonatomic, assign) CGFloat distanceBetweenPages; | ||
| 14 | + | ||
| 15 | +@end | ||
| 16 | + |
CNLiveOrganizationValidationModule/Classes/CNLiveOrganizationValidationModule/YBImageBrowser/Base/YBIBCollectionViewLayout.m
0 → 100644
| 1 | +// | ||
| 2 | +// YBIBCollectionViewLayout.m | ||
| 3 | +// YBImageBrowserDemo | ||
| 4 | +// | ||
| 5 | +// Created by 杨少 on 2018/4/17. | ||
| 6 | +// Copyright © 2018年 波儿菜. All rights reserved. | ||
| 7 | +// | ||
| 8 | + | ||
| 9 | +#import "YBIBCollectionViewLayout.h" | ||
| 10 | + | ||
| 11 | +@implementation YBIBCollectionViewLayout | ||
| 12 | + | ||
| 13 | +- (instancetype)init { | ||
| 14 | + self = [super init]; | ||
| 15 | + if (self) { | ||
| 16 | + self.minimumLineSpacing = 0; | ||
| 17 | + self.minimumInteritemSpacing = 0; | ||
| 18 | + self.sectionInset = UIEdgeInsetsZero; | ||
| 19 | + self.scrollDirection = UICollectionViewScrollDirectionHorizontal; | ||
| 20 | + _distanceBetweenPages = 20; | ||
| 21 | + } | ||
| 22 | + return self; | ||
| 23 | +} | ||
| 24 | + | ||
| 25 | +- (void)prepareLayout { | ||
| 26 | + [super prepareLayout]; | ||
| 27 | + self.itemSize = self.collectionView.bounds.size; | ||
| 28 | +} | ||
| 29 | + | ||
| 30 | +- (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect { | ||
| 31 | + NSArray<UICollectionViewLayoutAttributes *> *layoutAttsArray = [[NSArray alloc] initWithArray:[super layoutAttributesForElementsInRect:rect] copyItems:YES]; | ||
| 32 | + CGFloat halfWidth = self.collectionView.bounds.size.width / 2.0; | ||
| 33 | + CGFloat centerX = self.collectionView.contentOffset.x + halfWidth; | ||
| 34 | + [layoutAttsArray enumerateObjectsUsingBlock:^(UICollectionViewLayoutAttributes * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { | ||
| 35 | + obj.center = CGPointMake(obj.center.x + (obj.center.x - centerX) / halfWidth * self.distanceBetweenPages / 2, obj.center.y); | ||
| 36 | + }]; | ||
| 37 | + return layoutAttsArray; | ||
| 38 | +} | ||
| 39 | + | ||
| 40 | +- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds { | ||
| 41 | + return YES; | ||
| 42 | +} | ||
| 43 | + | ||
| 44 | +@end |
CNLiveOrganizationValidationModule/Classes/CNLiveOrganizationValidationModule/YBImageBrowser/Base/YBIBContainerView.h
0 → 100644
| 1 | +// | ||
| 2 | +// YBIBContainerView.h | ||
| 3 | +// YBImageBrowserDemo | ||
| 4 | +// | ||
| 5 | +// Created by 波儿菜 on 2019/7/11. | ||
| 6 | +// Copyright © 2019 杨波. All rights reserved. | ||
| 7 | +// | ||
| 8 | + | ||
| 9 | +#import <UIKit/UIKit.h> | ||
| 10 | + | ||
| 11 | +NS_ASSUME_NONNULL_BEGIN | ||
| 12 | + | ||
| 13 | +@interface YBIBContainerView : UIView | ||
| 14 | + | ||
| 15 | +@end | ||
| 16 | + | ||
| 17 | +NS_ASSUME_NONNULL_END |
CNLiveOrganizationValidationModule/Classes/CNLiveOrganizationValidationModule/YBImageBrowser/Base/YBIBContainerView.m
0 → 100644
| 1 | +// | ||
| 2 | +// YBIBContainerView.m | ||
| 3 | +// YBImageBrowserDemo | ||
| 4 | +// | ||
| 5 | +// Created by 波儿菜 on 2019/7/11. | ||
| 6 | +// Copyright © 2019 杨波. All rights reserved. | ||
| 7 | +// | ||
| 8 | + | ||
| 9 | +#import "YBIBContainerView.h" | ||
| 10 | + | ||
| 11 | +@implementation YBIBContainerView | ||
| 12 | + | ||
| 13 | +- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event { | ||
| 14 | + UIView *originView = [super hitTest:point withEvent:event]; | ||
| 15 | + if ([originView isKindOfClass:self.class]) { | ||
| 16 | + // Continue hit-testing if the view is kind of 'self.class'. | ||
| 17 | + return nil; | ||
| 18 | + } | ||
| 19 | + return originView; | ||
| 20 | +} | ||
| 21 | + | ||
| 22 | +@end |
CNLiveOrganizationValidationModule/Classes/CNLiveOrganizationValidationModule/YBImageBrowser/Base/YBIBDataMediator.h
0 → 100644
| 1 | +// | ||
| 2 | +// YBIBDataMediator.h | ||
| 3 | +// YBImageBrowserDemo | ||
| 4 | +// | ||
| 5 | +// Created by 波儿菜 on 2019/6/6. | ||
| 6 | +// Copyright © 2019 波儿菜. All rights reserved. | ||
| 7 | +// | ||
| 8 | + | ||
| 9 | +#import "YBImageBrowser.h" | ||
| 10 | + | ||
| 11 | +NS_ASSUME_NONNULL_BEGIN | ||
| 12 | + | ||
| 13 | +@interface YBIBDataMediator : NSObject | ||
| 14 | + | ||
| 15 | +- (instancetype)initWithBrowser:(YBImageBrowser *)browser; | ||
| 16 | + | ||
| 17 | +@property (nonatomic, assign) NSUInteger dataCacheCountLimit; | ||
| 18 | + | ||
| 19 | +- (NSInteger)numberOfCells; | ||
| 20 | + | ||
| 21 | +- (id<YBIBDataProtocol>)dataForCellAtIndex:(NSInteger)index; | ||
| 22 | + | ||
| 23 | +- (void)clear; | ||
| 24 | + | ||
| 25 | +@property (nonatomic, assign) NSUInteger preloadCount; | ||
| 26 | + | ||
| 27 | +- (void)preloadWithPage:(NSInteger)page; | ||
| 28 | + | ||
| 29 | +@end | ||
| 30 | + | ||
| 31 | +NS_ASSUME_NONNULL_END |
CNLiveOrganizationValidationModule/Classes/CNLiveOrganizationValidationModule/YBImageBrowser/Base/YBIBDataMediator.m
0 → 100644
| 1 | +// | ||
| 2 | +// YBIBDataMediator.m | ||
| 3 | +// YBImageBrowserDemo | ||
| 4 | +// | ||
| 5 | +// Created by 波儿菜 on 2019/6/6. | ||
| 6 | +// Copyright © 2019 波儿菜. All rights reserved. | ||
| 7 | +// | ||
| 8 | + | ||
| 9 | +#import "YBIBDataMediator.h" | ||
| 10 | +#import "YBImageBrowser+Internal.h" | ||
| 11 | + | ||
| 12 | +@implementation YBIBDataMediator { | ||
| 13 | + __weak YBImageBrowser *_browser; | ||
| 14 | + NSCache<NSNumber *, id<YBIBDataProtocol>> *_dataCache; | ||
| 15 | +} | ||
| 16 | + | ||
| 17 | +#pragma mark - life cycle | ||
| 18 | + | ||
| 19 | +- (instancetype)initWithBrowser:(YBImageBrowser *)browser { | ||
| 20 | + if (self = [super init]) { | ||
| 21 | + _browser = browser; | ||
| 22 | + _dataCache = [NSCache new]; | ||
| 23 | + } | ||
| 24 | + return self; | ||
| 25 | +} | ||
| 26 | + | ||
| 27 | +#pragma mark - public | ||
| 28 | + | ||
| 29 | +- (NSInteger)numberOfCells { | ||
| 30 | + return _browser.dataSource ? [_browser.dataSource yb_numberOfCellsInImageBrowser:_browser] : _browser.dataSourceArray.count; | ||
| 31 | +} | ||
| 32 | + | ||
| 33 | +- (id<YBIBDataProtocol>)dataForCellAtIndex:(NSInteger)index { | ||
| 34 | + if (index < 0 || index > self.numberOfCells - 1) return nil; | ||
| 35 | + | ||
| 36 | + id<YBIBDataProtocol> data = [_dataCache objectForKey:@(index)]; | ||
| 37 | + if (!data) { | ||
| 38 | + data = _browser.dataSource ? [_browser.dataSource yb_imageBrowser:_browser dataForCellAtIndex:index] : _browser.dataSourceArray[index]; | ||
| 39 | + [_dataCache setObject:data forKey:@(index)]; | ||
| 40 | + [_browser implementGetBaseInfoProtocol:data]; | ||
| 41 | + } | ||
| 42 | + return data; | ||
| 43 | +} | ||
| 44 | + | ||
| 45 | +- (void)clear { | ||
| 46 | + [_dataCache removeAllObjects]; | ||
| 47 | +} | ||
| 48 | + | ||
| 49 | +- (void)preloadWithPage:(NSInteger)page { | ||
| 50 | + if (_preloadCount == 0) return; | ||
| 51 | + | ||
| 52 | + NSInteger left = -(_preloadCount / 2), right = _preloadCount - ABS(left); | ||
| 53 | + for (NSInteger i = left; i <= right; ++i) { | ||
| 54 | + if (i == 0) continue; | ||
| 55 | + id<YBIBDataProtocol> targetData = [self dataForCellAtIndex:page + i]; | ||
| 56 | + if ([targetData respondsToSelector:@selector(yb_preload)]) { | ||
| 57 | + [targetData yb_preload]; | ||
| 58 | + } | ||
| 59 | + } | ||
| 60 | +} | ||
| 61 | + | ||
| 62 | +#pragma mark - getters & setters | ||
| 63 | + | ||
| 64 | +- (void)setDataCacheCountLimit:(NSUInteger)dataCacheCountLimit { | ||
| 65 | + _dataCacheCountLimit = dataCacheCountLimit; | ||
| 66 | + _dataCache.countLimit = dataCacheCountLimit; | ||
| 67 | +} | ||
| 68 | + | ||
| 69 | +@end |
CNLiveOrganizationValidationModule/Classes/CNLiveOrganizationValidationModule/YBImageBrowser/Base/YBIBScreenRotationHandler.h
0 → 100644
| 1 | +// | ||
| 2 | +// YBIBScreenRotationHandler.h | ||
| 3 | +// YBImageBrowserDemo | ||
| 4 | +// | ||
| 5 | +// Created by 波儿菜 on 2019/6/8. | ||
| 6 | +// Copyright © 2019 波儿菜. All rights reserved. | ||
| 7 | +// | ||
| 8 | + | ||
| 9 | +#import "YBImageBrowser.h" | ||
| 10 | + | ||
| 11 | +NS_ASSUME_NONNULL_BEGIN | ||
| 12 | + | ||
| 13 | +@interface YBIBScreenRotationHandler : NSObject | ||
| 14 | + | ||
| 15 | +- (instancetype)initWithBrowser:(YBImageBrowser *)browser; | ||
| 16 | + | ||
| 17 | +- (void)startObserveStatusBarOrientation; | ||
| 18 | + | ||
| 19 | +- (void)startObserveDeviceOrientation; | ||
| 20 | + | ||
| 21 | +- (void)clear; | ||
| 22 | + | ||
| 23 | +- (void)configContainerSize:(CGSize)size; | ||
| 24 | + | ||
| 25 | +- (CGSize)containerSizeWithOrientation:(UIDeviceOrientation)orientation; | ||
| 26 | + | ||
| 27 | +@property (nonatomic, assign, readonly, getter=isRotating) BOOL rotating; | ||
| 28 | + | ||
| 29 | +@property (nonatomic, assign, readonly) UIDeviceOrientation currentOrientation; | ||
| 30 | + | ||
| 31 | +@property (nonatomic, assign) UIInterfaceOrientationMask supportedOrientations; | ||
| 32 | + | ||
| 33 | +@property (nonatomic, assign) NSTimeInterval rotationDuration; | ||
| 34 | + | ||
| 35 | +@end | ||
| 36 | + | ||
| 37 | +NS_ASSUME_NONNULL_END |
CNLiveOrganizationValidationModule/Classes/CNLiveOrganizationValidationModule/YBImageBrowser/Base/YBIBScreenRotationHandler.m
0 → 100644
| 1 | +// | ||
| 2 | +// YBIBScreenRotationHandler.m | ||
| 3 | +// YBImageBrowserDemo | ||
| 4 | +// | ||
| 5 | +// Created by 波儿菜 on 2019/6/8. | ||
| 6 | +// Copyright © 2019 波儿菜. All rights reserved. | ||
| 7 | +// | ||
| 8 | + | ||
| 9 | +#import "YBIBScreenRotationHandler.h" | ||
| 10 | +#import "YBIBUtilities.h" | ||
| 11 | +#import "YBIBCellProtocol.h" | ||
| 12 | +#import "YBImageBrowser+Internal.h" | ||
| 13 | + | ||
| 14 | +BOOL YBIBValidDeviceOrientation(UIDeviceOrientation orientation) { | ||
| 15 | + static NSSet *validSet; | ||
| 16 | + static dispatch_once_t onceToken; | ||
| 17 | + dispatch_once(&onceToken, ^{ | ||
| 18 | + validSet = [NSSet setWithObjects:@(UIDeviceOrientationPortrait), @(UIDeviceOrientationPortraitUpsideDown), @(UIDeviceOrientationLandscapeLeft), @(UIDeviceOrientationLandscapeRight), nil]; | ||
| 19 | + }); | ||
| 20 | + return [validSet containsObject:@(orientation)]; | ||
| 21 | +} | ||
| 22 | + | ||
| 23 | +CGFloat YBIBRotationAngle(UIDeviceOrientation startOrientation, UIDeviceOrientation endOrientation) { | ||
| 24 | + static NSDictionary<NSNumber*, NSNumber*> *angleMap; | ||
| 25 | + static dispatch_once_t onceToken; | ||
| 26 | + dispatch_once(&onceToken, ^{ | ||
| 27 | + angleMap = @{@(UIDeviceOrientationPortrait):@(0), @(UIDeviceOrientationPortraitUpsideDown):@(M_PI), @(UIDeviceOrientationLandscapeLeft):@(M_PI_2), @(UIDeviceOrientationLandscapeRight): @(-M_PI_2)}; | ||
| 28 | + }); | ||
| 29 | + NSNumber *start = angleMap[@(startOrientation)], *end = angleMap[@(endOrientation)]; | ||
| 30 | + CGFloat res = CGFLOAT_IS_DOUBLE ? end.doubleValue - start.doubleValue : end.floatValue - start.floatValue; | ||
| 31 | + if (ABS(res) > M_PI) { | ||
| 32 | + return res > 0 ? res - M_PI * 2 : M_PI * 2 + res; | ||
| 33 | + } | ||
| 34 | + return res; | ||
| 35 | +} | ||
| 36 | + | ||
| 37 | + | ||
| 38 | +static NSUInteger const kMaskNull = 10000; | ||
| 39 | + | ||
| 40 | +@interface YBIBScreenRotationHandler () | ||
| 41 | +@property (nonatomic, assign) BOOL rotating; | ||
| 42 | +@property (nonatomic, assign) UIDeviceOrientation currentOrientation; | ||
| 43 | +@end | ||
| 44 | + | ||
| 45 | +@implementation YBIBScreenRotationHandler { | ||
| 46 | + __weak YBImageBrowser *_browser; | ||
| 47 | + CGSize _verticalContainerSize; | ||
| 48 | + CGSize _horizontalContainerSize; | ||
| 49 | + NSInteger _recordPage; | ||
| 50 | +} | ||
| 51 | + | ||
| 52 | +#pragma mark - life cycle | ||
| 53 | + | ||
| 54 | +- (void)dealloc { | ||
| 55 | + [self clear]; | ||
| 56 | +} | ||
| 57 | + | ||
| 58 | +- (instancetype)initWithBrowser:(YBImageBrowser *)browser { | ||
| 59 | + if (self = [super init]) { | ||
| 60 | + _browser = browser; | ||
| 61 | + _rotating = NO; | ||
| 62 | + _supportedOrientations = UIInterfaceOrientationMaskAllButUpsideDown; | ||
| 63 | + _rotationDuration = 0.25; | ||
| 64 | + } | ||
| 65 | + return self; | ||
| 66 | +} | ||
| 67 | + | ||
| 68 | +#pragma mark - public | ||
| 69 | + | ||
| 70 | +- (void)startObserveStatusBarOrientation { | ||
| 71 | + self.currentOrientation = (UIDeviceOrientation)UIApplication.sharedApplication.statusBarOrientation; | ||
| 72 | + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidChangedStatusBarOrientationNotification:) name:UIApplicationDidChangeStatusBarOrientationNotification object:nil]; | ||
| 73 | + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationWillChangeStatusBarOrientationNotification:) name:UIApplicationWillChangeStatusBarOrientationNotification object:nil]; | ||
| 74 | +} | ||
| 75 | + | ||
| 76 | +- (void)startObserveDeviceOrientation { | ||
| 77 | + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(deviceOrientationDidChangeNotification:) name:UIDeviceOrientationDidChangeNotification object:nil]; | ||
| 78 | +} | ||
| 79 | + | ||
| 80 | +- (void)clear { | ||
| 81 | + [[NSNotificationCenter defaultCenter] removeObserver:self]; | ||
| 82 | +} | ||
| 83 | + | ||
| 84 | +- (void)configContainerSize:(CGSize)size { | ||
| 85 | + if (UIDeviceOrientationIsLandscape(self.currentOrientation)) { | ||
| 86 | + // Now is horizontal. | ||
| 87 | + _verticalContainerSize = CGSizeMake(size.height, size.width); | ||
| 88 | + _horizontalContainerSize = size; | ||
| 89 | + } else { | ||
| 90 | + // Now is vertical. | ||
| 91 | + _verticalContainerSize = size; | ||
| 92 | + _horizontalContainerSize = CGSizeMake(size.height, size.width); | ||
| 93 | + } | ||
| 94 | +} | ||
| 95 | + | ||
| 96 | +- (CGSize)containerSizeWithOrientation:(UIDeviceOrientation)orientation { | ||
| 97 | + return UIDeviceOrientationIsLandscape(orientation) ? _horizontalContainerSize : _verticalContainerSize; | ||
| 98 | +} | ||
| 99 | + | ||
| 100 | +#pragma mark - private | ||
| 101 | + | ||
| 102 | +- (BOOL)supportedOfOrientation:(UIDeviceOrientation)orientation { | ||
| 103 | + if (!YBIBValidDeviceOrientation(orientation)) return NO; | ||
| 104 | + NSMutableSet *set = [NSMutableSet set]; | ||
| 105 | + if (_supportedOrientations & UIInterfaceOrientationMaskPortrait) [set addObject:@(UIDeviceOrientationPortrait)]; | ||
| 106 | + if (_supportedOrientations & UIInterfaceOrientationMaskPortraitUpsideDown) [set addObject:@(UIDeviceOrientationPortraitUpsideDown)]; | ||
| 107 | + if (_supportedOrientations & UIInterfaceOrientationMaskLandscapeRight) [set addObject:@(UIDeviceOrientationLandscapeLeft)]; | ||
| 108 | + if (_supportedOrientations & UIInterfaceOrientationMaskLandscapeLeft) [set addObject:@(UIDeviceOrientationLandscapeRight)]; | ||
| 109 | + return [set containsObject:@(orientation)]; | ||
| 110 | +} | ||
| 111 | + | ||
| 112 | +- (BOOL)supportedOnlyOneSystemOrientation { | ||
| 113 | + UIInterfaceOrientationMask mask = [self supportSystemOrientationMask]; | ||
| 114 | + return mask == (mask & (-mask)); | ||
| 115 | +} | ||
| 116 | + | ||
| 117 | +- (void)orientationWillChangeWithExpectOrientation:(UIDeviceOrientation)orientation centerCell:(UICollectionViewCell<YBIBCellProtocol> *)centerCell { | ||
| 118 | + if ([centerCell respondsToSelector:@selector(yb_orientationWillChangeWithExpectOrientation:)]) { | ||
| 119 | + [centerCell yb_orientationWillChangeWithExpectOrientation:orientation]; | ||
| 120 | + } | ||
| 121 | + for (id<YBIBToolViewHandler> handler in _browser.toolViewHandlers) { | ||
| 122 | + if ([handler respondsToSelector:@selector(yb_orientationWillChangeWithExpectOrientation:)]) { | ||
| 123 | + [handler yb_orientationWillChangeWithExpectOrientation:orientation]; | ||
| 124 | + } | ||
| 125 | + } | ||
| 126 | +} | ||
| 127 | + | ||
| 128 | +- (void)orientationChangeAnimationWithExpectOrientation:(UIDeviceOrientation)orientation centerCell:(UICollectionViewCell<YBIBCellProtocol> *)centerCell { | ||
| 129 | + if ([centerCell respondsToSelector:@selector(yb_orientationChangeAnimationWithExpectOrientation:)]) { | ||
| 130 | + [centerCell yb_orientationChangeAnimationWithExpectOrientation:orientation]; | ||
| 131 | + [centerCell layoutIfNeeded]; // Compatible with autolayout. | ||
| 132 | + } | ||
| 133 | + for (id<YBIBToolViewHandler> handler in _browser.toolViewHandlers) { | ||
| 134 | + if ([handler respondsToSelector:@selector(yb_orientationChangeAnimationWithExpectOrientation:)]) { | ||
| 135 | + [handler yb_orientationChangeAnimationWithExpectOrientation:orientation]; | ||
| 136 | + } | ||
| 137 | + } | ||
| 138 | +} | ||
| 139 | + | ||
| 140 | +- (void)orientationDidChangedWithOrientation:(UIDeviceOrientation)orientation centerCell:(UICollectionViewCell<YBIBCellProtocol> *)centerCell { | ||
| 141 | + if ([centerCell respondsToSelector:@selector(yb_orientationDidChangedWithOrientation:)]) { | ||
| 142 | + [centerCell yb_orientationDidChangedWithOrientation:orientation]; | ||
| 143 | + } | ||
| 144 | + for (id<YBIBToolViewHandler> handler in _browser.toolViewHandlers) { | ||
| 145 | + if ([handler respondsToSelector:@selector(yb_orientationDidChangedWithOrientation:)]) { | ||
| 146 | + [handler yb_orientationDidChangedWithOrientation:orientation]; | ||
| 147 | + } | ||
| 148 | + } | ||
| 149 | +} | ||
| 150 | + | ||
| 151 | +#pragma mark - event | ||
| 152 | + | ||
| 153 | +- (void)deviceOrientationDidChangeNotification:(NSNotification *)note { | ||
| 154 | + if (![self supportedOnlyOneSystemOrientation]) return; | ||
| 155 | + if (_browser.isTransitioning || self.rotating) return; | ||
| 156 | + | ||
| 157 | + UIDeviceOrientation expectOrientation = [UIDevice currentDevice].orientation; | ||
| 158 | + if (expectOrientation == self.currentOrientation || ![self supportedOfOrientation:expectOrientation]) return; | ||
| 159 | + | ||
| 160 | + self.rotating = YES; | ||
| 161 | + | ||
| 162 | + // Align. | ||
| 163 | + [_browser.collectionView scrollToPage:_browser.currentPage]; | ||
| 164 | + // Record current page number before transforming. | ||
| 165 | + NSInteger currentPage = _browser.currentPage; | ||
| 166 | + | ||
| 167 | + UIDeviceOrientation statusBarOrientation = (UIDeviceOrientation)UIApplication.sharedApplication.statusBarOrientation; | ||
| 168 | + CGFloat angleStatusBarToExpect = YBIBRotationAngle(statusBarOrientation, expectOrientation); | ||
| 169 | + CGFloat angleCurrentToExpect = YBIBRotationAngle(_currentOrientation, expectOrientation); | ||
| 170 | + CGRect expectBounds = (CGRect){CGPointZero, [self containerSizeWithOrientation:expectOrientation]}; | ||
| 171 | + UICollectionViewCell<YBIBCellProtocol> *centerCell = (UICollectionViewCell<YBIBCellProtocol> *)self->_browser.collectionView.centerCell; | ||
| 172 | + // Animate smoothly if bigger rotation angle. | ||
| 173 | + NSTimeInterval duration = self.rotationDuration * (ABS(angleCurrentToExpect) > M_PI_2 ? 2 : 1); | ||
| 174 | + | ||
| 175 | + // 'collectionView' transformation. | ||
| 176 | + self->_browser.collectionView.bounds = expectBounds; | ||
| 177 | + self->_browser.collectionView.transform = CGAffineTransformMakeRotation(angleStatusBarToExpect); | ||
| 178 | + centerCell.contentView.transform = CGAffineTransformMakeRotation(-angleCurrentToExpect); | ||
| 179 | + | ||
| 180 | + // Reset to prevent the page number change after transforming. | ||
| 181 | + [self->_browser.collectionView scrollToPage:currentPage]; | ||
| 182 | + | ||
| 183 | + [self orientationWillChangeWithExpectOrientation:expectOrientation centerCell:centerCell]; | ||
| 184 | + | ||
| 185 | + [UIView animateWithDuration:duration delay:0 options:UIViewAnimationOptionCurveEaseInOut animations:^{ | ||
| 186 | + | ||
| 187 | + // Maybe the internal UI need to transform. | ||
| 188 | + [self orientationChangeAnimationWithExpectOrientation:expectOrientation centerCell:centerCell]; | ||
| 189 | + | ||
| 190 | + centerCell.contentView.bounds = expectBounds; | ||
| 191 | + centerCell.contentView.transform = CGAffineTransformIdentity; | ||
| 192 | + | ||
| 193 | + self->_browser.containerView.bounds = expectBounds; | ||
| 194 | + self->_browser.containerView.transform = CGAffineTransformMakeRotation(angleStatusBarToExpect); | ||
| 195 | + | ||
| 196 | + } completion:^(BOOL finished) { | ||
| 197 | + self.currentOrientation = expectOrientation; | ||
| 198 | + self.rotating = NO; | ||
| 199 | + | ||
| 200 | + [self orientationDidChangedWithOrientation:expectOrientation centerCell:centerCell]; | ||
| 201 | + }]; | ||
| 202 | +} | ||
| 203 | + | ||
| 204 | +- (void)applicationWillChangeStatusBarOrientationNotification:(NSNotification *)noti { | ||
| 205 | + if ([self supportedOnlyOneSystemOrientation]) return; | ||
| 206 | + | ||
| 207 | + self.rotating = YES; | ||
| 208 | + // Record current page number before transforming. | ||
| 209 | + _recordPage = _browser.currentPage; | ||
| 210 | + | ||
| 211 | + UICollectionViewCell<YBIBCellProtocol> *centerCell = (UICollectionViewCell<YBIBCellProtocol> *)self->_browser.collectionView.centerCell; | ||
| 212 | + UIDeviceOrientation expectOrientation = ((NSNumber *)noti.userInfo[UIApplicationStatusBarOrientationUserInfoKey]).integerValue; | ||
| 213 | + [self orientationWillChangeWithExpectOrientation:expectOrientation centerCell:centerCell]; | ||
| 214 | +} | ||
| 215 | + | ||
| 216 | +- (void)applicationDidChangedStatusBarOrientationNotification:(NSNotification *)noti { | ||
| 217 | + if ([self supportedOnlyOneSystemOrientation]) return; | ||
| 218 | + | ||
| 219 | + UIDeviceOrientation expectOrientation = (UIDeviceOrientation)UIApplication.sharedApplication.statusBarOrientation; | ||
| 220 | + UICollectionViewCell<YBIBCellProtocol> *centerCell = (UICollectionViewCell<YBIBCellProtocol> *)self->_browser.collectionView.centerCell; | ||
| 221 | + | ||
| 222 | + [self orientationChangeAnimationWithExpectOrientation:expectOrientation centerCell:centerCell]; | ||
| 223 | + | ||
| 224 | + CGRect expectBounds = (CGRect){CGPointZero, [self containerSizeWithOrientation:expectOrientation]}; | ||
| 225 | + self->_browser.collectionView.layout.itemSize = expectBounds.size; | ||
| 226 | + | ||
| 227 | + // Reset to prevent the page number change after transforming. | ||
| 228 | + [_browser.collectionView scrollToPage:_recordPage]; | ||
| 229 | + | ||
| 230 | + self.currentOrientation = expectOrientation; | ||
| 231 | + self.rotating = NO; | ||
| 232 | + | ||
| 233 | + [self orientationDidChangedWithOrientation:expectOrientation centerCell:centerCell]; | ||
| 234 | +} | ||
| 235 | + | ||
| 236 | +#pragma mark - getters & setters | ||
| 237 | + | ||
| 238 | +- (void)setRotating:(BOOL)rotating { | ||
| 239 | + _rotating = rotating; | ||
| 240 | + _browser.containerView.userInteractionEnabled = !rotating; | ||
| 241 | + _browser.collectionView.userInteractionEnabled = !rotating; | ||
| 242 | + _browser.collectionView.panGestureRecognizer.enabled = !rotating; | ||
| 243 | +} | ||
| 244 | + | ||
| 245 | +#pragma mark - calculate supported orientation of system | ||
| 246 | + | ||
| 247 | +- (UIInterfaceOrientationMask)supportSystemOrientationMask { | ||
| 248 | + UIInterfaceOrientationMask limitMask = 0; | ||
| 249 | + // IphoneX series do not support UIInterfaceOrientationMaskPortraitUpsideDown, except selector '-application:supportedInterfaceOrientationsForWindow:' of '[UIApplication sharedApplication].delegate' return 0. Maybe it is BUG of Apple. | ||
| 250 | + BOOL ignoreUpsideDownIfIphoneX = YES; | ||
| 251 | + UIInterfaceOrientationMask delegateMask = [self maskOfApplicationDelegate]; | ||
| 252 | + if (delegateMask != kMaskNull) { | ||
| 253 | + if (delegateMask == 0) { | ||
| 254 | + // Apple do. | ||
| 255 | + limitMask = UIInterfaceOrientationMaskAll; | ||
| 256 | + ignoreUpsideDownIfIphoneX = NO; | ||
| 257 | + } else { | ||
| 258 | + limitMask = delegateMask; | ||
| 259 | + } | ||
| 260 | + } else { | ||
| 261 | + // Lower priority. | ||
| 262 | + limitMask = [self maskOfInfoPlist]; | ||
| 263 | + } | ||
| 264 | + | ||
| 265 | + UIInterfaceOrientationMask supportMask = limitMask & [self maskOfViewController]; | ||
| 266 | + | ||
| 267 | + if (ignoreUpsideDownIfIphoneX && YBIBIsIphoneXSeries() && (supportMask & UIInterfaceOrientationMaskPortraitUpsideDown)) { | ||
| 268 | + supportMask ^= UIInterfaceOrientationMaskPortraitUpsideDown; | ||
| 269 | + } | ||
| 270 | + return supportMask; | ||
| 271 | +} | ||
| 272 | + | ||
| 273 | +- (UIInterfaceOrientationMask)maskOfInfoPlist { | ||
| 274 | + // 'Info.plist' will not change in a process. | ||
| 275 | + static UIInterfaceOrientationMask mask = 0; | ||
| 276 | + static dispatch_once_t onceToken; | ||
| 277 | + dispatch_once(&onceToken, ^{ | ||
| 278 | + NSString *path = [[NSBundle mainBundle] pathForResource:@"Info" ofType:@"plist"]; | ||
| 279 | + NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithContentsOfFile:path]; | ||
| 280 | + NSArray *array = dict[@"UISupportedInterfaceOrientations"]; | ||
| 281 | + NSSet *set = [NSSet setWithArray:array]; | ||
| 282 | + if ([set containsObject:@"UIInterfaceOrientationPortrait"]) mask |= UIInterfaceOrientationMaskPortrait; | ||
| 283 | + if ([set containsObject:@"UIInterfaceOrientationLandscapeRight"]) mask |= UIInterfaceOrientationMaskLandscapeRight; | ||
| 284 | + if ([set containsObject:@"UIInterfaceOrientationLandscapeLeft"]) mask |= UIInterfaceOrientationMaskLandscapeLeft; | ||
| 285 | + if ([set containsObject:@"UIInterfaceOrientationPortraitUpsideDown"]) mask |= UIInterfaceOrientationMaskPortraitUpsideDown; | ||
| 286 | + }); | ||
| 287 | + return mask == 0 ? kMaskNull : mask; | ||
| 288 | +} | ||
| 289 | + | ||
| 290 | +- (UIInterfaceOrientationMask)maskOfApplicationDelegate { | ||
| 291 | + UIInterfaceOrientationMask mask = kMaskNull; | ||
| 292 | + id<UIApplicationDelegate> delegate = [UIApplication sharedApplication].delegate; | ||
| 293 | + if ([delegate respondsToSelector:@selector(application:supportedInterfaceOrientationsForWindow:)]) { | ||
| 294 | + mask = [delegate application:[UIApplication sharedApplication] supportedInterfaceOrientationsForWindow:_browser.window]; | ||
| 295 | + } | ||
| 296 | + return mask; | ||
| 297 | +} | ||
| 298 | + | ||
| 299 | +- (UIInterfaceOrientationMask)maskOfViewController { | ||
| 300 | + UIInterfaceOrientationMask mask = kMaskNull; | ||
| 301 | + | ||
| 302 | + // Find the UIViewController whitch 'browser' followed. | ||
| 303 | + UIViewController *target = nil; | ||
| 304 | + id next = _browser; | ||
| 305 | + while (next) { | ||
| 306 | + if ([next isKindOfClass:UIViewController.self]) { | ||
| 307 | + target = next; | ||
| 308 | + break; | ||
| 309 | + } | ||
| 310 | + if ([next isKindOfClass:UIWindow.self]) { | ||
| 311 | + target = YBIBTopControllerByWindow(next); | ||
| 312 | + break; | ||
| 313 | + } | ||
| 314 | + next = [next nextResponder]; | ||
| 315 | + } | ||
| 316 | + | ||
| 317 | + // Cover directly. | ||
| 318 | + if (target.tabBarController) { | ||
| 319 | + mask = target.tabBarController.shouldAutorotate ? target.tabBarController.supportedInterfaceOrientations : 0; | ||
| 320 | + } else if (target.navigationController) { | ||
| 321 | + mask = target.navigationController.shouldAutorotate ? target.navigationController.supportedInterfaceOrientations : 0; | ||
| 322 | + } else { | ||
| 323 | + mask = target.shouldAutorotate ? target.supportedInterfaceOrientations : 0; | ||
| 324 | + } | ||
| 325 | + return mask; | ||
| 326 | +} | ||
| 327 | + | ||
| 328 | +@end |
CNLiveOrganizationValidationModule/Classes/CNLiveOrganizationValidationModule/YBImageBrowser/Base/YBImageBrowser+Internal.h
0 → 100644
| 1 | +// | ||
| 2 | +// YBImageBrowser+Internal.h | ||
| 3 | +// YBImageBrowserDemo | ||
| 4 | +// | ||
| 5 | +// Created by 波儿菜 on 2019/7/1. | ||
| 6 | +// Copyright © 2019 杨波. All rights reserved. | ||
| 7 | +// | ||
| 8 | + | ||
| 9 | +#import "YBImageBrowser.h" | ||
| 10 | +#import "YBIBContainerView.h" | ||
| 11 | + | ||
| 12 | +NS_ASSUME_NONNULL_BEGIN | ||
| 13 | + | ||
| 14 | +@interface YBImageBrowser () | ||
| 15 | + | ||
| 16 | +@property (nonatomic, strong) YBIBContainerView *containerView; | ||
| 17 | + | ||
| 18 | +- (void)implementGetBaseInfoProtocol:(id<YBIBGetBaseInfoProtocol>)obj; | ||
| 19 | + | ||
| 20 | +@property (nonatomic, weak, nullable) NSObject *hiddenProjectiveView; | ||
| 21 | + | ||
| 22 | +@end | ||
| 23 | + | ||
| 24 | +NS_ASSUME_NONNULL_END |
CNLiveOrganizationValidationModule/Classes/CNLiveOrganizationValidationModule/YBImageBrowser/Helper/YBIBCopywriter.h
0 → 100644
| 1 | +// | ||
| 2 | +// YBIBCopywriter.h | ||
| 3 | +// YBImageBrowserDemo | ||
| 4 | +// | ||
| 5 | +// Created by 波儿菜 on 2018/9/13. | ||
| 6 | +// Copyright © 2018年 波儿菜. All rights reserved. | ||
| 7 | +// | ||
| 8 | + | ||
| 9 | +#import <Foundation/Foundation.h> | ||
| 10 | + | ||
| 11 | +NS_ASSUME_NONNULL_BEGIN | ||
| 12 | + | ||
| 13 | +typedef NS_ENUM(NSInteger, YBIBCopywriterType) { | ||
| 14 | + /// 简体中文 | ||
| 15 | + YBIBCopywriterTypeSimplifiedChinese, | ||
| 16 | + /// 英文 | ||
| 17 | + YBIBCopywriterTypeEnglish | ||
| 18 | +}; | ||
| 19 | + | ||
| 20 | +/** | ||
| 21 | + 文案管理类 | ||
| 22 | + */ | ||
| 23 | +@interface YBIBCopywriter : NSObject | ||
| 24 | + | ||
| 25 | +/** | ||
| 26 | + 唯一有效单例 | ||
| 27 | + */ | ||
| 28 | ++ (instancetype)sharedCopywriter; | ||
| 29 | + | ||
| 30 | +/// 语言类型 | ||
| 31 | +@property (nonatomic, assign) YBIBCopywriterType type; | ||
| 32 | + | ||
| 33 | +#pragma - 以下文案可更改 | ||
| 34 | + | ||
| 35 | +@property (nonatomic, copy) NSString *videoIsInvalid; | ||
| 36 | + | ||
| 37 | +@property (nonatomic, copy) NSString *videoError; | ||
| 38 | + | ||
| 39 | +@property (nonatomic, copy) NSString *unableToSave; | ||
| 40 | + | ||
| 41 | +@property (nonatomic, copy) NSString *imageIsInvalid; | ||
| 42 | + | ||
| 43 | +@property (nonatomic, copy) NSString *downloadFailed; | ||
| 44 | + | ||
| 45 | +@property (nonatomic, copy) NSString *getPhotoAlbumAuthorizationFailed; | ||
| 46 | + | ||
| 47 | +@property (nonatomic, copy) NSString *saveToPhotoAlbumSuccess; | ||
| 48 | + | ||
| 49 | +@property (nonatomic, copy) NSString *saveToPhotoAlbumFailed; | ||
| 50 | + | ||
| 51 | +@property (nonatomic, copy) NSString *saveToPhotoAlbum; | ||
| 52 | + | ||
| 53 | +@property (nonatomic, copy) NSString *cancel; | ||
| 54 | + | ||
| 55 | +@end | ||
| 56 | + | ||
| 57 | +NS_ASSUME_NONNULL_END |
CNLiveOrganizationValidationModule/Classes/CNLiveOrganizationValidationModule/YBImageBrowser/Helper/YBIBCopywriter.m
0 → 100644
| 1 | +// | ||
| 2 | +// YBIBCopywriter.m | ||
| 3 | +// YBImageBrowserDemo | ||
| 4 | +// | ||
| 5 | +// Created by 波儿菜 on 2018/9/13. | ||
| 6 | +// Copyright © 2018年 波儿菜. All rights reserved. | ||
| 7 | +// | ||
| 8 | + | ||
| 9 | +#import "YBIBCopywriter.h" | ||
| 10 | + | ||
| 11 | +@implementation YBIBCopywriter | ||
| 12 | + | ||
| 13 | +#pragma mark - life cycle | ||
| 14 | + | ||
| 15 | ++ (instancetype)sharedCopywriter { | ||
| 16 | + static YBIBCopywriter *copywriter = nil; | ||
| 17 | + static dispatch_once_t onceToken; | ||
| 18 | + dispatch_once(&onceToken, ^{ | ||
| 19 | + copywriter = [YBIBCopywriter new]; | ||
| 20 | + }); | ||
| 21 | + return copywriter; | ||
| 22 | +} | ||
| 23 | + | ||
| 24 | +- (instancetype)init { | ||
| 25 | + self = [super init]; | ||
| 26 | + if (self) { | ||
| 27 | + _type = YBIBCopywriterTypeSimplifiedChinese; | ||
| 28 | + NSArray *appleLanguages = [[NSUserDefaults standardUserDefaults] objectForKey:@"AppleLanguages"]; | ||
| 29 | + if (appleLanguages && appleLanguages.count > 0) { | ||
| 30 | + NSString *languages = appleLanguages[0]; | ||
| 31 | + if (![languages hasPrefix:@"zh-Hans"]) { | ||
| 32 | + _type = YBIBCopywriterTypeEnglish; | ||
| 33 | + } | ||
| 34 | + } | ||
| 35 | + | ||
| 36 | + [self initCopy]; | ||
| 37 | + } | ||
| 38 | + return self; | ||
| 39 | +} | ||
| 40 | + | ||
| 41 | +#pragma mark - private | ||
| 42 | + | ||
| 43 | +- (void)initCopy { | ||
| 44 | + BOOL en = self.type == YBIBCopywriterTypeEnglish; | ||
| 45 | + | ||
| 46 | + self.videoIsInvalid = en ? @"Video is invalid" : @"视频无效"; | ||
| 47 | + self.videoError = en ? @"Video error" : @"视频错误"; | ||
| 48 | + self.unableToSave = en ? @"Unable to save" : @"无法保存"; | ||
| 49 | + self.imageIsInvalid = en ? @"Image is invalid" : @"图片无效"; | ||
| 50 | + self.downloadFailed = en ? @"Download failed" : @"加载图片失败"; | ||
| 51 | + self.getPhotoAlbumAuthorizationFailed = en ? @"Failed to get album authorization" : @"获取相册权限失败"; | ||
| 52 | + self.saveToPhotoAlbumSuccess = en ? @"Save successful" : @"已保存到系统相册"; | ||
| 53 | + self.saveToPhotoAlbumFailed = en ? @"Save failed" : @"保存失败"; | ||
| 54 | + self.saveToPhotoAlbum = en ? @"Save" : @"保存到相册"; | ||
| 55 | + self.cancel = en ? @"Cancel" : @"取消"; | ||
| 56 | +} | ||
| 57 | + | ||
| 58 | +#pragma mark - public | ||
| 59 | + | ||
| 60 | +- (void)setType:(YBIBCopywriterType)type { | ||
| 61 | + _type = type; | ||
| 62 | + [self initCopy]; | ||
| 63 | +} | ||
| 64 | + | ||
| 65 | +@end |
CNLiveOrganizationValidationModule/Classes/CNLiveOrganizationValidationModule/YBImageBrowser/Helper/YBIBIconManager.h
0 → 100644
| 1 | +// | ||
| 2 | +// YBIBIconManager.h | ||
| 3 | +// YBImageBrowserDemo | ||
| 4 | +// | ||
| 5 | +// Created by 波儿菜 on 2018/8/29. | ||
| 6 | +// Copyright © 2018年 波儿菜. All rights reserved. | ||
| 7 | +// | ||
| 8 | + | ||
| 9 | +#import <UIKit/UIKit.h> | ||
| 10 | + | ||
| 11 | +NS_ASSUME_NONNULL_BEGIN | ||
| 12 | + | ||
| 13 | +@interface UIImage (YBImageBrowser) | ||
| 14 | + | ||
| 15 | +/** | ||
| 16 | + 获取图片便利构造方法 | ||
| 17 | + | ||
| 18 | + @param name 图片名字 | ||
| 19 | + @param bundle 资源对象 | ||
| 20 | + @return 图片实例 | ||
| 21 | + */ | ||
| 22 | ++ (instancetype)ybib_imageNamed:(NSString *)name bundle:(NSBundle *)bundle; | ||
| 23 | + | ||
| 24 | +@end | ||
| 25 | + | ||
| 26 | + | ||
| 27 | +/// 获取图片闭包 | ||
| 28 | +typedef UIImage * _Nullable (^YBIBIconBlock)(void); | ||
| 29 | + | ||
| 30 | +/** | ||
| 31 | + 图标管理类 | ||
| 32 | + */ | ||
| 33 | +@interface YBIBIconManager : NSObject | ||
| 34 | + | ||
| 35 | +/** | ||
| 36 | + 唯一有效单例 | ||
| 37 | + */ | ||
| 38 | ++ (instancetype)sharedManager; | ||
| 39 | + | ||
| 40 | +#pragma - 以下图片可更改 | ||
| 41 | + | ||
| 42 | +/// 基本-加载 | ||
| 43 | +@property (nonatomic, copy) YBIBIconBlock loadingImage; | ||
| 44 | + | ||
| 45 | +/// 工具视图-保存 | ||
| 46 | +@property (nonatomic, copy) YBIBIconBlock toolSaveImage; | ||
| 47 | +/// 工具视图-更多 | ||
| 48 | +@property (nonatomic, copy) YBIBIconBlock toolMoreImage; | ||
| 49 | + | ||
| 50 | +/// 视频-播放 | ||
| 51 | +@property (nonatomic, copy) YBIBIconBlock videoPlayImage; | ||
| 52 | +/// 视频-暂停 | ||
| 53 | +@property (nonatomic, copy) YBIBIconBlock videoPauseImage; | ||
| 54 | +/// 视频-取消 | ||
| 55 | +@property (nonatomic, copy) YBIBIconBlock videoCancelImage; | ||
| 56 | +/// 视频-播放大图 | ||
| 57 | +@property (nonatomic, copy) YBIBIconBlock videoBigPlayImage; | ||
| 58 | +/// 视频-拖动圆点 | ||
| 59 | +@property (nonatomic, copy) YBIBIconBlock videoDragCircleImage; | ||
| 60 | + | ||
| 61 | +@end | ||
| 62 | + | ||
| 63 | +NS_ASSUME_NONNULL_END |
CNLiveOrganizationValidationModule/Classes/CNLiveOrganizationValidationModule/YBImageBrowser/Helper/YBIBIconManager.m
0 → 100644
| 1 | +// | ||
| 2 | +// YBIBIconManager.m | ||
| 3 | +// YBImageBrowserDemo | ||
| 4 | +// | ||
| 5 | +// Created by 波儿菜 on 2018/8/29. | ||
| 6 | +// Copyright © 2018年 波儿菜. All rights reserved. | ||
| 7 | +// | ||
| 8 | + | ||
| 9 | +#import "YBIBIconManager.h" | ||
| 10 | + | ||
| 11 | +// The best order for path scale search. | ||
| 12 | +static NSArray *_NSBundlePreferredScales() { | ||
| 13 | + static NSArray *scales; | ||
| 14 | + static dispatch_once_t onceToken; | ||
| 15 | + dispatch_once(&onceToken, ^{ | ||
| 16 | + CGFloat screenScale = [UIScreen mainScreen].scale; | ||
| 17 | + if (screenScale <= 1) { | ||
| 18 | + scales = @[@1,@2,@3]; | ||
| 19 | + } else if (screenScale <= 2) { | ||
| 20 | + scales = @[@2,@3,@1]; | ||
| 21 | + } else { | ||
| 22 | + scales = @[@3,@2,@1]; | ||
| 23 | + } | ||
| 24 | + }); | ||
| 25 | + return scales; | ||
| 26 | +} | ||
| 27 | + | ||
| 28 | +// Add scale modifier to the file name (without path extension), from @"name" to @"name@2x". | ||
| 29 | +static NSString *_NSStringByAppendingNameScale(NSString *string, CGFloat scale) { | ||
| 30 | + if (!string) return nil; | ||
| 31 | + if (fabs(scale - 1) <= __FLT_EPSILON__ || string.length == 0 || [string hasSuffix:@"/"]) return string.copy; | ||
| 32 | + return [string stringByAppendingFormat:@"@%@x", @(scale)]; | ||
| 33 | +} | ||
| 34 | + | ||
| 35 | +@implementation UIImage (YBImageBrowser) | ||
| 36 | + | ||
| 37 | ++ (instancetype)ybib_imageNamed:(NSString *)name bundle:(NSBundle *)bundle { | ||
| 38 | + if (name.length == 0) return nil; | ||
| 39 | + if ([name hasSuffix:@"/"]) return nil; | ||
| 40 | + | ||
| 41 | + NSString *res = name.stringByDeletingPathExtension; | ||
| 42 | + NSString *ext = name.pathExtension; | ||
| 43 | + NSString *path = nil; | ||
| 44 | + CGFloat scale = 1; | ||
| 45 | + | ||
| 46 | + // If no extension, guess by system supported (same as UIImage). | ||
| 47 | + NSArray *exts = ext.length > 0 ? @[ext] : @[@"", @"png", @"jpeg", @"jpg", @"gif", @"webp", @"apng"]; | ||
| 48 | + NSArray *scales = _NSBundlePreferredScales(); | ||
| 49 | + for (int s = 0; s < scales.count; s++) { | ||
| 50 | + scale = ((NSNumber *)scales[s]).floatValue; | ||
| 51 | + NSString *scaledName = _NSStringByAppendingNameScale(res, scale); | ||
| 52 | + for (NSString *e in exts) { | ||
| 53 | + path = [bundle pathForResource:scaledName ofType:e]; | ||
| 54 | + if (path) break; | ||
| 55 | + } | ||
| 56 | + if (path) break; | ||
| 57 | + } | ||
| 58 | + if (path.length == 0) { | ||
| 59 | + // Assets.xcassets supported. | ||
| 60 | + return [self imageNamed:name]; | ||
| 61 | + } | ||
| 62 | + | ||
| 63 | + NSData *data = [NSData dataWithContentsOfFile:path]; | ||
| 64 | + if (data.length == 0) return nil; | ||
| 65 | + | ||
| 66 | + return [[self alloc] initWithData:data scale:scale]; | ||
| 67 | +} | ||
| 68 | + | ||
| 69 | +@end | ||
| 70 | + | ||
| 71 | + | ||
| 72 | +NSBundle *YBIBDefaultBundle(void) { | ||
| 73 | + static NSBundle *bundle = nil; | ||
| 74 | + static dispatch_once_t onceToken; | ||
| 75 | + dispatch_once(&onceToken, ^{ | ||
| 76 | + NSBundle *_bundle = [NSBundle bundleForClass:NSClassFromString(@"YBImageBrowser")]; | ||
| 77 | + NSString *path = [_bundle pathForResource:@"YBImageBrowser" ofType:@"bundle"]; | ||
| 78 | + bundle = [NSBundle bundleWithPath:path]; | ||
| 79 | + }); | ||
| 80 | + return bundle; | ||
| 81 | +} | ||
| 82 | + | ||
| 83 | +NSBundle *YBIBVideoBundle(void) { | ||
| 84 | + static NSBundle *bundle = nil; | ||
| 85 | + static dispatch_once_t onceToken; | ||
| 86 | + dispatch_once(&onceToken, ^{ | ||
| 87 | + NSBundle *_bundle = [NSBundle bundleForClass:NSClassFromString(@"YBImageBrowser")]; | ||
| 88 | + NSString *path = [_bundle pathForResource:@"YBImageBrowserVideo" ofType:@"bundle"]; | ||
| 89 | + bundle = [NSBundle bundleWithPath:path]; | ||
| 90 | + }); | ||
| 91 | + return bundle; | ||
| 92 | +} | ||
| 93 | + | ||
| 94 | +@implementation YBIBIconManager | ||
| 95 | + | ||
| 96 | ++ (instancetype)sharedManager { | ||
| 97 | + static YBIBIconManager *manager = nil; | ||
| 98 | + static dispatch_once_t onceToken; | ||
| 99 | + dispatch_once(&onceToken, ^{ | ||
| 100 | + manager = [YBIBIconManager new]; | ||
| 101 | + }); | ||
| 102 | + return manager; | ||
| 103 | +} | ||
| 104 | + | ||
| 105 | +- (instancetype)init { | ||
| 106 | + self = [super init]; | ||
| 107 | + if (self) { | ||
| 108 | + _loadingImage = ^UIImage * _Nullable{ | ||
| 109 | + return [UIImage ybib_imageNamed:@"ybib_loading" bundle:YBIBDefaultBundle()]; | ||
| 110 | + }; | ||
| 111 | + | ||
| 112 | + _toolSaveImage = ^UIImage * _Nullable{ | ||
| 113 | + return [UIImage ybib_imageNamed:@"ybib_save" bundle:YBIBDefaultBundle()]; | ||
| 114 | + }; | ||
| 115 | + _toolMoreImage = ^UIImage * _Nullable{ | ||
| 116 | + return [UIImage ybib_imageNamed:@"ybib_more" bundle:YBIBDefaultBundle()]; | ||
| 117 | + }; | ||
| 118 | + | ||
| 119 | + _videoPlayImage = ^UIImage * _Nullable{ | ||
| 120 | + return [UIImage ybib_imageNamed:@"ybib_play" bundle:YBIBVideoBundle()]; | ||
| 121 | + }; | ||
| 122 | + _videoPauseImage = ^UIImage * _Nullable{ | ||
| 123 | + return [UIImage ybib_imageNamed:@"ybib_pause" bundle:YBIBVideoBundle()]; | ||
| 124 | + }; | ||
| 125 | + _videoCancelImage = ^UIImage * _Nullable{ | ||
| 126 | + return [UIImage ybib_imageNamed:@"ybib_cancel" bundle:YBIBVideoBundle()]; | ||
| 127 | + }; | ||
| 128 | + _videoBigPlayImage = ^UIImage * _Nullable{ | ||
| 129 | + return [UIImage ybib_imageNamed:@"ybib_bigPlay" bundle:YBIBVideoBundle()]; | ||
| 130 | + }; | ||
| 131 | + _videoDragCircleImage = ^UIImage * _Nullable{ | ||
| 132 | + return [UIImage ybib_imageNamed:@"ybib_circlePoint" bundle:YBIBVideoBundle()]; | ||
| 133 | + }; | ||
| 134 | + } | ||
| 135 | + return self; | ||
| 136 | +} | ||
| 137 | + | ||
| 138 | +@end |
CNLiveOrganizationValidationModule/Classes/CNLiveOrganizationValidationModule/YBImageBrowser/Helper/YBIBPhotoAlbumManager.h
0 → 100644
| 1 | +// | ||
| 2 | +// YBIBPhotoAlbumManager.h | ||
| 3 | +// YBImageBrowserDemo | ||
| 4 | +// | ||
| 5 | +// Created by 波儿菜 on 2018/8/28. | ||
| 6 | +// Copyright © 2018年 波儿菜. All rights reserved. | ||
| 7 | +// | ||
| 8 | + | ||
| 9 | +#import <Photos/Photos.h> | ||
| 10 | + | ||
| 11 | +NS_ASSUME_NONNULL_BEGIN | ||
| 12 | + | ||
| 13 | +@interface YBIBPhotoAlbumManager : NSObject | ||
| 14 | + | ||
| 15 | +/** | ||
| 16 | + Get photo album authorization. | ||
| 17 | + */ | ||
| 18 | ++ (void)getPhotoAlbumAuthorizationSuccess:(void(^)(void))success failed:(void(^)(void))failed; | ||
| 19 | + | ||
| 20 | +/** | ||
| 21 | + Get 'AVAsset' by 'PHAsset' asynchronously, callback is in child thread. | ||
| 22 | + */ | ||
| 23 | ++ (void)getAVAssetWithPHAsset:(PHAsset *)phAsset completion:(void(^)(AVAsset * _Nullable asset))completion; | ||
| 24 | + | ||
| 25 | +/** | ||
| 26 | + Get 'ImageData' by 'PHAsset' synchronously, callback is in child thread. | ||
| 27 | + */ | ||
| 28 | ++ (void)getImageDataWithPHAsset:(PHAsset *)phAsset completion:(void(^)(NSData * _Nullable data))completion; | ||
| 29 | + | ||
| 30 | +@end | ||
| 31 | + | ||
| 32 | +NS_ASSUME_NONNULL_END |
CNLiveOrganizationValidationModule/Classes/CNLiveOrganizationValidationModule/YBImageBrowser/Helper/YBIBPhotoAlbumManager.m
0 → 100644
| 1 | +// | ||
| 2 | +// YBIBPhotoAlbumManager.m | ||
| 3 | +// YBImageBrowserDemo | ||
| 4 | +// | ||
| 5 | +// Created by 波儿菜 on 2018/8/28. | ||
| 6 | +// Copyright © 2018年 波儿菜. All rights reserved. | ||
| 7 | +// | ||
| 8 | + | ||
| 9 | +#import "YBIBPhotoAlbumManager.h" | ||
| 10 | +#import "YBIBUtilities.h" | ||
| 11 | + | ||
| 12 | +@implementation YBIBPhotoAlbumManager | ||
| 13 | + | ||
| 14 | ++ (void)getAVAssetWithPHAsset:(PHAsset *)phAsset completion:(nonnull void (^)(AVAsset * _Nullable))completion { | ||
| 15 | + PHVideoRequestOptions *options = [PHVideoRequestOptions new]; | ||
| 16 | + options.networkAccessAllowed = YES; | ||
| 17 | + [[PHImageManager defaultManager] requestAVAssetForVideo:phAsset options:options resultHandler:^(AVAsset * _Nullable asset, AVAudioMix * _Nullable audioMix, NSDictionary * _Nullable info) { | ||
| 18 | + completion(asset); | ||
| 19 | + }]; | ||
| 20 | +} | ||
| 21 | + | ||
| 22 | ++ (void)getImageDataWithPHAsset:(PHAsset *)phAsset completion:(nonnull void (^)(NSData * _Nullable))completion { | ||
| 23 | + PHImageRequestOptions *options = [PHImageRequestOptions new]; | ||
| 24 | + options.networkAccessAllowed = YES; | ||
| 25 | + options.resizeMode = PHImageRequestOptionsResizeModeNone; | ||
| 26 | + // Only when this property is YES, the callback will in child thread. | ||
| 27 | + options.synchronous = YES; | ||
| 28 | + [[PHImageManager defaultManager] requestImageDataForAsset:phAsset options:options resultHandler:^(NSData * _Nullable imageData, NSString * _Nullable dataUTI, UIImageOrientation orientation, NSDictionary * _Nullable info) { | ||
| 29 | + BOOL complete = ![[info objectForKey:PHImageCancelledKey] boolValue] && ![info objectForKey:PHImageErrorKey] && ![[info objectForKey:PHImageResultIsDegradedKey] boolValue]; | ||
| 30 | + if (complete && imageData) { | ||
| 31 | + completion(imageData); | ||
| 32 | + } else { | ||
| 33 | + completion(nil); | ||
| 34 | + } | ||
| 35 | + }]; | ||
| 36 | +} | ||
| 37 | + | ||
| 38 | ++ (void)getPhotoAlbumAuthorizationSuccess:(void(^)(void))success failed:(void(^)(void))failed { | ||
| 39 | + PHAuthorizationStatus status = [PHPhotoLibrary authorizationStatus]; | ||
| 40 | + switch (status) { | ||
| 41 | + case PHAuthorizationStatusDenied: | ||
| 42 | + if (failed) failed(); | ||
| 43 | + break; | ||
| 44 | + case PHAuthorizationStatusRestricted: | ||
| 45 | + if (failed) failed(); | ||
| 46 | + break; | ||
| 47 | + case PHAuthorizationStatusNotDetermined: { | ||
| 48 | + [PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status){ | ||
| 49 | + YBIB_DISPATCH_ASYNC_MAIN(^{ | ||
| 50 | + if (status == PHAuthorizationStatusAuthorized) { | ||
| 51 | + if (success) success(); | ||
| 52 | + } else { | ||
| 53 | + if (failed) failed(); | ||
| 54 | + } | ||
| 55 | + }) | ||
| 56 | + }]; | ||
| 57 | + } | ||
| 58 | + break; | ||
| 59 | + case PHAuthorizationStatusAuthorized: | ||
| 60 | + if (success) success(); | ||
| 61 | + break; | ||
| 62 | + } | ||
| 63 | +} | ||
| 64 | + | ||
| 65 | +@end |
CNLiveOrganizationValidationModule/Classes/CNLiveOrganizationValidationModule/YBImageBrowser/Helper/YBIBSentinel.h
0 → 100644
| 1 | +// | ||
| 2 | +// YBIBSentinel.h | ||
| 3 | +// YBImageBrowserDemo | ||
| 4 | +// | ||
| 5 | +// Created by 波儿菜 on 2019/6/18. | ||
| 6 | +// Copyright © 2019 波儿菜. All rights reserved. | ||
| 7 | +// | ||
| 8 | + | ||
| 9 | +#import <Foundation/Foundation.h> | ||
| 10 | + | ||
| 11 | +NS_ASSUME_NONNULL_BEGIN | ||
| 12 | + | ||
| 13 | +/** | ||
| 14 | + Thread safe. | ||
| 15 | + */ | ||
| 16 | +@interface YBIBSentinel : NSObject | ||
| 17 | + | ||
| 18 | +/// Returns the current value of the counter. | ||
| 19 | +@property (readonly) int32_t value; | ||
| 20 | + | ||
| 21 | +/** | ||
| 22 | + Increase the value atomically. | ||
| 23 | + @return The new value. | ||
| 24 | + */ | ||
| 25 | +- (int32_t)increase; | ||
| 26 | + | ||
| 27 | +@end | ||
| 28 | + | ||
| 29 | +NS_ASSUME_NONNULL_END |
CNLiveOrganizationValidationModule/Classes/CNLiveOrganizationValidationModule/YBImageBrowser/Helper/YBIBSentinel.m
0 → 100644
| 1 | +// | ||
| 2 | +// YBIBSentinel.m | ||
| 3 | +// YBImageBrowserDemo | ||
| 4 | +// | ||
| 5 | +// Created by 波儿菜 on 2019/6/18. | ||
| 6 | +// Copyright © 2019 波儿菜. All rights reserved. | ||
| 7 | +// | ||
| 8 | + | ||
| 9 | +#import "YBIBSentinel.h" | ||
| 10 | +#import <libkern/OSAtomic.h> | ||
| 11 | + | ||
| 12 | +@implementation YBIBSentinel { | ||
| 13 | + int32_t _value; | ||
| 14 | +} | ||
| 15 | + | ||
| 16 | +- (int32_t)value { | ||
| 17 | + return _value; | ||
| 18 | +} | ||
| 19 | + | ||
| 20 | +- (int32_t)increase { | ||
| 21 | + return OSAtomicIncrement32(&_value); | ||
| 22 | +} | ||
| 23 | + | ||
| 24 | +@end |
CNLiveOrganizationValidationModule/Classes/CNLiveOrganizationValidationModule/YBImageBrowser/Helper/YBIBUtilities.h
0 → 100644
| 1 | +// | ||
| 2 | +// YBIBUtilities.h | ||
| 3 | +// YBImageBrowserDemo | ||
| 4 | +// | ||
| 5 | +// Created by 杨少 on 2018/4/11. | ||
| 6 | +// Copyright © 2018年 波儿菜. All rights reserved. | ||
| 7 | +// | ||
| 8 | + | ||
| 9 | +#import <UIKit/UIKit.h> | ||
| 10 | + | ||
| 11 | +NS_ASSUME_NONNULL_BEGIN | ||
| 12 | + | ||
| 13 | + | ||
| 14 | +#define YBIB_DISPATCH_ASYNC(queue, block)\ | ||
| 15 | +if (strcmp(dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL), dispatch_queue_get_label(queue)) == 0) {\ | ||
| 16 | +block();\ | ||
| 17 | +} else {\ | ||
| 18 | +dispatch_async(queue, block);\ | ||
| 19 | +} | ||
| 20 | + | ||
| 21 | +#define YBIB_DISPATCH_ASYNC_MAIN(block) YBIB_DISPATCH_ASYNC(dispatch_get_main_queue(), block) | ||
| 22 | + | ||
| 23 | + | ||
| 24 | +#define YBIB_CODE_EXEC_TIME(KEY, ...) \ | ||
| 25 | +CFAbsoluteTime startTime = CFAbsoluteTimeGetCurrent(); \ | ||
| 26 | +__VA_ARGS__ \ | ||
| 27 | +CFAbsoluteTime linkTime = (CFAbsoluteTimeGetCurrent() - startTime); \ | ||
| 28 | +NSLog(@"%@ Time-Consuming: %fms", KEY, linkTime * 1000.0); | ||
| 29 | + | ||
| 30 | + | ||
| 31 | +UIWindow * _Nonnull YBIBNormalWindow(void); | ||
| 32 | + | ||
| 33 | +UIViewController * _Nullable YBIBTopController(void); | ||
| 34 | +UIViewController * _Nullable YBIBTopControllerByWindow(UIWindow *); | ||
| 35 | + | ||
| 36 | +BOOL YBIBLowMemory(void); | ||
| 37 | + | ||
| 38 | +BOOL YBIBIsIphoneXSeries(void); | ||
| 39 | +CGFloat YBIBStatusbarHeight(void); | ||
| 40 | +CGFloat YBIBSafeAreaBottomHeight(void); | ||
| 41 | + | ||
| 42 | +UIImage *YBIBSnapshotView(UIView *); | ||
| 43 | + | ||
| 44 | +/// This is orientation of 'YBImageBrowser' not 'UIDevice'. | ||
| 45 | +UIEdgeInsets YBIBPaddingByBrowserOrientation(UIDeviceOrientation); | ||
| 46 | + | ||
| 47 | + | ||
| 48 | +@interface YBIBUtilities : NSObject | ||
| 49 | + | ||
| 50 | +@end | ||
| 51 | + | ||
| 52 | +NS_ASSUME_NONNULL_END |
CNLiveOrganizationValidationModule/Classes/CNLiveOrganizationValidationModule/YBImageBrowser/Helper/YBIBUtilities.m
0 → 100644
| 1 | +// | ||
| 2 | +// YBIBUtilities.m | ||
| 3 | +// YBImageBrowserDemo | ||
| 4 | +// | ||
| 5 | +// Created by 杨少 on 2018/4/11. | ||
| 6 | +// Copyright © 2018年 波儿菜. All rights reserved. | ||
| 7 | +// | ||
| 8 | + | ||
| 9 | +#import "YBIBUtilities.h" | ||
| 10 | +#import <sys/utsname.h> | ||
| 11 | + | ||
| 12 | + | ||
| 13 | +UIWindow * _Nullable YBIBNormalWindow(void) { | ||
| 14 | + UIWindow *window = [[UIApplication sharedApplication] keyWindow]; | ||
| 15 | + if (window.windowLevel != UIWindowLevelNormal) { | ||
| 16 | + NSArray *windows = [[UIApplication sharedApplication] windows]; | ||
| 17 | + for(UIWindow *temp in windows) { | ||
| 18 | + if (temp.windowLevel == UIWindowLevelNormal) { | ||
| 19 | + return temp; | ||
| 20 | + } | ||
| 21 | + } | ||
| 22 | + } | ||
| 23 | + return window; | ||
| 24 | +} | ||
| 25 | + | ||
| 26 | +UIViewController * _Nullable YBIBTopController(void) { | ||
| 27 | + return YBIBTopControllerByWindow(YBIBNormalWindow()); | ||
| 28 | +} | ||
| 29 | + | ||
| 30 | +UIViewController * _Nullable YBIBTopControllerByWindow(UIWindow *window) { | ||
| 31 | + if (!window) return nil; | ||
| 32 | + | ||
| 33 | + UIViewController *top = nil; | ||
| 34 | + id nextResponder; | ||
| 35 | + if (window.subviews.count > 0) { | ||
| 36 | + UIView *frontView = [window.subviews objectAtIndex:0]; | ||
| 37 | + nextResponder = frontView.nextResponder; | ||
| 38 | + } | ||
| 39 | + if (nextResponder && [nextResponder isKindOfClass:UIViewController.class]) { | ||
| 40 | + top = nextResponder; | ||
| 41 | + } else { | ||
| 42 | + top = window.rootViewController; | ||
| 43 | + } | ||
| 44 | + | ||
| 45 | + while ([top isKindOfClass:UITabBarController.class] || [top isKindOfClass:UINavigationController.class] || top.presentedViewController) { | ||
| 46 | + if ([top isKindOfClass:UITabBarController.class]) { | ||
| 47 | + top = ((UITabBarController *)top).selectedViewController; | ||
| 48 | + } else if ([top isKindOfClass:UINavigationController.class]) { | ||
| 49 | + top = ((UINavigationController *)top).topViewController; | ||
| 50 | + } else if (top.presentedViewController) { | ||
| 51 | + top = top.presentedViewController; | ||
| 52 | + } | ||
| 53 | + } | ||
| 54 | + return top; | ||
| 55 | +} | ||
| 56 | + | ||
| 57 | +BOOL YBIBLowMemory(void) { | ||
| 58 | + static BOOL lowMemory = NO; | ||
| 59 | + static dispatch_once_t onceToken; | ||
| 60 | + dispatch_once(&onceToken, ^{ | ||
| 61 | + unsigned long long physicalMemory = [[NSProcessInfo processInfo] physicalMemory]; | ||
| 62 | + lowMemory = physicalMemory > 0 && physicalMemory < 1024 * 1024 * 1500; | ||
| 63 | + }); | ||
| 64 | + return lowMemory; | ||
| 65 | +} | ||
| 66 | + | ||
| 67 | +BOOL YBIBIsIphoneXSeries(void) { | ||
| 68 | + return YBIBStatusbarHeight() > 20; | ||
| 69 | +} | ||
| 70 | + | ||
| 71 | +CGFloat YBIBStatusbarHeight(void) { | ||
| 72 | + CGFloat height = 0; | ||
| 73 | + if (@available(iOS 11.0, *)) { | ||
| 74 | + height = UIApplication.sharedApplication.delegate.window.safeAreaInsets.top; | ||
| 75 | + } | ||
| 76 | + if (height <= 0) { | ||
| 77 | + height = UIApplication.sharedApplication.statusBarFrame.size.height; | ||
| 78 | + } | ||
| 79 | + if (height <= 0) { | ||
| 80 | + height = 20; | ||
| 81 | + } | ||
| 82 | + return height; | ||
| 83 | +} | ||
| 84 | + | ||
| 85 | +CGFloat YBIBSafeAreaBottomHeight(void) { | ||
| 86 | + CGFloat bottom = 0; | ||
| 87 | + if (@available(iOS 11.0, *)) { | ||
| 88 | + bottom = UIApplication.sharedApplication.delegate.window.safeAreaInsets.bottom; | ||
| 89 | + } | ||
| 90 | + return bottom; | ||
| 91 | +} | ||
| 92 | + | ||
| 93 | +UIImage *YBIBSnapshotView(UIView *view) { | ||
| 94 | + UIGraphicsBeginImageContextWithOptions(view.bounds.size, YES, [UIScreen mainScreen].scale); | ||
| 95 | + [view drawViewHierarchyInRect:view.bounds afterScreenUpdates:NO]; | ||
| 96 | + UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); | ||
| 97 | + UIGraphicsEndImageContext(); | ||
| 98 | + return image; | ||
| 99 | +} | ||
| 100 | + | ||
| 101 | +UIEdgeInsets YBIBPaddingByBrowserOrientation(UIDeviceOrientation orientation) { | ||
| 102 | + UIEdgeInsets padding = UIEdgeInsetsZero; | ||
| 103 | + if (!YBIBIsIphoneXSeries()) return padding; | ||
| 104 | + | ||
| 105 | + UIDeviceOrientation barOrientation = (UIDeviceOrientation)UIApplication.sharedApplication.statusBarOrientation; | ||
| 106 | + | ||
| 107 | + if (UIDeviceOrientationIsLandscape(orientation)) { | ||
| 108 | + BOOL same = orientation == barOrientation; | ||
| 109 | + BOOL reverse = !same && UIDeviceOrientationIsLandscape(barOrientation); | ||
| 110 | + if (same) { | ||
| 111 | + padding.bottom = YBIBSafeAreaBottomHeight(); | ||
| 112 | + padding.top = 0; | ||
| 113 | + } else if (reverse) { | ||
| 114 | + padding.top = YBIBSafeAreaBottomHeight(); | ||
| 115 | + padding.bottom = 0; | ||
| 116 | + } | ||
| 117 | + padding.left = padding.right = MAX(YBIBSafeAreaBottomHeight(), YBIBStatusbarHeight()); | ||
| 118 | + } else { | ||
| 119 | + if (orientation == UIDeviceOrientationPortrait) { | ||
| 120 | + padding.top = YBIBStatusbarHeight(); | ||
| 121 | + padding.bottom = barOrientation == UIDeviceOrientationPortrait ? YBIBSafeAreaBottomHeight() : 0; | ||
| 122 | + } else { | ||
| 123 | + padding.bottom = YBIBStatusbarHeight(); | ||
| 124 | + padding.top = barOrientation == UIDeviceOrientationPortrait ? YBIBSafeAreaBottomHeight() : 0; | ||
| 125 | + } | ||
| 126 | + padding.left = padding.right = UIDeviceOrientationIsLandscape(barOrientation) ? YBIBSafeAreaBottomHeight() : 0 ; | ||
| 127 | + } | ||
| 128 | + return padding; | ||
| 129 | +} | ||
| 130 | + | ||
| 131 | + | ||
| 132 | +@implementation YBIBUtilities | ||
| 133 | + | ||
| 134 | +@end |
CNLiveOrganizationValidationModule/Classes/CNLiveOrganizationValidationModule/YBImageBrowser/Image/YBIBImageCache+Internal.h
0 → 100644
| 1 | +// | ||
| 2 | +// YBIBImageCache+Internal.h | ||
| 3 | +// YBImageBrowserDemo | ||
| 4 | +// | ||
| 5 | +// Created by 波儿菜 on 2019/6/13. | ||
| 6 | +// Copyright © 2019 波儿菜. All rights reserved. | ||
| 7 | +// | ||
| 8 | + | ||
| 9 | +#import "YBIBImageCache.h" | ||
| 10 | + | ||
| 11 | +NS_ASSUME_NONNULL_BEGIN | ||
| 12 | + | ||
| 13 | +typedef NS_ENUM(NSInteger, YBIBImageCacheType) { | ||
| 14 | + YBIBImageCacheTypeOrigin, | ||
| 15 | + YBIBImageCacheTypeCompressed | ||
| 16 | +}; | ||
| 17 | + | ||
| 18 | +/** | ||
| 19 | + Not thread safe. | ||
| 20 | + */ | ||
| 21 | +@interface YBIBImageCache () | ||
| 22 | + | ||
| 23 | +- (void)setImage:(UIImage *)image type:(YBIBImageCacheType)type forKey:(NSString *)key resident:(BOOL)resident; | ||
| 24 | + | ||
| 25 | +- (nullable UIImage *)imageForKey:(NSString *)key type:(YBIBImageCacheType)type; | ||
| 26 | + | ||
| 27 | +- (void)removeForKey:(NSString *)key; | ||
| 28 | + | ||
| 29 | +- (void)removeResidentForKey:(NSString *)key; | ||
| 30 | + | ||
| 31 | +@end | ||
| 32 | + | ||
| 33 | +NS_ASSUME_NONNULL_END |
CNLiveOrganizationValidationModule/Classes/CNLiveOrganizationValidationModule/YBImageBrowser/Image/YBIBImageCache.h
0 → 100644
| 1 | +// | ||
| 2 | +// YBIBImageCache.h | ||
| 3 | +// YBImageBrowserDemo | ||
| 4 | +// | ||
| 5 | +// Created by 波儿菜 on 2019/6/13. | ||
| 6 | +// Copyright © 2019 波儿菜. All rights reserved. | ||
| 7 | +// | ||
| 8 | + | ||
| 9 | +#import <UIKit/UIKit.h> | ||
| 10 | + | ||
| 11 | +NS_ASSUME_NONNULL_BEGIN | ||
| 12 | + | ||
| 13 | +@class YBIBImageCache; | ||
| 14 | + | ||
| 15 | +@interface NSObject (YBIBImageCache) | ||
| 16 | + | ||
| 17 | +/// 图片浏览器持有的图片缓存管理类 | ||
| 18 | +@property (nonatomic, strong, readonly) YBIBImageCache *ybib_imageCache; | ||
| 19 | + | ||
| 20 | +@end | ||
| 21 | + | ||
| 22 | + | ||
| 23 | +@interface YBIBImageCache : NSObject | ||
| 24 | + | ||
| 25 | +/// 缓存数量限制(一个单位表示一个 YBIBImageData 产生的所有图片数据) | ||
| 26 | +@property (nonatomic, assign) NSUInteger imageCacheCountLimit; | ||
| 27 | + | ||
| 28 | +@end | ||
| 29 | + | ||
| 30 | +NS_ASSUME_NONNULL_END |
CNLiveOrganizationValidationModule/Classes/CNLiveOrganizationValidationModule/YBImageBrowser/Image/YBIBImageCache.m
0 → 100644
| 1 | +// | ||
| 2 | +// YBIBImageCache.m | ||
| 3 | +// YBImageBrowserDemo | ||
| 4 | +// | ||
| 5 | +// Created by 波儿菜 on 2019/6/13. | ||
| 6 | +// Copyright © 2019 波儿菜. All rights reserved. | ||
| 7 | +// | ||
| 8 | + | ||
| 9 | +#import "YBIBImageCache.h" | ||
| 10 | +#import "YBIBImageCache+Internal.h" | ||
| 11 | +#import "YBIBUtilities.h" | ||
| 12 | +#import <objc/runtime.h> | ||
| 13 | + | ||
| 14 | + | ||
| 15 | +@implementation NSObject (YBIBImageCache) | ||
| 16 | +static void *YBIBImageCacheKey = &YBIBImageCacheKey; | ||
| 17 | +- (void)setYbib_imageCache:(YBIBImageCache *)ybib_imageCache { | ||
| 18 | + objc_setAssociatedObject(self, YBIBImageCacheKey, ybib_imageCache, OBJC_ASSOCIATION_RETAIN_NONATOMIC); | ||
| 19 | +} | ||
| 20 | +- (YBIBImageCache *)ybib_imageCache { | ||
| 21 | + YBIBImageCache *cache = objc_getAssociatedObject(self, YBIBImageCacheKey); | ||
| 22 | + if (!cache) { | ||
| 23 | + cache = [YBIBImageCache new]; | ||
| 24 | + self.ybib_imageCache = cache; | ||
| 25 | + } | ||
| 26 | + return cache; | ||
| 27 | +} | ||
| 28 | +@end | ||
| 29 | + | ||
| 30 | + | ||
| 31 | +@interface YBIBImageCachePack : NSObject | ||
| 32 | +@property (nonatomic, strong) UIImage *originImage; | ||
| 33 | +@property (nonatomic, strong) UIImage *compressedImage; | ||
| 34 | +@end | ||
| 35 | +@implementation YBIBImageCachePack | ||
| 36 | +@end | ||
| 37 | + | ||
| 38 | + | ||
| 39 | +@implementation YBIBImageCache { | ||
| 40 | + NSCache<NSString *, YBIBImageCachePack *> *_imageCache; | ||
| 41 | + NSMutableDictionary<NSString *, YBIBImageCachePack *> *_residentCache; | ||
| 42 | +} | ||
| 43 | + | ||
| 44 | +#pragma mark - life cycle | ||
| 45 | + | ||
| 46 | +- (void)dealloc { | ||
| 47 | + [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidReceiveMemoryWarningNotification object:nil]; | ||
| 48 | +} | ||
| 49 | + | ||
| 50 | +- (instancetype)init { | ||
| 51 | + self = [super init]; | ||
| 52 | + if (self) { | ||
| 53 | + _imageCache = [NSCache new]; | ||
| 54 | + _imageCache.countLimit = _imageCacheCountLimit = YBIBLowMemory() ? 6 : 12; | ||
| 55 | + _residentCache = [NSMutableDictionary dictionary]; | ||
| 56 | + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveMemoryWarning:) name:UIApplicationDidReceiveMemoryWarningNotification object:nil]; | ||
| 57 | + } | ||
| 58 | + return self; | ||
| 59 | +} | ||
| 60 | + | ||
| 61 | +#pragma mark - event | ||
| 62 | + | ||
| 63 | +- (void)didReceiveMemoryWarning:(NSNotification *)notification { | ||
| 64 | + [_imageCache removeAllObjects]; | ||
| 65 | + [_residentCache removeAllObjects]; | ||
| 66 | +} | ||
| 67 | + | ||
| 68 | +#pragma mark - public | ||
| 69 | + | ||
| 70 | +- (void)setImage:(UIImage *)image type:(YBIBImageCacheType)type forKey:(NSString *)key resident:(BOOL)resident { | ||
| 71 | + YBIBImageCachePack *pack = [_imageCache objectForKey:key]; | ||
| 72 | + if (!pack) { | ||
| 73 | + pack = [YBIBImageCachePack new]; | ||
| 74 | + [_imageCache setObject:pack forKey:key]; | ||
| 75 | + } | ||
| 76 | + switch (type) { | ||
| 77 | + case YBIBImageCacheTypeOrigin: | ||
| 78 | + pack.originImage = image; | ||
| 79 | + break; | ||
| 80 | + case YBIBImageCacheTypeCompressed: | ||
| 81 | + pack.compressedImage = image; | ||
| 82 | + break; | ||
| 83 | + } | ||
| 84 | + [_residentCache setObject:pack forKey:key]; | ||
| 85 | +} | ||
| 86 | + | ||
| 87 | +- (UIImage *)imageForKey:(NSString *)key type:(YBIBImageCacheType)type { | ||
| 88 | + YBIBImageCachePack *pack = [_imageCache objectForKey:key] ?: [_residentCache objectForKey:key]; | ||
| 89 | + switch (type) { | ||
| 90 | + case YBIBImageCacheTypeOrigin: return pack.originImage; | ||
| 91 | + case YBIBImageCacheTypeCompressed: return pack.compressedImage; | ||
| 92 | + default: return nil; | ||
| 93 | + } | ||
| 94 | +} | ||
| 95 | + | ||
| 96 | +- (void)removeForKey:(NSString *)key { | ||
| 97 | + [_imageCache removeObjectForKey:key]; | ||
| 98 | + [_residentCache removeObjectForKey:key]; | ||
| 99 | +} | ||
| 100 | + | ||
| 101 | +- (void)removeResidentForKey:(NSString *)key { | ||
| 102 | + [_residentCache removeObjectForKey:key]; | ||
| 103 | +} | ||
| 104 | + | ||
| 105 | +#pragma mark - setter | ||
| 106 | + | ||
| 107 | +- (void)setImageCacheCountLimit:(NSUInteger)imageCacheCountLimit { | ||
| 108 | + _imageCacheCountLimit = imageCacheCountLimit; | ||
| 109 | + _imageCache.countLimit = imageCacheCountLimit; | ||
| 110 | +} | ||
| 111 | + | ||
| 112 | +@end |
CNLiveOrganizationValidationModule/Classes/CNLiveOrganizationValidationModule/YBImageBrowser/Image/YBIBImageCell+Internal.h
0 → 100644
| 1 | +// | ||
| 2 | +// YBIBImageCell+Internal.h | ||
| 3 | +// YBImageBrowserDemo | ||
| 4 | +// | ||
| 5 | +// Created by 波儿菜 on 2019/12/23. | ||
| 6 | +// Copyright © 2019 杨波. All rights reserved. | ||
| 7 | +// | ||
| 8 | + | ||
| 9 | +#import "YBIBImageCell.h" | ||
| 10 | +#import "YBIBImageScrollView.h" | ||
| 11 | + | ||
| 12 | +NS_ASSUME_NONNULL_BEGIN | ||
| 13 | + | ||
| 14 | +@interface YBIBImageCell () | ||
| 15 | + | ||
| 16 | +@property (nonatomic, strong) YBIBImageScrollView *imageScrollView; | ||
| 17 | + | ||
| 18 | +@property (nonatomic, strong) UIImageView *tailoringImageView; | ||
| 19 | + | ||
| 20 | +@end | ||
| 21 | + | ||
| 22 | +NS_ASSUME_NONNULL_END |
CNLiveOrganizationValidationModule/Classes/CNLiveOrganizationValidationModule/YBImageBrowser/Image/YBIBImageCell.h
0 → 100644
| 1 | +// | ||
| 2 | +// YBIBImageCell.h | ||
| 3 | +// YBImageBrowserDemo | ||
| 4 | +// | ||
| 5 | +// Created by 波儿菜 on 2019/6/5. | ||
| 6 | +// Copyright © 2019 波儿菜. All rights reserved. | ||
| 7 | +// | ||
| 8 | + | ||
| 9 | +#import "YBIBCellProtocol.h" | ||
| 10 | + | ||
| 11 | +NS_ASSUME_NONNULL_BEGIN | ||
| 12 | + | ||
| 13 | +@interface YBIBImageCell : UICollectionViewCell <YBIBCellProtocol> | ||
| 14 | + | ||
| 15 | +@end | ||
| 16 | + | ||
| 17 | +NS_ASSUME_NONNULL_END |
CNLiveOrganizationValidationModule/Classes/CNLiveOrganizationValidationModule/YBImageBrowser/Image/YBIBImageCell.m
0 → 100644
| 1 | +// | ||
| 2 | +// YBIBImageCell.m | ||
| 3 | +// YBImageBrowserDemo | ||
| 4 | +// | ||
| 5 | +// Created by 波儿菜 on 2019/6/5. | ||
| 6 | +// Copyright © 2019 波儿菜. All rights reserved. | ||
| 7 | +// | ||
| 8 | + | ||
| 9 | +#import "YBIBImageCell.h" | ||
| 10 | +#import "YBIBImageData.h" | ||
| 11 | +#import "YBIBIconManager.h" | ||
| 12 | +#import "YBIBImageCell+Internal.h" | ||
| 13 | +#import "YBIBImageData+Internal.h" | ||
| 14 | +#import "YBIBCopywriter.h" | ||
| 15 | +#import "YBIBUtilities.h" | ||
| 16 | + | ||
| 17 | +@interface YBIBImageCell () <YBIBImageDataDelegate, UIScrollViewDelegate, UIGestureRecognizerDelegate> | ||
| 18 | +@end | ||
| 19 | + | ||
| 20 | +@implementation YBIBImageCell { | ||
| 21 | + CGPoint _interactStartPoint; | ||
| 22 | + BOOL _interacting; | ||
| 23 | +} | ||
| 24 | + | ||
| 25 | +#pragma mark - life cycle | ||
| 26 | + | ||
| 27 | +- (instancetype)initWithFrame:(CGRect)frame { | ||
| 28 | + self = [super initWithFrame:frame]; | ||
| 29 | + if (self) { | ||
| 30 | + [self initValue]; | ||
| 31 | + [self.contentView addSubview:self.imageScrollView]; | ||
| 32 | + [self addGesture]; | ||
| 33 | + } | ||
| 34 | + return self; | ||
| 35 | +} | ||
| 36 | + | ||
| 37 | +- (void)layoutSubviews { | ||
| 38 | + [super layoutSubviews]; | ||
| 39 | + self.imageScrollView.frame = self.bounds; | ||
| 40 | +} | ||
| 41 | + | ||
| 42 | +- (void)initValue { | ||
| 43 | + _interactStartPoint = CGPointZero; | ||
| 44 | + _interacting = NO; | ||
| 45 | +} | ||
| 46 | + | ||
| 47 | +- (void)prepareForReuse { | ||
| 48 | + ((YBIBImageData *)self.yb_cellData).delegate = nil; | ||
| 49 | + [self.imageScrollView reset]; | ||
| 50 | + [self hideTailoringImageView]; | ||
| 51 | + [self hideAuxiliaryView]; | ||
| 52 | + [super prepareForReuse]; | ||
| 53 | +} | ||
| 54 | + | ||
| 55 | +#pragma mark - <YBIBCellProtocol> | ||
| 56 | + | ||
| 57 | +@synthesize yb_currentOrientation = _yb_currentOrientation; | ||
| 58 | +@synthesize yb_containerSize = _yb_containerSize; | ||
| 59 | +@synthesize yb_backView = _yb_backView; | ||
| 60 | +@synthesize yb_collectionView = _yb_collectionView; | ||
| 61 | +@synthesize yb_isTransitioning = _yb_isTransitioning; | ||
| 62 | +@synthesize yb_isRotating = _yb_isRotating; | ||
| 63 | +@synthesize yb_auxiliaryViewHandler = _yb_auxiliaryViewHandler; | ||
| 64 | +@synthesize yb_hideStatusBar = _yb_hideStatusBar; | ||
| 65 | +@synthesize yb_hideBrowser = _yb_hideBrowser; | ||
| 66 | +@synthesize yb_hideToolViews = _yb_hideToolViews; | ||
| 67 | +@synthesize yb_cellData = _yb_cellData; | ||
| 68 | +@synthesize yb_cellIsInCenter = _yb_cellIsInCenter; | ||
| 69 | +@synthesize yb_selfPage = _yb_selfPage; | ||
| 70 | +@synthesize yb_currentPage = _yb_currentPage; | ||
| 71 | + | ||
| 72 | +- (void)setYb_cellData:(id<YBIBDataProtocol>)yb_cellData { | ||
| 73 | + _yb_cellData = yb_cellData; | ||
| 74 | + ((YBIBImageData *)yb_cellData).delegate = self; | ||
| 75 | +} | ||
| 76 | + | ||
| 77 | +- (UIView *)yb_foregroundView { | ||
| 78 | + return self.imageScrollView.imageView; | ||
| 79 | +} | ||
| 80 | + | ||
| 81 | +- (void)yb_orientationWillChangeWithExpectOrientation:(UIDeviceOrientation)orientation { | ||
| 82 | + [self hideTailoringImageView]; | ||
| 83 | +} | ||
| 84 | + | ||
| 85 | +- (void)yb_orientationChangeAnimationWithExpectOrientation:(UIDeviceOrientation)orientation { | ||
| 86 | + [self updateImageLayoutWithOrientation:orientation previousImageSize:self.imageScrollView.imageView.image.size]; | ||
| 87 | +} | ||
| 88 | + | ||
| 89 | +#pragma mark - private | ||
| 90 | + | ||
| 91 | +- (CGSize)contentSizeWithContainerSize:(CGSize)containerSize imageViewFrame:(CGRect)imageViewFrame { | ||
| 92 | + return CGSizeMake(MAX(containerSize.width, imageViewFrame.size.width), MAX(containerSize.height, imageViewFrame.size.height)); | ||
| 93 | +} | ||
| 94 | + | ||
| 95 | +- (void)updateImageLayoutWithOrientation:(UIDeviceOrientation)orientation previousImageSize:(CGSize)previousImageSize { | ||
| 96 | + if (_interacting) [self restoreInteractionWithDuration:0]; | ||
| 97 | + | ||
| 98 | + YBIBImageData *data = self.yb_cellData; | ||
| 99 | + | ||
| 100 | + CGSize imageSize; | ||
| 101 | + | ||
| 102 | + UIImage *image = self.imageScrollView.imageView.image; | ||
| 103 | + YBIBScrollImageType imageType = self.imageScrollView.imageType; | ||
| 104 | + if (imageType == YBIBScrollImageTypeCompressed) { | ||
| 105 | + imageSize = data.originImage ? data.originImage.size : image.size; | ||
| 106 | + } else { | ||
| 107 | + imageSize = image.size; | ||
| 108 | + } | ||
| 109 | + | ||
| 110 | + CGSize containerSize = self.yb_containerSize(orientation); | ||
| 111 | + CGRect imageViewFrame = [data.layout yb_imageViewFrameWithContainerSize:containerSize imageSize:imageSize orientation:orientation]; | ||
| 112 | + CGSize contentSize = [self contentSizeWithContainerSize:containerSize imageViewFrame:imageViewFrame]; | ||
| 113 | + CGFloat maxZoomScale = imageType == YBIBScrollImageTypeThumb ? 1 : [data.layout yb_maximumZoomScaleWithContainerSize:containerSize imageSize:imageSize orientation:orientation]; | ||
| 114 | + | ||
| 115 | + // 'zoomScale' must set before 'contentSize' and 'imageView.frame'. | ||
| 116 | + self.imageScrollView.zoomScale = 1; | ||
| 117 | + self.imageScrollView.contentSize = contentSize; | ||
| 118 | + self.imageScrollView.minimumZoomScale = 1; | ||
| 119 | + self.imageScrollView.maximumZoomScale = maxZoomScale; | ||
| 120 | + | ||
| 121 | + CGFloat scale; | ||
| 122 | + if (previousImageSize.width > 0 && previousImageSize.height > 0) { | ||
| 123 | + scale = imageSize.width / imageSize.height - previousImageSize.width / previousImageSize.height; | ||
| 124 | + } else { | ||
| 125 | + scale = 0; | ||
| 126 | + } | ||
| 127 | + // '0.001' is admissible error. | ||
| 128 | + if (ABS(scale) <= 0.001) { | ||
| 129 | + self.imageScrollView.imageView.frame = imageViewFrame; | ||
| 130 | + } else { | ||
| 131 | + [UIView animateWithDuration:0.25 animations:^{ | ||
| 132 | + self.imageScrollView.imageView.frame = imageViewFrame; | ||
| 133 | + }]; | ||
| 134 | + } | ||
| 135 | +} | ||
| 136 | + | ||
| 137 | +- (void)cuttingImage { | ||
| 138 | + // This method has been delayed called, so 'browser' may be in transit now. | ||
| 139 | + if (self.yb_isTransitioning()) return; | ||
| 140 | + if (_interacting) return; | ||
| 141 | + | ||
| 142 | + YBIBImageData *data = self.yb_cellData; | ||
| 143 | + if (!data.originImage) return; | ||
| 144 | + | ||
| 145 | + if (self.imageScrollView.zoomScale < data.cuttingZoomScale) return; | ||
| 146 | + | ||
| 147 | + if ([data shouldCompress]) { | ||
| 148 | + [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(cuttingImage_) object:nil]; | ||
| 149 | + [self performSelector:@selector(cuttingImage_) withObject:nil afterDelay:0.15]; | ||
| 150 | + } | ||
| 151 | +} | ||
| 152 | +- (void)cuttingImage_ { | ||
| 153 | + YBIBImageData *data = self.yb_cellData; | ||
| 154 | + if (!data.originImage) return; | ||
| 155 | + | ||
| 156 | + CGFloat scale = data.originImage.size.width / self.imageScrollView.contentSize.width; | ||
| 157 | + CGFloat x = self.imageScrollView.contentOffset.x * scale, | ||
| 158 | + y = self.imageScrollView.contentOffset.y * scale, | ||
| 159 | + width = self.imageScrollView.bounds.size.width * scale, | ||
| 160 | + height = self.imageScrollView.bounds.size.height * scale; | ||
| 161 | + | ||
| 162 | + __weak typeof(self) wSelf = self; | ||
| 163 | + [data cuttingImageToRect:CGRectMake(x, y, width, height) complete:^(UIImage *image) { | ||
| 164 | + if (!image) return; | ||
| 165 | + YBIB_DISPATCH_ASYNC_MAIN(^{ | ||
| 166 | + __strong typeof(wSelf) self = wSelf; | ||
| 167 | + if (!self) return; | ||
| 168 | + if (data == self.yb_cellData && !self.imageScrollView.isDragging && !self->_interacting && !self.yb_isTransitioning()) { | ||
| 169 | + [self showTailoringImageView:image]; | ||
| 170 | + } | ||
| 171 | + }) | ||
| 172 | + }]; | ||
| 173 | +} | ||
| 174 | + | ||
| 175 | +- (void)showTailoringImageView:(UIImage *)image { | ||
| 176 | + CGSize containerSize = self.yb_containerSize(self.yb_currentOrientation()); | ||
| 177 | + if (!self.tailoringImageView.superview) { | ||
| 178 | + [self.contentView addSubview:self.tailoringImageView]; | ||
| 179 | + } | ||
| 180 | + self.tailoringImageView.frame = CGRectMake(0, 0, containerSize.width, containerSize.height); | ||
| 181 | + self.tailoringImageView.hidden = NO; | ||
| 182 | + self.tailoringImageView.image = image; | ||
| 183 | +} | ||
| 184 | + | ||
| 185 | +- (void)hideTailoringImageView { | ||
| 186 | + // Don't use 'getter' method, because it's according to the need to load. | ||
| 187 | + if (_tailoringImageView) { | ||
| 188 | + self.tailoringImageView.hidden = YES; | ||
| 189 | + } | ||
| 190 | +} | ||
| 191 | + | ||
| 192 | +- (void)hideAuxiliaryView { | ||
| 193 | + [self.yb_auxiliaryViewHandler() yb_hideLoadingWithContainer:self]; | ||
| 194 | + [self.yb_auxiliaryViewHandler() yb_hideToastWithContainer:self]; | ||
| 195 | +} | ||
| 196 | + | ||
| 197 | +- (void)hideBrowser { | ||
| 198 | + ((YBIBImageData *)self.yb_cellData).delegate = nil; | ||
| 199 | + [self hideTailoringImageView]; | ||
| 200 | + [self hideAuxiliaryView]; | ||
| 201 | + self.yb_hideBrowser(); | ||
| 202 | + _interacting = NO; | ||
| 203 | +} | ||
| 204 | + | ||
| 205 | +#pragma mark - <YBIBImageDataDelegate> | ||
| 206 | + | ||
| 207 | +- (void)yb_imageData:(YBIBImageData *)data startLoadingWithStatus:(YBIBImageLoadingStatus)status { | ||
| 208 | + switch (status) { | ||
| 209 | + case YBIBImageLoadingStatusDecoding: { | ||
| 210 | + if (!self.imageScrollView.imageView.image) { | ||
| 211 | + [self.yb_auxiliaryViewHandler() yb_showLoadingWithContainer:self]; | ||
| 212 | + } | ||
| 213 | + } | ||
| 214 | + break; | ||
| 215 | + case YBIBImageLoadingStatusProcessing: { | ||
| 216 | + if (!self.imageScrollView.imageView.image) { | ||
| 217 | + [self.yb_auxiliaryViewHandler() yb_showLoadingWithContainer:self]; | ||
| 218 | + } | ||
| 219 | + } | ||
| 220 | + break; | ||
| 221 | + case YBIBImageLoadingStatusCompressing: { | ||
| 222 | + if (!self.imageScrollView.imageView.image) { | ||
| 223 | + [self.yb_auxiliaryViewHandler() yb_showLoadingWithContainer:self]; | ||
| 224 | + } | ||
| 225 | + } | ||
| 226 | + break; | ||
| 227 | + case YBIBImageLoadingStatusReadingPHAsset: { | ||
| 228 | + if (!self.imageScrollView.imageView.image) { | ||
| 229 | + [self.yb_auxiliaryViewHandler() yb_showLoadingWithContainer:self]; | ||
| 230 | + } | ||
| 231 | + } | ||
| 232 | + break; | ||
| 233 | + case YBIBImageLoadingStatusNone: { | ||
| 234 | + [self hideAuxiliaryView]; | ||
| 235 | + } | ||
| 236 | + break; | ||
| 237 | + default: | ||
| 238 | + break; | ||
| 239 | + } | ||
| 240 | +} | ||
| 241 | + | ||
| 242 | +- (void)yb_imageData:(YBIBImageData *)data readyForImage:(__kindof UIImage *)image { | ||
| 243 | + [self.yb_auxiliaryViewHandler() yb_hideLoadingWithContainer:self]; | ||
| 244 | + if (self.imageScrollView.imageView.image == image) return; | ||
| 245 | + | ||
| 246 | + CGSize size = self.imageScrollView.imageView.image.size; | ||
| 247 | + [self.imageScrollView setImage:image type:YBIBScrollImageTypeOriginal]; | ||
| 248 | + [self updateImageLayoutWithOrientation:self.yb_currentOrientation() previousImageSize:size]; | ||
| 249 | +} | ||
| 250 | + | ||
| 251 | +- (void)yb_imageData:(YBIBImageData *)data readyForCompressedImage:(__kindof UIImage *)image { | ||
| 252 | + [self.yb_auxiliaryViewHandler() yb_hideLoadingWithContainer:self]; | ||
| 253 | + if (self.imageScrollView.imageView.image == image) return; | ||
| 254 | + | ||
| 255 | + CGSize size = self.imageScrollView.imageView.image.size; | ||
| 256 | + [self.imageScrollView setImage:image type:YBIBScrollImageTypeCompressed]; | ||
| 257 | + [self updateImageLayoutWithOrientation:self.yb_currentOrientation() previousImageSize:size]; | ||
| 258 | +} | ||
| 259 | + | ||
| 260 | +- (void)yb_imageData:(YBIBImageData *)data readyForThumbImage:(__kindof UIImage *)image { | ||
| 261 | + if (self.imageScrollView.imageView.image) return; | ||
| 262 | + | ||
| 263 | + [self.imageScrollView setImage:image type:YBIBScrollImageTypeThumb]; | ||
| 264 | + [self updateImageLayoutWithOrientation:self.yb_currentOrientation() previousImageSize:image.size]; | ||
| 265 | +} | ||
| 266 | + | ||
| 267 | +- (void)yb_imageIsInvalidForData:(YBIBImageData *)data { | ||
| 268 | + [self.yb_auxiliaryViewHandler() yb_hideLoadingWithContainer:self]; | ||
| 269 | + NSString *imageIsInvalid = [YBIBCopywriter sharedCopywriter].imageIsInvalid; | ||
| 270 | + if (self.imageScrollView.imageView.image) { | ||
| 271 | + [self.yb_auxiliaryViewHandler() yb_showIncorrectToastWithContainer:self text:imageIsInvalid]; | ||
| 272 | + } else { | ||
| 273 | + [self.yb_auxiliaryViewHandler() yb_showLoadingWithContainer:self text:imageIsInvalid]; | ||
| 274 | + } | ||
| 275 | +} | ||
| 276 | + | ||
| 277 | +- (void)yb_imageData:(YBIBImageData *)data downloadProgress:(CGFloat)progress { | ||
| 278 | + [self.yb_auxiliaryViewHandler() yb_showLoadingWithContainer:self progress:progress]; | ||
| 279 | +} | ||
| 280 | + | ||
| 281 | +- (void)yb_imageDownloadFailedForData:(YBIBImageData *)data { | ||
| 282 | + if (self.imageScrollView.imageView.image) { | ||
| 283 | + [self.yb_auxiliaryViewHandler() yb_hideLoadingWithContainer:self]; | ||
| 284 | + [self.yb_auxiliaryViewHandler() yb_showIncorrectToastWithContainer:self text:[YBIBCopywriter sharedCopywriter].downloadFailed]; | ||
| 285 | + } else { | ||
| 286 | + [self.yb_auxiliaryViewHandler() yb_showLoadingWithContainer:self text:[YBIBCopywriter sharedCopywriter].downloadFailed]; | ||
| 287 | + } | ||
| 288 | +} | ||
| 289 | + | ||
| 290 | +#pragma mark - gesture | ||
| 291 | + | ||
| 292 | +- (void)addGesture { | ||
| 293 | + UITapGestureRecognizer *tapSingle = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(respondsToTapSingle:)]; | ||
| 294 | + tapSingle.numberOfTapsRequired = 1; | ||
| 295 | + UITapGestureRecognizer *tapDouble = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(respondsToTapDouble:)]; | ||
| 296 | + tapDouble.numberOfTapsRequired = 2; | ||
| 297 | + UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(respondsToPan:)]; | ||
| 298 | + pan.maximumNumberOfTouches = 1; | ||
| 299 | + pan.delegate = self; | ||
| 300 | + | ||
| 301 | + [tapSingle requireGestureRecognizerToFail:tapDouble]; | ||
| 302 | + [tapSingle requireGestureRecognizerToFail:pan]; | ||
| 303 | + [tapDouble requireGestureRecognizerToFail:pan]; | ||
| 304 | + | ||
| 305 | + [self addGestureRecognizer:tapSingle]; | ||
| 306 | + [self addGestureRecognizer:tapDouble]; | ||
| 307 | + [self addGestureRecognizer:pan]; | ||
| 308 | +} | ||
| 309 | + | ||
| 310 | +- (void)respondsToTapSingle:(UITapGestureRecognizer *)tap { | ||
| 311 | + if (self.yb_isRotating()) return; | ||
| 312 | + | ||
| 313 | + YBIBImageData *data = self.yb_cellData; | ||
| 314 | + if (data.singleTouchBlock) { | ||
| 315 | + data.singleTouchBlock(data); | ||
| 316 | + } else { | ||
| 317 | + [self hideTailoringImageView]; | ||
| 318 | + [self hideAuxiliaryView]; | ||
| 319 | + self.yb_hideBrowser(); | ||
| 320 | + } | ||
| 321 | +} | ||
| 322 | + | ||
| 323 | +- (void)respondsToTapDouble:(UITapGestureRecognizer *)tap { | ||
| 324 | + if (self.yb_isRotating()) return; | ||
| 325 | + | ||
| 326 | + [self hideTailoringImageView]; | ||
| 327 | + | ||
| 328 | + UIScrollView *scrollView = self.imageScrollView; | ||
| 329 | + UIView *zoomView = [self viewForZoomingInScrollView:scrollView]; | ||
| 330 | + CGPoint point = [tap locationInView:zoomView]; | ||
| 331 | + if (!CGRectContainsPoint(zoomView.bounds, point)) return; | ||
| 332 | + if (scrollView.zoomScale == scrollView.maximumZoomScale) { | ||
| 333 | + [scrollView setZoomScale:1 animated:YES]; | ||
| 334 | + } else { | ||
| 335 | + [scrollView zoomToRect:CGRectMake(point.x, point.y, 1, 1) animated:YES]; | ||
| 336 | + } | ||
| 337 | +} | ||
| 338 | + | ||
| 339 | +- (void)respondsToPan:(UIPanGestureRecognizer *)pan { | ||
| 340 | + if (self.yb_isRotating()) return; | ||
| 341 | + | ||
| 342 | + YBIBInteractionProfile *profile = ((YBIBImageData *)self.yb_cellData).interactionProfile; | ||
| 343 | + if (profile.disable) return; | ||
| 344 | + if ((CGRectIsEmpty(self.imageScrollView.imageView.frame) || !self.imageScrollView.imageView.image)) return; | ||
| 345 | + | ||
| 346 | + CGPoint point = [pan locationInView:self]; | ||
| 347 | + CGSize containerSize = self.yb_containerSize(self.yb_currentOrientation()); | ||
| 348 | + | ||
| 349 | + if (pan.state == UIGestureRecognizerStateBegan) { | ||
| 350 | + _interactStartPoint = point; | ||
| 351 | + } else if (pan.state == UIGestureRecognizerStateCancelled || pan.state == UIGestureRecognizerStateEnded || pan.state == UIGestureRecognizerStateRecognized || pan.state == UIGestureRecognizerStateFailed) { | ||
| 352 | + | ||
| 353 | + // End. | ||
| 354 | + if (_interacting) { | ||
| 355 | + CGPoint velocity = [pan velocityInView:self.imageScrollView]; | ||
| 356 | + | ||
| 357 | + BOOL velocityArrive = ABS(velocity.y) > profile.dismissVelocityY; | ||
| 358 | + BOOL distanceArrive = ABS(point.y - _interactStartPoint.y) > containerSize.height * profile.dismissScale; | ||
| 359 | + | ||
| 360 | + BOOL shouldDismiss = distanceArrive || velocityArrive; | ||
| 361 | + if (shouldDismiss) { | ||
| 362 | + [self hideBrowser]; | ||
| 363 | + } else { | ||
| 364 | + [self restoreInteractionWithDuration:profile.restoreDuration]; | ||
| 365 | + } | ||
| 366 | + } | ||
| 367 | + | ||
| 368 | + } else if (pan.state == UIGestureRecognizerStateChanged) { | ||
| 369 | + if (_interacting) { | ||
| 370 | + | ||
| 371 | + // Change. | ||
| 372 | + self.imageScrollView.center = point; | ||
| 373 | + CGFloat scale = 1 - ABS(point.y - _interactStartPoint.y) / (containerSize.height * 1.2); | ||
| 374 | + if (scale > 1) scale = 1; | ||
| 375 | + if (scale < 0.35) scale = 0.35; | ||
| 376 | + self.imageScrollView.transform = CGAffineTransformMakeScale(scale, scale); | ||
| 377 | + | ||
| 378 | + CGFloat alpha = 1 - ABS(point.y - _interactStartPoint.y) / (containerSize.height * 0.7); | ||
| 379 | + if (alpha > 1) alpha = 1; | ||
| 380 | + if (alpha < 0) alpha = 0; | ||
| 381 | + self.yb_backView.backgroundColor = [self.yb_backView.backgroundColor colorWithAlphaComponent:alpha]; | ||
| 382 | + | ||
| 383 | + } else { | ||
| 384 | + | ||
| 385 | + // Start. | ||
| 386 | + if (CGPointEqualToPoint(_interactStartPoint, CGPointZero) || self.yb_currentPage() != self.yb_selfPage() || !self.yb_cellIsInCenter() || self.imageScrollView.isZooming) return; | ||
| 387 | + | ||
| 388 | + CGPoint velocity = [pan velocityInView:self.imageScrollView]; | ||
| 389 | + CGFloat triggerDistance = profile.triggerDistance; | ||
| 390 | + CGFloat offsetY = self.imageScrollView.contentOffset.y, height = self.imageScrollView.bounds.size.height; | ||
| 391 | + | ||
| 392 | + BOOL distanceArrive = ABS(point.x - _interactStartPoint.x) < triggerDistance && ABS(velocity.x) < 500; | ||
| 393 | + BOOL upArrive = point.y - _interactStartPoint.y > triggerDistance && offsetY <= 1; | ||
| 394 | + BOOL downArrive = point.y - _interactStartPoint.y < -triggerDistance && offsetY + height >= MAX(self.imageScrollView.contentSize.height, height) - 1; | ||
| 395 | + | ||
| 396 | + BOOL shouldStart = (upArrive || downArrive) && distanceArrive; | ||
| 397 | + if (!shouldStart) return; | ||
| 398 | + | ||
| 399 | + _interactStartPoint = point; | ||
| 400 | + | ||
| 401 | + CGRect startFrame = self.imageScrollView.frame; | ||
| 402 | + CGFloat anchorX = point.x / startFrame.size.width, anchorY = point.y / startFrame.size.height; | ||
| 403 | + self.imageScrollView.layer.anchorPoint = CGPointMake(anchorX, anchorY); | ||
| 404 | + self.imageScrollView.userInteractionEnabled = NO; | ||
| 405 | + self.imageScrollView.scrollEnabled = NO; | ||
| 406 | + self.imageScrollView.center = point; | ||
| 407 | + | ||
| 408 | + self.yb_hideToolViews(YES); | ||
| 409 | + self.yb_hideStatusBar(NO); | ||
| 410 | + self.yb_collectionView().scrollEnabled = NO; | ||
| 411 | + [self hideTailoringImageView]; | ||
| 412 | + | ||
| 413 | + _interacting = YES; | ||
| 414 | + } | ||
| 415 | + } | ||
| 416 | +} | ||
| 417 | + | ||
| 418 | +- (void)restoreInteractionWithDuration:(NSTimeInterval)duration { | ||
| 419 | + CGSize containerSize = self.yb_containerSize(self.yb_currentOrientation()); | ||
| 420 | + | ||
| 421 | + void (^animations)(void) = ^{ | ||
| 422 | + self.yb_backView.backgroundColor = [self.yb_backView.backgroundColor colorWithAlphaComponent:1]; | ||
| 423 | + | ||
| 424 | + CGPoint anchorPoint = self.imageScrollView.layer.anchorPoint; | ||
| 425 | + self.imageScrollView.center = CGPointMake(containerSize.width * anchorPoint.x, containerSize.height * anchorPoint.y); | ||
| 426 | + self.imageScrollView.transform = CGAffineTransformIdentity; | ||
| 427 | + }; | ||
| 428 | + void (^completion)(BOOL finished) = ^(BOOL finished){ | ||
| 429 | + self.imageScrollView.layer.anchorPoint = CGPointMake(0.5, 0.5); | ||
| 430 | + self.imageScrollView.center = CGPointMake(containerSize.width * 0.5, containerSize.height * 0.5); | ||
| 431 | + self.imageScrollView.userInteractionEnabled = YES; | ||
| 432 | + self.imageScrollView.scrollEnabled = YES; | ||
| 433 | + | ||
| 434 | + self.yb_hideToolViews(NO); | ||
| 435 | + self.yb_hideStatusBar(YES); | ||
| 436 | + self.yb_collectionView().scrollEnabled = YES; | ||
| 437 | + [self cuttingImage]; | ||
| 438 | + | ||
| 439 | + self->_interactStartPoint = CGPointZero; | ||
| 440 | + self->_interacting = NO; | ||
| 441 | + }; | ||
| 442 | + | ||
| 443 | + if (duration <= 0) { | ||
| 444 | + animations(); | ||
| 445 | + completion(NO); | ||
| 446 | + } else { | ||
| 447 | + [UIView animateWithDuration:duration animations:animations completion:completion]; | ||
| 448 | + } | ||
| 449 | +} | ||
| 450 | + | ||
| 451 | +#pragma mark - <UIScrollViewDelegate> | ||
| 452 | + | ||
| 453 | +- (void)scrollViewDidZoom:(UIScrollView *)scrollView { | ||
| 454 | + YBIBImageData *data = self.yb_cellData; | ||
| 455 | + if (data.imageDidZoomBlock) { | ||
| 456 | + data.imageDidZoomBlock(data, scrollView); | ||
| 457 | + } | ||
| 458 | + | ||
| 459 | + CGRect imageViewFrame = self.imageScrollView.imageView.frame; | ||
| 460 | + CGFloat width = imageViewFrame.size.width, | ||
| 461 | + height = imageViewFrame.size.height, | ||
| 462 | + sHeight = scrollView.bounds.size.height, | ||
| 463 | + sWidth = scrollView.bounds.size.width; | ||
| 464 | + if (height > sHeight) { | ||
| 465 | + imageViewFrame.origin.y = 0; | ||
| 466 | + } else { | ||
| 467 | + imageViewFrame.origin.y = (sHeight - height) / 2.0; | ||
| 468 | + } | ||
| 469 | + if (width > sWidth) { | ||
| 470 | + imageViewFrame.origin.x = 0; | ||
| 471 | + } else { | ||
| 472 | + imageViewFrame.origin.x = (sWidth - width) / 2.0; | ||
| 473 | + } | ||
| 474 | + self.imageScrollView.imageView.frame = imageViewFrame; | ||
| 475 | +} | ||
| 476 | + | ||
| 477 | +- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView { | ||
| 478 | + return self.imageScrollView.imageView; | ||
| 479 | +} | ||
| 480 | + | ||
| 481 | +- (void)scrollViewDidScroll:(UIScrollView *)scrollView { | ||
| 482 | + YBIBImageData *data = self.yb_cellData; | ||
| 483 | + if (data.imageDidScrollBlock) { | ||
| 484 | + data.imageDidScrollBlock(data, scrollView); | ||
| 485 | + } | ||
| 486 | + | ||
| 487 | + [self cuttingImage]; | ||
| 488 | +} | ||
| 489 | + | ||
| 490 | +- (void)scrollViewWillBeginZooming:(UIScrollView *)scrollView withView:(UIView *)view { | ||
| 491 | + [self hideTailoringImageView]; | ||
| 492 | +} | ||
| 493 | + | ||
| 494 | +- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView { | ||
| 495 | + [self hideTailoringImageView]; | ||
| 496 | +} | ||
| 497 | + | ||
| 498 | +#pragma mark - <UIGestureRecognizerDelegate> | ||
| 499 | + | ||
| 500 | +- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer { | ||
| 501 | + return YES; | ||
| 502 | +} | ||
| 503 | + | ||
| 504 | +#pragma mark - getters | ||
| 505 | + | ||
| 506 | +- (YBIBImageScrollView *)imageScrollView { | ||
| 507 | + if (!_imageScrollView) { | ||
| 508 | + _imageScrollView = [YBIBImageScrollView new]; | ||
| 509 | + _imageScrollView.delegate = self; | ||
| 510 | + } | ||
| 511 | + return _imageScrollView; | ||
| 512 | +} | ||
| 513 | + | ||
| 514 | +- (UIImageView *)tailoringImageView { | ||
| 515 | + if (!_tailoringImageView) { | ||
| 516 | + _tailoringImageView = [UIImageView new]; | ||
| 517 | + _tailoringImageView.contentMode = UIViewContentModeScaleAspectFit; | ||
| 518 | + } | ||
| 519 | + return _tailoringImageView; | ||
| 520 | +} | ||
| 521 | + | ||
| 522 | +@end |
CNLiveOrganizationValidationModule/Classes/CNLiveOrganizationValidationModule/YBImageBrowser/Image/YBIBImageData+Internal.h
0 → 100644
| 1 | +// | ||
| 2 | +// YBIBImageData+Internal.h | ||
| 3 | +// YBImageBrowserDemo | ||
| 4 | +// | ||
| 5 | +// Created by 波儿菜 on 2019/6/12. | ||
| 6 | +// Copyright © 2019 波儿菜. All rights reserved. | ||
| 7 | +// | ||
| 8 | + | ||
| 9 | +#import "YBIBImageData.h" | ||
| 10 | + | ||
| 11 | +NS_ASSUME_NONNULL_BEGIN | ||
| 12 | + | ||
| 13 | +typedef NS_ENUM(NSInteger, YBIBImageLoadingStatus) { | ||
| 14 | + YBIBImageLoadingStatusNone, | ||
| 15 | + YBIBImageLoadingStatusCompressing, | ||
| 16 | + YBIBImageLoadingStatusDecoding, | ||
| 17 | + YBIBImageLoadingStatusQuerying, | ||
| 18 | + YBIBImageLoadingStatusProcessing, | ||
| 19 | + YBIBImageLoadingStatusDownloading, | ||
| 20 | + YBIBImageLoadingStatusReadingPHAsset, | ||
| 21 | +}; | ||
| 22 | + | ||
| 23 | +@class YBIBImageData; | ||
| 24 | + | ||
| 25 | +@protocol YBIBImageDataDelegate <NSObject> | ||
| 26 | +@required | ||
| 27 | + | ||
| 28 | +- (void)yb_imageData:(YBIBImageData *)data startLoadingWithStatus:(YBIBImageLoadingStatus)status; | ||
| 29 | + | ||
| 30 | +- (void)yb_imageIsInvalidForData:(YBIBImageData *)data; | ||
| 31 | + | ||
| 32 | +- (void)yb_imageData:(YBIBImageData *)data readyForImage:(__kindof UIImage *)image; | ||
| 33 | + | ||
| 34 | +- (void)yb_imageData:(YBIBImageData *)data readyForThumbImage:(__kindof UIImage *)image; | ||
| 35 | + | ||
| 36 | +- (void)yb_imageData:(YBIBImageData *)data readyForCompressedImage:(__kindof UIImage *)image; | ||
| 37 | + | ||
| 38 | +- (void)yb_imageData:(YBIBImageData *)data downloadProgress:(CGFloat)progress; | ||
| 39 | + | ||
| 40 | +- (void)yb_imageDownloadFailedForData:(YBIBImageData *)data; | ||
| 41 | + | ||
| 42 | +@end | ||
| 43 | + | ||
| 44 | +@interface YBIBImageData () | ||
| 45 | + | ||
| 46 | +@property (nonatomic, weak) id<YBIBImageDataDelegate> delegate; | ||
| 47 | + | ||
| 48 | +/// The status of asynchronous tasks. | ||
| 49 | +@property (nonatomic, assign) YBIBImageLoadingStatus loadingStatus; | ||
| 50 | + | ||
| 51 | +/// The origin image. | ||
| 52 | +@property (nonatomic, strong) UIImage *originImage; | ||
| 53 | + | ||
| 54 | +/// The image compressed by 'originImage' if need. | ||
| 55 | +@property (nonatomic, strong) UIImage *compressedImage; | ||
| 56 | + | ||
| 57 | +- (BOOL)shouldCompress; | ||
| 58 | + | ||
| 59 | +- (void)cuttingImageToRect:(CGRect)rect complete:(void(^)(UIImage * _Nullable image))complete; | ||
| 60 | + | ||
| 61 | +@end | ||
| 62 | + | ||
| 63 | +NS_ASSUME_NONNULL_END |
CNLiveOrganizationValidationModule/Classes/CNLiveOrganizationValidationModule/YBImageBrowser/Image/YBIBImageData.h
0 → 100644
| 1 | +// | ||
| 2 | +// YBIBImageData.h | ||
| 3 | +// YBImageBrowserDemo | ||
| 4 | +// | ||
| 5 | +// Created by 波儿菜 on 2019/6/5. | ||
| 6 | +// Copyright © 2019 波儿菜. All rights reserved. | ||
| 7 | +// | ||
| 8 | + | ||
| 9 | +#import <Photos/Photos.h> | ||
| 10 | +#import "YBIBDataProtocol.h" | ||
| 11 | +#import "YBIBImageLayout.h" | ||
| 12 | +#import "YBIBImageCache.h" | ||
| 13 | +#import "YBIBInteractionProfile.h" | ||
| 14 | + | ||
| 15 | +NS_ASSUME_NONNULL_BEGIN | ||
| 16 | + | ||
| 17 | +@class YBIBImageData; | ||
| 18 | + | ||
| 19 | +/// 获取 NSData 的闭包 | ||
| 20 | +typedef NSData * _Nullable (^YBIBImageDataBlock)(void); | ||
| 21 | + | ||
| 22 | +/// 获取 UIImage 的闭包 | ||
| 23 | +typedef UIImage * _Nullable (^YBIBImageBlock)(void); | ||
| 24 | + | ||
| 25 | +/// 修改 NSURLRequest 并返回的闭包 | ||
| 26 | +typedef NSURLRequest * _Nullable (^YBIBRequestModifierBlock)(YBIBImageData *imageData, NSURLRequest *request); | ||
| 27 | + | ||
| 28 | +/// 根据图片逻辑像素和 scale 判断是否需要预解码的闭包 | ||
| 29 | +typedef BOOL (^YBIBPreDecodeDecisionBlock)(YBIBImageData *imageData, CGSize imageSize, CGFloat scale); | ||
| 30 | + | ||
| 31 | +/// 修改 UIImage 并返回的闭包 | ||
| 32 | +typedef void (^YBIBImageModifierBlock)(YBIBImageData *imageData, UIImage *image, void(^completion)(UIImage *processedImage)); | ||
| 33 | + | ||
| 34 | +/// 单击事件的处理闭包 | ||
| 35 | +typedef void (^YBIBImageSingleTouchBlock)(YBIBImageData *imageData); | ||
| 36 | + | ||
| 37 | +/// 内部图片滚动视图状态回调闭包 | ||
| 38 | +typedef void (^YBIBImageScrollViewStatusBlock)(YBIBImageData *imageData, UIScrollView *scrollView); | ||
| 39 | + | ||
| 40 | + | ||
| 41 | +/** | ||
| 42 | + 图片数据类,承担配置数据和处理数据的责任 | ||
| 43 | + */ | ||
| 44 | +@interface YBIBImageData : NSObject <YBIBDataProtocol> | ||
| 45 | + | ||
| 46 | +/// 本地图片名字 | ||
| 47 | +@property (nonatomic, copy, nullable) NSString *imageName; | ||
| 48 | + | ||
| 49 | +/// 本地图片路径 | ||
| 50 | +@property (nonatomic, copy, nullable) NSString *imagePath; | ||
| 51 | + | ||
| 52 | +/// 本地图片字节码,返回 NSData | ||
| 53 | +@property (nonatomic, copy, nullable) YBIBImageDataBlock imageData; | ||
| 54 | + | ||
| 55 | +/// 本地图片,返回 UIImage 及其衍生类 (若不是遵循'YYAnimatedImage'协议的类型,将失去对动图和拓展格式的支持) | ||
| 56 | +@property (nonatomic, copy, nullable) YBIBImageBlock image; | ||
| 57 | + | ||
| 58 | +/// 网络图片资源 | ||
| 59 | +@property (nonatomic, copy, nullable) NSURL *imageURL; | ||
| 60 | + | ||
| 61 | +/// 修改 NSURLRequest 并返回 | ||
| 62 | +@property (nonatomic, copy, nullable) YBIBRequestModifierBlock requestModifier; | ||
| 63 | + | ||
| 64 | +/// 相册图片资源 | ||
| 65 | +@property (nonatomic, strong, nullable) PHAsset *imagePHAsset; | ||
| 66 | + | ||
| 67 | +/// 投影视图,当前数据模型对应外界业务的 UIView (通常为 UIImageView),做转场动效用 | ||
| 68 | +@property (nonatomic, weak, nullable) __kindof UIView *projectiveView; | ||
| 69 | + | ||
| 70 | +/// 预览图/缩约图,注意若这个图片过大会导致内存压力(若 projectiveView 存在且是 UIImageView 类型将会自动获取缩约图) | ||
| 71 | +@property (nonatomic, strong, nullable) UIImage *thumbImage; | ||
| 72 | + | ||
| 73 | +/// 预览图/缩约图 URL,缓存中未找到则忽略(若 projectiveView 存在且是 UIImageView 类型将会自动获取缩约图) | ||
| 74 | +@property (nonatomic, copy, nullable) NSURL *thumbURL; | ||
| 75 | + | ||
| 76 | +/// 是否允许保存到相册 | ||
| 77 | +@property (nonatomic, assign) BOOL allowSaveToPhotoAlbum; | ||
| 78 | + | ||
| 79 | +/// 根据图片信息判断是否需要预解码 | ||
| 80 | +@property (nonatomic, copy, nullable) YBIBPreDecodeDecisionBlock preDecodeDecision; | ||
| 81 | + | ||
| 82 | +/// 是否异步预解码,默认为 YES | ||
| 83 | +@property (nonatomic, assign) BOOL shouldPreDecodeAsync; | ||
| 84 | + | ||
| 85 | +/// 压缩物理像素界限大小,当图片超过这个值将会被压缩显示,默认为 4096*4096 | ||
| 86 | +@property (nonatomic, assign) CGFloat compressingSize; | ||
| 87 | + | ||
| 88 | +/// 触发裁剪的缩放比例,必须大于等于 1,默认情况内部会动态计算 (仅当图片需要压缩显示时有效) | ||
| 89 | +@property (nonatomic, assign) CGFloat cuttingZoomScale; | ||
| 90 | + | ||
| 91 | +/** | ||
| 92 | + 判断图片是否需要压缩显示 | ||
| 93 | + */ | ||
| 94 | +- (BOOL)shouldCompressWithImage:(UIImage *)image; | ||
| 95 | + | ||
| 96 | +/** | ||
| 97 | + 修改原始图片并返回处理后的图片 (特别注意当 image 是大图的时候,避免 OOM) | ||
| 98 | + Example: | ||
| 99 | + [... setImageModifier:^(YBIBImageData *imageData, UIImage * _Nonnull image, void (^ _Nonnull completion)(UIImage * _Nonnull)) { | ||
| 100 | + //step 1 : Add watermark, trademark, etc. (Sync or async). | ||
| 101 | + ... processing code ... | ||
| 102 | + //step 2 : Return the processed UIImage. | ||
| 103 | + completion(image); | ||
| 104 | + }]; | ||
| 105 | + */ | ||
| 106 | +@property (nonatomic, copy, nullable) YBIBImageModifierBlock originImageModifier; | ||
| 107 | + | ||
| 108 | +/// 修改压缩图片并返回处理后的图片 (仅在当前图片需要被压缩时有效) | ||
| 109 | +@property (nonatomic, copy, nullable) YBIBImageModifierBlock compressedImageModifier; | ||
| 110 | + | ||
| 111 | +/// 修改裁剪图片并返回处理后的图片 (仅在当前图片需要被裁剪时有效) | ||
| 112 | +@property (nonatomic, copy, nullable) YBIBImageModifierBlock cuttedImageModifier; | ||
| 113 | + | ||
| 114 | +/// 预留属性可随意使用 | ||
| 115 | +@property (nonatomic, strong, nullable) id extraData; | ||
| 116 | + | ||
| 117 | +/// 手势交互动效配置文件 | ||
| 118 | +@property (nonatomic, strong) YBIBInteractionProfile *interactionProfile; | ||
| 119 | + | ||
| 120 | +/// 单击的处理,默认是退出图片浏览器 | ||
| 121 | +@property (nonatomic, copy, nullable) YBIBImageSingleTouchBlock singleTouchBlock; | ||
| 122 | + | ||
| 123 | +/// 图片滚动的回调 | ||
| 124 | +@property (nonatomic, copy, nullable) YBIBImageScrollViewStatusBlock imageDidScrollBlock; | ||
| 125 | + | ||
| 126 | +/// 图片缩放的回调 | ||
| 127 | +@property (nonatomic, copy, nullable) YBIBImageScrollViewStatusBlock imageDidZoomBlock; | ||
| 128 | + | ||
| 129 | +/// 图片布局类 (赋值可自定义) | ||
| 130 | +@property (nonatomic, strong) id<YBIBImageLayout> layout; | ||
| 131 | +/// 默认图片布局类 (可配置其属性) | ||
| 132 | +@property (nonatomic, weak, readonly) YBIBImageLayout *defaultLayout; | ||
| 133 | + | ||
| 134 | +/** | ||
| 135 | + 终止处理数据程序 | ||
| 136 | + */ | ||
| 137 | +- (void)stopLoading; | ||
| 138 | + | ||
| 139 | +/** | ||
| 140 | + 清除缓存的数据 | ||
| 141 | + */ | ||
| 142 | +- (void)clearCache; | ||
| 143 | + | ||
| 144 | +/** | ||
| 145 | + 加载数据,一般不需要调用这个方法,当该数据对象做了数据更新时调用 | ||
| 146 | + */ | ||
| 147 | +- (void)loadData; | ||
| 148 | + | ||
| 149 | +/// 处理后的原始图片 | ||
| 150 | +@property (nonatomic, strong, readonly) UIImage *originImage; | ||
| 151 | + | ||
| 152 | +/// 处理后的压缩图片 | ||
| 153 | +@property (nonatomic, strong, readonly) UIImage *compressedImage; | ||
| 154 | + | ||
| 155 | +@end | ||
| 156 | + | ||
| 157 | +NS_ASSUME_NONNULL_END |
CNLiveOrganizationValidationModule/Classes/CNLiveOrganizationValidationModule/YBImageBrowser/Image/YBIBImageData.m
0 → 100644
| 1 | +// | ||
| 2 | +// YBIBImageData.m | ||
| 3 | +// YBImageBrowserDemo | ||
| 4 | +// | ||
| 5 | +// Created by 波儿菜 on 2019/6/5. | ||
| 6 | +// Copyright © 2019 波儿菜. All rights reserved. | ||
| 7 | +// | ||
| 8 | + | ||
| 9 | +#import "YBImage.h" | ||
| 10 | +#import "YBIBImageData.h" | ||
| 11 | +#import "YBIBImageCell.h" | ||
| 12 | +#import "YBIBPhotoAlbumManager.h" | ||
| 13 | +#import "YBIBImageData+Internal.h" | ||
| 14 | +#import "YBIBUtilities.h" | ||
| 15 | +#import "YBIBImageCache+Internal.h" | ||
| 16 | +#import "YBIBSentinel.h" | ||
| 17 | +#import "YBIBCopywriter.h" | ||
| 18 | +#import <AssetsLibrary/AssetsLibrary.h> | ||
| 19 | + | ||
| 20 | +extern CGImageRef YYCGImageCreateDecodedCopy(CGImageRef imageRef, BOOL decodeForDisplay); | ||
| 21 | + | ||
| 22 | +static dispatch_queue_t YBIBImageProcessingQueue(void) { | ||
| 23 | + static dispatch_queue_t queue; | ||
| 24 | + static dispatch_once_t onceToken; | ||
| 25 | + dispatch_once(&onceToken, ^{ | ||
| 26 | + queue = dispatch_queue_create("com.yangbo.imagebrowser.imageprocessing", DISPATCH_QUEUE_CONCURRENT); | ||
| 27 | + }); | ||
| 28 | + return queue; | ||
| 29 | +} | ||
| 30 | + | ||
| 31 | +@implementation YBIBImageData { | ||
| 32 | + __weak id _downloadToken; | ||
| 33 | + YBIBSentinel *_cuttingSentinel; | ||
| 34 | + /// Stop processing tasks when in freeze. | ||
| 35 | + BOOL _freezing; | ||
| 36 | +} | ||
| 37 | + | ||
| 38 | +#pragma mark - life cycle | ||
| 39 | + | ||
| 40 | +- (void)dealloc { | ||
| 41 | + if (_downloadToken && [self.yb_webImageMediator() respondsToSelector:@selector(yb_cancelTaskWithDownloadToken:)]) { | ||
| 42 | + [self.yb_webImageMediator() yb_cancelTaskWithDownloadToken:_downloadToken]; | ||
| 43 | + } | ||
| 44 | + [self.imageCache removeForKey:self.cacheKey]; | ||
| 45 | +} | ||
| 46 | + | ||
| 47 | +- (instancetype)init { | ||
| 48 | + self = [super init]; | ||
| 49 | + if (self) { | ||
| 50 | + [self initValue]; | ||
| 51 | + } | ||
| 52 | + return self; | ||
| 53 | +} | ||
| 54 | + | ||
| 55 | +- (void)initValue { | ||
| 56 | + _defaultLayout = _layout = [YBIBImageLayout new]; | ||
| 57 | + _loadingStatus = YBIBImageLoadingStatusNone; | ||
| 58 | + _compressingSize = 4096 * 4096; | ||
| 59 | + _shouldPreDecodeAsync = YES; | ||
| 60 | + _freezing = NO; | ||
| 61 | + _cuttingSentinel = [YBIBSentinel new]; | ||
| 62 | + _interactionProfile = [YBIBInteractionProfile new]; | ||
| 63 | + _allowSaveToPhotoAlbum = YES; | ||
| 64 | +} | ||
| 65 | + | ||
| 66 | +#pragma mark - load data | ||
| 67 | + | ||
| 68 | +- (void)loadData { | ||
| 69 | + _freezing = NO; | ||
| 70 | + | ||
| 71 | + // Avoid handling asynchronous tasks repeatedly. | ||
| 72 | + if (self.loadingStatus != YBIBImageLoadingStatusNone) { | ||
| 73 | + [self loadThumbImage]; | ||
| 74 | + self.loadingStatus = self.loadingStatus; | ||
| 75 | + return; | ||
| 76 | + } | ||
| 77 | + | ||
| 78 | + if (self.originImage) { | ||
| 79 | + [self loadOriginImage]; | ||
| 80 | + } else if (self.imageName || self.imagePath || self.imageData) { | ||
| 81 | + [self loadYBImage]; | ||
| 82 | + } else if (self.image) { | ||
| 83 | + [self loadImageBlock]; | ||
| 84 | + } else if (self.imageURL) { | ||
| 85 | + [self loadThumbImage]; | ||
| 86 | + [self loadURL]; | ||
| 87 | + } else if (self.imagePHAsset) { | ||
| 88 | + [self loadThumbImage]; | ||
| 89 | + [self loadPHAsset]; | ||
| 90 | + } else { | ||
| 91 | + [self.delegate yb_imageIsInvalidForData:self]; | ||
| 92 | + } | ||
| 93 | +} | ||
| 94 | + | ||
| 95 | +- (void)loadOriginImage { | ||
| 96 | + if (_freezing) return; | ||
| 97 | + if (!self.originImage) return; | ||
| 98 | + | ||
| 99 | + if ([self shouldCompress]) { | ||
| 100 | + if (self.compressedImage) { | ||
| 101 | + [self.delegate yb_imageData:self readyForCompressedImage:self.compressedImage]; | ||
| 102 | + } else { | ||
| 103 | + [self loadThumbImage]; | ||
| 104 | + [self loadOriginImage_compress]; | ||
| 105 | + } | ||
| 106 | + } else { | ||
| 107 | + [self.delegate yb_imageData:self readyForImage:self.originImage]; | ||
| 108 | + } | ||
| 109 | +} | ||
| 110 | +- (void)loadOriginImage_compress { | ||
| 111 | + if (_freezing) return; | ||
| 112 | + if (!self.originImage) return; | ||
| 113 | + | ||
| 114 | + self.loadingStatus = YBIBImageLoadingStatusCompressing; | ||
| 115 | + __weak typeof(self) wSelf = self; | ||
| 116 | + CGSize size = [self bestSizeOfCompressing]; | ||
| 117 | + | ||
| 118 | + YBIB_DISPATCH_ASYNC(YBIBImageProcessingQueue(), ^{ | ||
| 119 | + if (self->_freezing) { | ||
| 120 | + self.loadingStatus = YBIBImageLoadingStatusNone; | ||
| 121 | + return; | ||
| 122 | + } | ||
| 123 | + // Ensure the best display effect. | ||
| 124 | + UIGraphicsBeginImageContextWithOptions(size, NO, UIScreen.mainScreen.scale); | ||
| 125 | + [self.originImage drawInRect:CGRectMake(0, 0, size.width, size.height)]; | ||
| 126 | + if (self->_freezing) { | ||
| 127 | + UIGraphicsEndImageContext(); | ||
| 128 | + self.loadingStatus = YBIBImageLoadingStatusNone; | ||
| 129 | + return; | ||
| 130 | + } | ||
| 131 | + UIImage *resultImage = UIGraphicsGetImageFromCurrentImageContext(); | ||
| 132 | + UIGraphicsEndImageContext(); | ||
| 133 | + | ||
| 134 | + YBIB_DISPATCH_ASYNC_MAIN(^{ | ||
| 135 | + __strong typeof(wSelf) self = wSelf; | ||
| 136 | + if (!self) return; | ||
| 137 | + | ||
| 138 | + self.loadingStatus = YBIBImageLoadingStatusNone; | ||
| 139 | + | ||
| 140 | + [self modifyImageWithModifier:self.compressedImageModifier image:resultImage completion:^(UIImage *processedImage) { | ||
| 141 | + __strong typeof(wSelf) self = wSelf; | ||
| 142 | + if (!self) return; | ||
| 143 | + self.compressedImage = processedImage ?: self.originImage; | ||
| 144 | + [self.delegate yb_imageData:self readyForCompressedImage:self.compressedImage]; | ||
| 145 | + }]; | ||
| 146 | + }) | ||
| 147 | + }) | ||
| 148 | +} | ||
| 149 | + | ||
| 150 | +- (void)loadYBImage { | ||
| 151 | + if (_freezing) return; | ||
| 152 | + NSString *name = self.imageName.copy; | ||
| 153 | + NSString *path = self.imagePath.copy; | ||
| 154 | + NSData *data = self.imageData ? self.imageData().copy : nil; | ||
| 155 | + if (name.length == 0 && path.length == 0 && data.length == 0) return; | ||
| 156 | + | ||
| 157 | + YBImageDecodeDecision decision = [self defaultDecodeDecision]; | ||
| 158 | + | ||
| 159 | + __block YBImage *image; | ||
| 160 | + __weak typeof(self) wSelf = self; | ||
| 161 | + void(^dealBlock)(void) = ^{ | ||
| 162 | + if (name.length > 0) { | ||
| 163 | + image = [YBImage imageNamed:name decodeDecision:decision]; | ||
| 164 | + } else if (path.length > 0) { | ||
| 165 | + image = [YBImage imageWithContentsOfFile:path decodeDecision:decision]; | ||
| 166 | + } else if (data.length > 0) { | ||
| 167 | + image = [YBImage imageWithData:data scale:UIScreen.mainScreen.scale decodeDecision:decision]; | ||
| 168 | + } | ||
| 169 | + YBIB_DISPATCH_ASYNC_MAIN(^{ | ||
| 170 | + __strong typeof(wSelf) self = wSelf; | ||
| 171 | + if (!self) return; | ||
| 172 | + self.loadingStatus = YBIBImageLoadingStatusNone; | ||
| 173 | + if (image) { | ||
| 174 | + [self setOriginImageAndLoadWithImage:image]; | ||
| 175 | + } else { | ||
| 176 | + [self.delegate yb_imageIsInvalidForData:self]; | ||
| 177 | + } | ||
| 178 | + }) | ||
| 179 | + }; | ||
| 180 | + | ||
| 181 | + if (self.shouldPreDecodeAsync) { | ||
| 182 | + [self loadThumbImage]; | ||
| 183 | + self.loadingStatus = YBIBImageLoadingStatusDecoding; | ||
| 184 | + YBIB_DISPATCH_ASYNC(YBIBImageProcessingQueue(), dealBlock) | ||
| 185 | + } else { | ||
| 186 | + self.loadingStatus = YBIBImageLoadingStatusDecoding; | ||
| 187 | + dealBlock(); | ||
| 188 | + } | ||
| 189 | +} | ||
| 190 | + | ||
| 191 | +- (void)loadImageBlock { | ||
| 192 | + if (_freezing) return; | ||
| 193 | + __block UIImage *image = self.image ? self.image() : nil; | ||
| 194 | + if (!image) return; | ||
| 195 | + | ||
| 196 | + BOOL shouldPreDecode = self.preDecodeDecision ? self.preDecodeDecision(self, image.size, image.scale) : ![self shouldCompressWithImage:image]; | ||
| 197 | + | ||
| 198 | + __weak typeof(self) wSelf = self; | ||
| 199 | + void(^dealBlock)(void) = ^{ | ||
| 200 | + // Do not need to decode If 'image' conformed 'YYAnimatedImage'. (Not entirely accurate.) | ||
| 201 | + if (![image conformsToProtocol:@protocol(YYAnimatedImage)]) { | ||
| 202 | + CGImageRef cgImage = YYCGImageCreateDecodedCopy(image.CGImage, shouldPreDecode); | ||
| 203 | + image = [UIImage imageWithCGImage:cgImage scale:image.scale orientation:image.imageOrientation]; | ||
| 204 | + if (cgImage) CGImageRelease(cgImage); | ||
| 205 | + } | ||
| 206 | + YBIB_DISPATCH_ASYNC_MAIN(^{ | ||
| 207 | + __strong typeof(wSelf) self = wSelf; | ||
| 208 | + if (!self) return; | ||
| 209 | + self.loadingStatus = YBIBImageLoadingStatusNone; | ||
| 210 | + [self setOriginImageAndLoadWithImage:image]; | ||
| 211 | + }) | ||
| 212 | + }; | ||
| 213 | + | ||
| 214 | + if (self.shouldPreDecodeAsync) { | ||
| 215 | + [self loadThumbImage]; | ||
| 216 | + self.loadingStatus = YBIBImageLoadingStatusDecoding; | ||
| 217 | + YBIB_DISPATCH_ASYNC(YBIBImageProcessingQueue(), dealBlock) | ||
| 218 | + } else { | ||
| 219 | + self.loadingStatus = YBIBImageLoadingStatusDecoding; | ||
| 220 | + dealBlock(); | ||
| 221 | + } | ||
| 222 | +} | ||
| 223 | + | ||
| 224 | +- (void)loadURL { | ||
| 225 | + if (!self.imageURL || self.imageURL.absoluteString.length == 0) return; | ||
| 226 | + [self loadURL_queryCache]; | ||
| 227 | +} | ||
| 228 | +- (void)loadURL_queryCache { | ||
| 229 | + if (_freezing) return; | ||
| 230 | + if (!self.imageURL || self.imageURL.absoluteString.length == 0) return; | ||
| 231 | + | ||
| 232 | + YBImageDecodeDecision decision = [self defaultDecodeDecision]; | ||
| 233 | + | ||
| 234 | + self.loadingStatus = YBIBImageLoadingStatusQuerying; | ||
| 235 | + [self.yb_webImageMediator() yb_queryCacheOperationForKey:self.imageURL completed:^(UIImage * _Nullable image, NSData * _Nullable imageData) { | ||
| 236 | + if (!imageData || imageData.length == 0) { | ||
| 237 | + YBIB_DISPATCH_ASYNC_MAIN(^{ | ||
| 238 | + self.loadingStatus = YBIBImageLoadingStatusNone; | ||
| 239 | + [self loadURL_download]; | ||
| 240 | + }) | ||
| 241 | + return; | ||
| 242 | + } | ||
| 243 | + | ||
| 244 | + YBIB_DISPATCH_ASYNC(YBIBImageProcessingQueue(), ^{ | ||
| 245 | + if (self->_freezing) { | ||
| 246 | + self.loadingStatus = YBIBImageLoadingStatusNone; | ||
| 247 | + return; | ||
| 248 | + } | ||
| 249 | + YBImage *image = [YBImage imageWithData:imageData scale:UIScreen.mainScreen.scale decodeDecision:decision]; | ||
| 250 | + __weak typeof(self) wSelf = self; | ||
| 251 | + YBIB_DISPATCH_ASYNC_MAIN(^{ | ||
| 252 | + __strong typeof(wSelf) self = wSelf; | ||
| 253 | + if (!self) return; | ||
| 254 | + self.loadingStatus = YBIBImageLoadingStatusNone; | ||
| 255 | + if (image) { // Maybe the image data is invalid. | ||
| 256 | + [self setOriginImageAndLoadWithImage:image]; | ||
| 257 | + } else { | ||
| 258 | + [self loadURL_download]; | ||
| 259 | + } | ||
| 260 | + }) | ||
| 261 | + }) | ||
| 262 | + }]; | ||
| 263 | +} | ||
| 264 | +- (void)loadURL_download { | ||
| 265 | + if (_freezing) return; | ||
| 266 | + if (!self.imageURL || self.imageURL.absoluteString.length == 0) return; | ||
| 267 | + | ||
| 268 | + YBImageDecodeDecision decision = [self defaultDecodeDecision]; | ||
| 269 | + | ||
| 270 | + self.loadingStatus = YBIBImageLoadingStatusDownloading; | ||
| 271 | + __weak typeof(self) wSelf = self; | ||
| 272 | + _downloadToken = [self.yb_webImageMediator() yb_downloadImageWithURL:self.imageURL requestModifier:^NSURLRequest * _Nullable(NSURLRequest * _Nonnull request) { | ||
| 273 | + return self.requestModifier ? self.requestModifier(self, request) : request; | ||
| 274 | + } progress:^(NSInteger receivedSize, NSInteger expectedSize) { | ||
| 275 | + CGFloat progress = receivedSize * 1.0 / expectedSize ?: 0; | ||
| 276 | + YBIB_DISPATCH_ASYNC_MAIN(^{ | ||
| 277 | + __strong typeof(wSelf) self = wSelf; | ||
| 278 | + if (!self) return; | ||
| 279 | + [self.delegate yb_imageData:self downloadProgress:progress]; | ||
| 280 | + }) | ||
| 281 | + } success:^(NSData * _Nullable imageData, BOOL finished) { | ||
| 282 | + if (!finished) return; | ||
| 283 | + | ||
| 284 | + YBIB_DISPATCH_ASYNC(YBIBImageProcessingQueue(), ^{ | ||
| 285 | + if (self->_freezing) { | ||
| 286 | + self.loadingStatus = YBIBImageLoadingStatusNone; | ||
| 287 | + return; | ||
| 288 | + } | ||
| 289 | + YBImage *image = [YBImage imageWithData:imageData scale:UIScreen.mainScreen.scale decodeDecision:decision]; | ||
| 290 | + YBIB_DISPATCH_ASYNC_MAIN(^{ | ||
| 291 | + __strong typeof(wSelf) self = wSelf; | ||
| 292 | + if (!self) return; | ||
| 293 | + [self.yb_webImageMediator() yb_storeToDiskWithImageData:imageData forKey:self.imageURL]; | ||
| 294 | + self.loadingStatus = YBIBImageLoadingStatusNone; | ||
| 295 | + if (image) { | ||
| 296 | + [self setOriginImageAndLoadWithImage:image]; | ||
| 297 | + } else { | ||
| 298 | + [self.delegate yb_imageIsInvalidForData:self]; | ||
| 299 | + } | ||
| 300 | + }) | ||
| 301 | + }) | ||
| 302 | + } failed:^(NSError * _Nullable error, BOOL finished) { | ||
| 303 | + if (!finished) return; | ||
| 304 | + __strong typeof(wSelf) self = wSelf; | ||
| 305 | + if (!self) return; | ||
| 306 | + self.loadingStatus = YBIBImageLoadingStatusNone; | ||
| 307 | + [self.delegate yb_imageDownloadFailedForData:self]; | ||
| 308 | + }]; | ||
| 309 | +} | ||
| 310 | + | ||
| 311 | +- (void)loadPHAsset { | ||
| 312 | + if (_freezing) return; | ||
| 313 | + if (!self.imagePHAsset) return; | ||
| 314 | + | ||
| 315 | + YBImageDecodeDecision decision = [self defaultDecodeDecision]; | ||
| 316 | + | ||
| 317 | + self.loadingStatus = YBIBImageLoadingStatusReadingPHAsset; | ||
| 318 | + YBIB_DISPATCH_ASYNC(YBIBImageProcessingQueue(), ^{ | ||
| 319 | + [YBIBPhotoAlbumManager getImageDataWithPHAsset:self.imagePHAsset completion:^(NSData * _Nullable data) { | ||
| 320 | + if (self->_freezing) { | ||
| 321 | + self.loadingStatus = YBIBImageLoadingStatusNone; | ||
| 322 | + return; | ||
| 323 | + } | ||
| 324 | + YBImage *image = [YBImage imageWithData:data scale:UIScreen.mainScreen.scale decodeDecision:decision]; | ||
| 325 | + __weak typeof(self) wSelf = self; | ||
| 326 | + YBIB_DISPATCH_ASYNC_MAIN(^{ | ||
| 327 | + __strong typeof(wSelf) self = wSelf; | ||
| 328 | + if (!self) return; | ||
| 329 | + | ||
| 330 | + self.loadingStatus = YBIBImageLoadingStatusNone; | ||
| 331 | + if (image) { | ||
| 332 | + [self setOriginImageAndLoadWithImage:image]; | ||
| 333 | + } else { | ||
| 334 | + [self.delegate yb_imageIsInvalidForData:self]; | ||
| 335 | + } | ||
| 336 | + }) | ||
| 337 | + }]; | ||
| 338 | + }) | ||
| 339 | +} | ||
| 340 | + | ||
| 341 | +- (void)loadThumbImage { | ||
| 342 | + if (_freezing) return; | ||
| 343 | + if (self.thumbImage) { | ||
| 344 | + [self.delegate yb_imageData:self readyForThumbImage:self.thumbImage]; | ||
| 345 | + } else if (self.thumbURL) { | ||
| 346 | + __weak typeof(self) wSelf = self; | ||
| 347 | + [self.yb_webImageMediator() yb_queryCacheOperationForKey:self.thumbURL completed:^(UIImage * _Nullable image, NSData * _Nullable imageData) { | ||
| 348 | + __strong typeof(wSelf) self = wSelf; | ||
| 349 | + if (!self) return; | ||
| 350 | + | ||
| 351 | + UIImage *thumbImage; | ||
| 352 | + if (image) { | ||
| 353 | + thumbImage = image; | ||
| 354 | + } else if (imageData) { | ||
| 355 | + thumbImage = [UIImage imageWithData:imageData]; | ||
| 356 | + } | ||
| 357 | + // If the target image is ready, ignore the thumb image. | ||
| 358 | + BOOL shouldIgnore = [self shouldCompress] ? (self.compressedImage != nil) : (self.originImage != nil); | ||
| 359 | + if (!shouldIgnore && thumbImage) { | ||
| 360 | + [self.delegate yb_imageData:self readyForThumbImage:thumbImage]; | ||
| 361 | + } | ||
| 362 | + }]; | ||
| 363 | + } else if (self.projectiveView && [self.projectiveView isKindOfClass:UIImageView.self] && ((UIImageView *)self.projectiveView).image) { | ||
| 364 | + UIImage *thumbImage = ((UIImageView *)self.projectiveView).image; | ||
| 365 | + [self.delegate yb_imageData:self readyForThumbImage:thumbImage]; | ||
| 366 | + } | ||
| 367 | +} | ||
| 368 | + | ||
| 369 | +#pragma mark - internal | ||
| 370 | + | ||
| 371 | +- (void)cuttingImageToRect:(CGRect)rect complete:(void (^)(UIImage * _Nullable))complete { | ||
| 372 | + if (_freezing) return; | ||
| 373 | + if (!self.originImage) return; | ||
| 374 | + | ||
| 375 | + int32_t value = [_cuttingSentinel increase]; | ||
| 376 | + BOOL (^isCancelled)(void) = ^BOOL(void) { | ||
| 377 | + if (self->_freezing) return YES; | ||
| 378 | + return value != self->_cuttingSentinel.value; | ||
| 379 | + }; | ||
| 380 | + | ||
| 381 | + YBIB_DISPATCH_ASYNC(YBIBImageProcessingQueue(), ^{ | ||
| 382 | + if (isCancelled()) { | ||
| 383 | + complete(nil); | ||
| 384 | + return; | ||
| 385 | + } | ||
| 386 | + CGFloat scale = self.originImage.scale; | ||
| 387 | + CGFloat width = self.originImage.size.width; | ||
| 388 | + CGFloat height = self.originImage.size.height; | ||
| 389 | + | ||
| 390 | + BOOL reverseWidthHeight = NO; | ||
| 391 | + CGAffineTransform transform = CGAffineTransformIdentity; | ||
| 392 | + switch (self.originImage.imageOrientation) { | ||
| 393 | + case UIImageOrientationDown: | ||
| 394 | + case UIImageOrientationDownMirrored: | ||
| 395 | + CGAffineTransformTranslate(CGAffineTransformMakeRotation(-M_PI), -width, -height); | ||
| 396 | + break; | ||
| 397 | + case UIImageOrientationLeft: | ||
| 398 | + case UIImageOrientationLeftMirrored: | ||
| 399 | + transform = CGAffineTransformTranslate(CGAffineTransformMakeRotation(M_PI_2), 0, -height); | ||
| 400 | + reverseWidthHeight = YES; | ||
| 401 | + break; | ||
| 402 | + case UIImageOrientationRight: | ||
| 403 | + case UIImageOrientationRightMirrored: | ||
| 404 | + transform = CGAffineTransformTranslate(CGAffineTransformMakeRotation(-M_PI_2), -width, 0); | ||
| 405 | + reverseWidthHeight = YES; | ||
| 406 | + break; | ||
| 407 | + default: break; | ||
| 408 | + } | ||
| 409 | + transform = CGAffineTransformScale(transform, scale, scale); | ||
| 410 | + CGRect correctRect = CGRectApplyAffineTransform(rect, transform); | ||
| 411 | + CGImageRef cgImage = CGImageCreateWithImageInRect(self.originImage.CGImage, correctRect); | ||
| 412 | + | ||
| 413 | + if (isCancelled()) { | ||
| 414 | + complete(nil); | ||
| 415 | + if (cgImage) CGImageRelease(cgImage); | ||
| 416 | + return; | ||
| 417 | + } | ||
| 418 | + CGFloat cgWidth = reverseWidthHeight ? CGImageGetHeight(cgImage) : CGImageGetWidth(cgImage); | ||
| 419 | + CGFloat cgHeight = reverseWidthHeight ? CGImageGetWidth(cgImage) : CGImageGetHeight(cgImage); | ||
| 420 | + CGSize size = [self bestSizeOfCuttingWithOriginSize:CGSizeMake(cgWidth / scale, cgHeight / scale)]; | ||
| 421 | + UIImage *tmpImage = [UIImage imageWithCGImage:cgImage scale:self.originImage.scale orientation:self.originImage.imageOrientation]; | ||
| 422 | + if (isCancelled()) { | ||
| 423 | + complete(nil); | ||
| 424 | + if (cgImage) CGImageRelease(cgImage); | ||
| 425 | + return; | ||
| 426 | + } | ||
| 427 | + | ||
| 428 | + // Ensure the best display effect. | ||
| 429 | + UIGraphicsBeginImageContextWithOptions(size, NO, UIScreen.mainScreen.scale); | ||
| 430 | + [tmpImage drawInRect:CGRectMake(0, 0, size.width, size.height)]; | ||
| 431 | + if (isCancelled()) { | ||
| 432 | + complete(nil); | ||
| 433 | + UIGraphicsEndImageContext(); | ||
| 434 | + if (cgImage) CGImageRelease(cgImage); | ||
| 435 | + return; | ||
| 436 | + } | ||
| 437 | + UIImage *resultImage = UIGraphicsGetImageFromCurrentImageContext(); | ||
| 438 | + UIGraphicsEndImageContext(); | ||
| 439 | + if (cgImage) CGImageRelease(cgImage); | ||
| 440 | + | ||
| 441 | + __weak typeof(self) wSelf = self; | ||
| 442 | + YBIB_DISPATCH_ASYNC_MAIN(^{ | ||
| 443 | + __strong typeof(wSelf) self = wSelf; | ||
| 444 | + if (!self) return; | ||
| 445 | + | ||
| 446 | + [self modifyImageWithModifier:self.cuttedImageModifier image:resultImage completion:^(UIImage *image) { | ||
| 447 | + __strong typeof(wSelf) self = wSelf; | ||
| 448 | + if (!self) return; | ||
| 449 | + complete(image); | ||
| 450 | + }]; | ||
| 451 | + }) | ||
| 452 | + }) | ||
| 453 | +} | ||
| 454 | + | ||
| 455 | +- (BOOL)shouldCompress { | ||
| 456 | + return [self shouldCompressWithImage:self.originImage]; | ||
| 457 | +} | ||
| 458 | + | ||
| 459 | +#pragma mark - public | ||
| 460 | + | ||
| 461 | +- (BOOL)shouldCompressWithImage:(UIImage *)image { | ||
| 462 | + if (!image) return NO; | ||
| 463 | + return [self shouldCompressWithImageSize:image.size scale:image.scale]; | ||
| 464 | +} | ||
| 465 | + | ||
| 466 | +- (void)stopLoading { | ||
| 467 | + _freezing = YES; | ||
| 468 | + self.loadingStatus = YBIBImageLoadingStatusNone; | ||
| 469 | +} | ||
| 470 | + | ||
| 471 | +- (void)clearCache { | ||
| 472 | + [self.imageCache removeForKey:self.cacheKey]; | ||
| 473 | +} | ||
| 474 | + | ||
| 475 | +#pragma mark - private | ||
| 476 | + | ||
| 477 | +/// 'size': logic pixel. | ||
| 478 | +- (BOOL)shouldCompressWithImageSize:(CGSize)size scale:(CGFloat)scale { | ||
| 479 | + return size.width * scale * size.height * scale > self.compressingSize; | ||
| 480 | +} | ||
| 481 | + | ||
| 482 | +/// Logic pixel. | ||
| 483 | +- (CGSize)bestSizeOfCompressing { | ||
| 484 | + if (!self.originImage) return CGSizeZero; | ||
| 485 | + UIDeviceOrientation orientation = self.yb_currentOrientation(); | ||
| 486 | + CGRect imageViewFrame = [self.layout yb_imageViewFrameWithContainerSize:self.yb_containerSize(orientation) imageSize:self.originImage.size orientation:orientation]; | ||
| 487 | + return imageViewFrame.size; | ||
| 488 | +} | ||
| 489 | + | ||
| 490 | +/// Logic pixel. | ||
| 491 | +- (CGSize)bestSizeOfCuttingWithOriginSize:(CGSize)originSize { | ||
| 492 | + CGSize containerSize = self.yb_containerSize(self.yb_currentOrientation()); | ||
| 493 | + CGFloat maxWidth = containerSize.width, maxHeight = containerSize.height; | ||
| 494 | + CGFloat oWidth = originSize.width, oHeight = originSize.height; | ||
| 495 | + if (maxWidth <= 0 || maxHeight <= 0 || oWidth <= 0 || oHeight <= 0) return CGSizeZero; | ||
| 496 | + | ||
| 497 | + if (oWidth <= maxWidth && oHeight <= maxHeight) { | ||
| 498 | + return originSize; | ||
| 499 | + } | ||
| 500 | + CGFloat rWidth = 0, rHeight = 0; | ||
| 501 | + if (oWidth / maxWidth < oHeight / maxHeight) { | ||
| 502 | + rHeight = maxHeight; | ||
| 503 | + rWidth = oWidth / oHeight * rHeight; | ||
| 504 | + } else { | ||
| 505 | + rWidth = maxWidth; | ||
| 506 | + rHeight = oHeight / oWidth * rWidth; | ||
| 507 | + } | ||
| 508 | + return CGSizeMake(rWidth, rHeight); | ||
| 509 | +} | ||
| 510 | + | ||
| 511 | +- (YBImageDecodeDecision)defaultDecodeDecision { | ||
| 512 | + __weak typeof(self) wSelf = self; | ||
| 513 | + return ^BOOL(CGSize imageSize, CGFloat scale) { | ||
| 514 | + __strong typeof(wSelf) self = wSelf; | ||
| 515 | + if (!self) return NO; | ||
| 516 | + CGSize logicSize = CGSizeMake(imageSize.width / scale, imageSize.height / scale); | ||
| 517 | + if (self.preDecodeDecision) return self.preDecodeDecision(self, logicSize, scale); | ||
| 518 | + if ([self shouldCompressWithImageSize:logicSize scale:scale]) return NO; | ||
| 519 | + return YES; | ||
| 520 | + }; | ||
| 521 | +} | ||
| 522 | + | ||
| 523 | +- (void)modifyImageWithModifier:(YBIBImageModifierBlock)modifier image:(UIImage *)image completion:(void(^)(UIImage *processedImage))completion { | ||
| 524 | + if (modifier) { | ||
| 525 | + self.loadingStatus = YBIBImageLoadingStatusProcessing; | ||
| 526 | + __weak typeof(self) wSelf = self; | ||
| 527 | + modifier(self, image, ^(UIImage *processedImage){ | ||
| 528 | + // This step is necessary, maybe 'self' is already 'dealloc' if processing code takes too much time. | ||
| 529 | + __strong typeof(wSelf) self = wSelf; | ||
| 530 | + if (!self) return; | ||
| 531 | + self.loadingStatus = YBIBImageLoadingStatusNone; | ||
| 532 | + completion(processedImage); | ||
| 533 | + }); | ||
| 534 | + } else { | ||
| 535 | + completion(image); | ||
| 536 | + } | ||
| 537 | +} | ||
| 538 | + | ||
| 539 | +- (void)setOriginImageAndLoadWithImage:(UIImage *)image { | ||
| 540 | + __weak typeof(self) wSelf = self; | ||
| 541 | + [self modifyImageWithModifier:self.originImageModifier image:image completion:^(UIImage *processedImage) { | ||
| 542 | + __strong typeof(wSelf) self = wSelf; | ||
| 543 | + if (!self) return; | ||
| 544 | + self.originImage = processedImage; | ||
| 545 | + [self loadOriginImage]; | ||
| 546 | + }]; | ||
| 547 | +} | ||
| 548 | + | ||
| 549 | +- (void)saveToPhotoAlbumCompleteWithError:(nullable NSError *)error { | ||
| 550 | + if (error) { | ||
| 551 | + [self.yb_auxiliaryViewHandler() yb_showIncorrectToastWithContainer:self.yb_containerView text:[YBIBCopywriter sharedCopywriter].saveToPhotoAlbumFailed]; | ||
| 552 | + } else { | ||
| 553 | + [self.yb_auxiliaryViewHandler() yb_showCorrectToastWithContainer:self.yb_containerView text:[YBIBCopywriter sharedCopywriter].saveToPhotoAlbumSuccess]; | ||
| 554 | + } | ||
| 555 | +} | ||
| 556 | + | ||
| 557 | +- (void)UIImageWriteToSavedPhotosAlbum_completedWithImage:(UIImage *)image error:(NSError *)error context:(void *)context { | ||
| 558 | + [self saveToPhotoAlbumCompleteWithError:error]; | ||
| 559 | +} | ||
| 560 | + | ||
| 561 | +- (YBIBImageCache *)imageCache { | ||
| 562 | + return self.yb_backView.ybib_imageCache; | ||
| 563 | +} | ||
| 564 | + | ||
| 565 | +#pragma mark - <YBIBDataProtocol> | ||
| 566 | + | ||
| 567 | +@synthesize yb_isHideTransitioning = _yb_isHideTransitioning; | ||
| 568 | +@synthesize yb_currentOrientation = _yb_currentOrientation; | ||
| 569 | +@synthesize yb_containerSize = _yb_containerSize; | ||
| 570 | +@synthesize yb_containerView = _yb_containerView; | ||
| 571 | +@synthesize yb_auxiliaryViewHandler = _yb_auxiliaryViewHandler; | ||
| 572 | +@synthesize yb_webImageMediator = _yb_webImageMediator; | ||
| 573 | +@synthesize yb_backView = _yb_backView; | ||
| 574 | + | ||
| 575 | +- (nonnull Class)yb_classOfCell { | ||
| 576 | + return YBIBImageCell.self; | ||
| 577 | +} | ||
| 578 | + | ||
| 579 | +- (UIView *)yb_projectiveView { | ||
| 580 | + return self.projectiveView; | ||
| 581 | +} | ||
| 582 | + | ||
| 583 | +- (CGRect)yb_imageViewFrameWithContainerSize:(CGSize)containerSize imageSize:(CGSize)imageSize orientation:(UIDeviceOrientation)orientation { | ||
| 584 | + return [self.layout yb_imageViewFrameWithContainerSize:containerSize imageSize:imageSize orientation:orientation]; | ||
| 585 | +} | ||
| 586 | + | ||
| 587 | +- (void)yb_preload { | ||
| 588 | + if (!self.delegate) { | ||
| 589 | + [self loadData]; | ||
| 590 | + } | ||
| 591 | +} | ||
| 592 | + | ||
| 593 | +- (BOOL)yb_allowSaveToPhotoAlbum { | ||
| 594 | + return self.allowSaveToPhotoAlbum; | ||
| 595 | +} | ||
| 596 | + | ||
| 597 | +- (void)yb_saveToPhotoAlbum { | ||
| 598 | + void(^saveData)(NSData *) = ^(NSData * _Nonnull data){ | ||
| 599 | + [[ALAssetsLibrary new] writeImageDataToSavedPhotosAlbum:data metadata:nil completionBlock:^(NSURL *assetURL, NSError *error) { | ||
| 600 | + [self saveToPhotoAlbumCompleteWithError:error]; | ||
| 601 | + }]; | ||
| 602 | + }; | ||
| 603 | + void(^saveImage)(UIImage *) = ^(UIImage * _Nonnull image){ | ||
| 604 | + UIImageWriteToSavedPhotosAlbum(image, self, @selector(UIImageWriteToSavedPhotosAlbum_completedWithImage:error:context:), NULL); | ||
| 605 | + }; | ||
| 606 | + void(^unableToSave)(void) = ^(){ | ||
| 607 | + [self.yb_auxiliaryViewHandler() yb_showIncorrectToastWithContainer:self.yb_containerView text:[YBIBCopywriter sharedCopywriter].unableToSave]; | ||
| 608 | + }; | ||
| 609 | + | ||
| 610 | + [YBIBPhotoAlbumManager getPhotoAlbumAuthorizationSuccess:^{ | ||
| 611 | + if ([self.originImage conformsToProtocol:@protocol(YYAnimatedImage)] && [self.originImage respondsToSelector:@selector(animatedImageData)] && [self.originImage performSelector:@selector(animatedImageData)]) { | ||
| 612 | + NSData *data = [self.originImage performSelector:@selector(animatedImageData)]; | ||
| 613 | + data ? saveData(data) : unableToSave(); | ||
| 614 | + } else if (self.originImage) { | ||
| 615 | + saveImage(self.originImage); | ||
| 616 | + } else if (self.imageURL) { | ||
| 617 | + [self.yb_webImageMediator() yb_queryCacheOperationForKey:self.imageURL completed:^(UIImage * _Nullable image, NSData * _Nullable data) { | ||
| 618 | + if (data) { | ||
| 619 | + saveData(data); | ||
| 620 | + } else if (image) { | ||
| 621 | + saveImage(image); | ||
| 622 | + } else { | ||
| 623 | + unableToSave(); | ||
| 624 | + } | ||
| 625 | + }]; | ||
| 626 | + } else { | ||
| 627 | + unableToSave(); | ||
| 628 | + } | ||
| 629 | + } failed:^{ | ||
| 630 | + [self.yb_auxiliaryViewHandler() yb_showIncorrectToastWithContainer:self.yb_containerView text:[YBIBCopywriter sharedCopywriter].getPhotoAlbumAuthorizationFailed]; | ||
| 631 | + }]; | ||
| 632 | +} | ||
| 633 | + | ||
| 634 | +#pragma mark - getters & setters | ||
| 635 | + | ||
| 636 | +@synthesize delegate = _delegate; | ||
| 637 | +- (void)setDelegate:(id<YBIBImageDataDelegate>)delegate { | ||
| 638 | + _delegate = delegate; | ||
| 639 | + if (delegate) { | ||
| 640 | + [self loadData]; | ||
| 641 | + } else { | ||
| 642 | + _freezing = YES; | ||
| 643 | + // Remove the resident cache if '_delegate' is nil. | ||
| 644 | + [self.imageCache removeResidentForKey:self.cacheKey]; | ||
| 645 | + } | ||
| 646 | +} | ||
| 647 | +- (id<YBIBImageDataDelegate>)delegate { | ||
| 648 | + // Stop sending data to the '_delegate' if it is transiting. | ||
| 649 | + return self.yb_isHideTransitioning() ? nil : _delegate; | ||
| 650 | +} | ||
| 651 | + | ||
| 652 | +- (void)setImageURL:(NSURL *)imageURL { | ||
| 653 | + _imageURL = [imageURL isKindOfClass:NSString.class] ? [NSURL URLWithString:(NSString *)imageURL] : imageURL; | ||
| 654 | +} | ||
| 655 | + | ||
| 656 | +- (NSString *)cacheKey { | ||
| 657 | + return [NSString stringWithFormat:@"%p", self]; | ||
| 658 | +} | ||
| 659 | + | ||
| 660 | +- (void)setOriginImage:(__kindof UIImage *)originImage { | ||
| 661 | + // 'image' should be resident if '_delegate' exists. | ||
| 662 | + [self.imageCache setImage:originImage type:YBIBImageCacheTypeOrigin forKey:self.cacheKey resident:self->_delegate != nil]; | ||
| 663 | +} | ||
| 664 | +- (UIImage *)originImage { | ||
| 665 | + return [self.imageCache imageForKey:self.cacheKey type:YBIBImageCacheTypeOrigin]; | ||
| 666 | +} | ||
| 667 | + | ||
| 668 | +- (void)setCompressedImage:(UIImage *)compressedImage { | ||
| 669 | + // 'image' should be resident if '_delegate' exists. | ||
| 670 | + [self.imageCache setImage:compressedImage type:YBIBImageCacheTypeCompressed forKey:self.cacheKey resident:_delegate != nil]; | ||
| 671 | +} | ||
| 672 | +- (UIImage *)compressedImage { | ||
| 673 | + return [self.imageCache imageForKey:self.cacheKey type:YBIBImageCacheTypeCompressed]; | ||
| 674 | +} | ||
| 675 | + | ||
| 676 | +- (void)setLoadingStatus:(YBIBImageLoadingStatus)loadingStatus { | ||
| 677 | + // Ensure thread safety. | ||
| 678 | + YBIB_DISPATCH_ASYNC_MAIN(^{ | ||
| 679 | + self->_loadingStatus = loadingStatus; | ||
| 680 | + [self.delegate yb_imageData:self startLoadingWithStatus:loadingStatus]; | ||
| 681 | + }); | ||
| 682 | +} | ||
| 683 | + | ||
| 684 | +- (CGFloat)cuttingZoomScale { | ||
| 685 | + if (_cuttingZoomScale >= 1) return _cuttingZoomScale; | ||
| 686 | + _cuttingZoomScale = 1.1; | ||
| 687 | + if (!self.originImage) return _cuttingZoomScale; | ||
| 688 | + CGFloat imagePixel = self.originImage.size.width * self.originImage.size.height * self.originImage.scale * self.originImage.scale; | ||
| 689 | + // The larger the image size, the larger the '_cuttingZoomScale', in order to reduce the burden of CPU and memory. | ||
| 690 | + CGFloat scale = YBIBLowMemory() ? 0.28 : 0.19; | ||
| 691 | + _cuttingZoomScale += (imagePixel / self.compressingSize * scale); | ||
| 692 | + return _cuttingZoomScale; | ||
| 693 | +} | ||
| 694 | + | ||
| 695 | +@end |
CNLiveOrganizationValidationModule/Classes/CNLiveOrganizationValidationModule/YBImageBrowser/Image/YBIBImageLayout.h
0 → 100644
| 1 | +// | ||
| 2 | +// YBIBImageLayout.h | ||
| 3 | +// YBImageBrowserDemo | ||
| 4 | +// | ||
| 5 | +// Created by 波儿菜 on 2019/6/12. | ||
| 6 | +// Copyright © 2019 波儿菜. All rights reserved. | ||
| 7 | +// | ||
| 8 | + | ||
| 9 | +#import <UIKit/UIKit.h> | ||
| 10 | + | ||
| 11 | +NS_ASSUME_NONNULL_BEGIN | ||
| 12 | + | ||
| 13 | +@protocol YBIBImageLayout <NSObject> | ||
| 14 | +@required | ||
| 15 | + | ||
| 16 | +/** | ||
| 17 | + 计算图片展示的位置 | ||
| 18 | + | ||
| 19 | + @param containerSize 容器大小 | ||
| 20 | + @param imageSize 图片大小 (逻辑像素) | ||
| 21 | + @param orientation 图片浏览器的方向 | ||
| 22 | + @return 图片展示的位置 (frame) | ||
| 23 | + */ | ||
| 24 | +- (CGRect)yb_imageViewFrameWithContainerSize:(CGSize)containerSize imageSize:(CGSize)imageSize orientation:(UIDeviceOrientation)orientation; | ||
| 25 | + | ||
| 26 | +/** | ||
| 27 | + 计算最大缩放比例 | ||
| 28 | + | ||
| 29 | + @param containerSize 容器大小 | ||
| 30 | + @param imageSize 图片大小 (逻辑像素) | ||
| 31 | + @param orientation 图片浏览器的方向 | ||
| 32 | + @return 最大缩放比例 | ||
| 33 | + */ | ||
| 34 | +- (CGFloat)yb_maximumZoomScaleWithContainerSize:(CGSize)containerSize imageSize:(CGSize)imageSize orientation:(UIDeviceOrientation)orientation; | ||
| 35 | + | ||
| 36 | +@end | ||
| 37 | + | ||
| 38 | +typedef NS_ENUM(NSUInteger, YBIBImageFillType) { | ||
| 39 | + /// 宽度优先填充满 | ||
| 40 | + YBIBImageFillTypeFullWidth, | ||
| 41 | + /// 完整显示 | ||
| 42 | + YBIBImageFillTypeCompletely | ||
| 43 | +}; | ||
| 44 | + | ||
| 45 | +@interface YBIBImageLayout : NSObject <YBIBImageLayout> | ||
| 46 | + | ||
| 47 | +/// 纵向的填充方式,默认 YBIBImageFillTypeCompletely | ||
| 48 | +@property (nonatomic, assign) YBIBImageFillType verticalFillType; | ||
| 49 | + | ||
| 50 | +/// 横向的填充方式,默认 YBIBImageFillTypeFullWidth | ||
| 51 | +@property (nonatomic, assign) YBIBImageFillType horizontalFillType; | ||
| 52 | + | ||
| 53 | +/// 最大缩放比例 (必须大于 1 才有效,若不指定内部会自动计算) | ||
| 54 | +@property (nonatomic, assign) CGFloat maxZoomScale; | ||
| 55 | + | ||
| 56 | +/// 自动计算严格缩放比例后,再乘以这个值作为最终缩放比例,默认 1.5 | ||
| 57 | +@property (nonatomic, assign) CGFloat zoomScaleSurplus; | ||
| 58 | + | ||
| 59 | +@end | ||
| 60 | + | ||
| 61 | +NS_ASSUME_NONNULL_END |
CNLiveOrganizationValidationModule/Classes/CNLiveOrganizationValidationModule/YBImageBrowser/Image/YBIBImageLayout.m
0 → 100644
| 1 | +// | ||
| 2 | +// YBIBImageLayout.m | ||
| 3 | +// YBImageBrowserDemo | ||
| 4 | +// | ||
| 5 | +// Created by 波儿菜 on 2019/6/12. | ||
| 6 | +// Copyright © 2019 波儿菜. All rights reserved. | ||
| 7 | +// | ||
| 8 | + | ||
| 9 | +#import "YBIBImageLayout.h" | ||
| 10 | + | ||
| 11 | +@implementation YBIBImageLayout | ||
| 12 | + | ||
| 13 | +#pragma mark - life cycle | ||
| 14 | + | ||
| 15 | +- (instancetype)init { | ||
| 16 | + self = [super init]; | ||
| 17 | + if (self) { | ||
| 18 | + _verticalFillType = YBIBImageFillTypeCompletely; | ||
| 19 | + _horizontalFillType = YBIBImageFillTypeFullWidth; | ||
| 20 | + _zoomScaleSurplus = 1.5; | ||
| 21 | + } | ||
| 22 | + return self; | ||
| 23 | +} | ||
| 24 | + | ||
| 25 | +#pragma mark - private | ||
| 26 | + | ||
| 27 | +- (YBIBImageFillType)fillTypeByOrientation:(UIDeviceOrientation)orientation { | ||
| 28 | + return UIDeviceOrientationIsLandscape(orientation) ? self.horizontalFillType : self.verticalFillType; | ||
| 29 | +} | ||
| 30 | + | ||
| 31 | +#pragma mark - <YBIBImageLayout> | ||
| 32 | + | ||
| 33 | +- (CGRect)yb_imageViewFrameWithContainerSize:(CGSize)containerSize imageSize:(CGSize)imageSize orientation:(UIDeviceOrientation)orientation { | ||
| 34 | + if (containerSize.width <= 0 || containerSize.height <= 0 || imageSize.width <= 0 || imageSize.height <= 0) return CGRectZero; | ||
| 35 | + | ||
| 36 | + CGFloat x = 0, y = 0, width = 0, height = 0; | ||
| 37 | + switch ([self fillTypeByOrientation:orientation]) { | ||
| 38 | + case YBIBImageFillTypeFullWidth: { | ||
| 39 | + x = 0; | ||
| 40 | + width = containerSize.width; | ||
| 41 | + height = containerSize.width * (imageSize.height / imageSize.width); | ||
| 42 | + if (imageSize.width / imageSize.height >= containerSize.width / containerSize.height) | ||
| 43 | + y = (containerSize.height - height) / 2.0; | ||
| 44 | + else | ||
| 45 | + y = 0; | ||
| 46 | + } | ||
| 47 | + break; | ||
| 48 | + case YBIBImageFillTypeCompletely: { | ||
| 49 | + if (imageSize.width / imageSize.height >= containerSize.width / containerSize.height) { | ||
| 50 | + width = containerSize.width; | ||
| 51 | + height = containerSize.width * (imageSize.height / imageSize.width); | ||
| 52 | + x = 0; | ||
| 53 | + y = (containerSize.height - height) / 2.0; | ||
| 54 | + } else { | ||
| 55 | + height = containerSize.height; | ||
| 56 | + width = containerSize.height * (imageSize.width / imageSize.height); | ||
| 57 | + x = (containerSize.width - width) / 2.0; | ||
| 58 | + y = 0; | ||
| 59 | + } | ||
| 60 | + } | ||
| 61 | + break; | ||
| 62 | + default: return CGRectZero; | ||
| 63 | + } | ||
| 64 | + return CGRectMake(x, y, width, height); | ||
| 65 | +} | ||
| 66 | + | ||
| 67 | +- (CGFloat)yb_maximumZoomScaleWithContainerSize:(CGSize)containerSize imageSize:(CGSize)imageSize orientation:(UIDeviceOrientation)orientation { | ||
| 68 | + if (self.maxZoomScale >= 1) return self.maxZoomScale; | ||
| 69 | + | ||
| 70 | + if (containerSize.width <= 0 || containerSize.height <= 0) return 0; | ||
| 71 | + | ||
| 72 | + CGFloat scale = [UIScreen mainScreen].scale; | ||
| 73 | + if (scale <= 0) return 0; | ||
| 74 | + | ||
| 75 | + CGFloat widthScale = imageSize.width / scale / containerSize.width, | ||
| 76 | + heightScale = imageSize.height / scale / containerSize.height, | ||
| 77 | + maxScale = 1; | ||
| 78 | + switch ([self fillTypeByOrientation:orientation]) { | ||
| 79 | + case YBIBImageFillTypeFullWidth: | ||
| 80 | + maxScale = widthScale; | ||
| 81 | + break; | ||
| 82 | + case YBIBImageFillTypeCompletely: | ||
| 83 | + maxScale = MAX(widthScale, heightScale); | ||
| 84 | + break; | ||
| 85 | + default: return 0; | ||
| 86 | + } | ||
| 87 | + return MAX(maxScale, 1) * self.zoomScaleSurplus; | ||
| 88 | +} | ||
| 89 | + | ||
| 90 | +@end |
CNLiveOrganizationValidationModule/Classes/CNLiveOrganizationValidationModule/YBImageBrowser/Image/YBIBImageScrollView.h
0 → 100644
| 1 | +// | ||
| 2 | +// YBIBImageScrollView.h | ||
| 3 | +// YBImageBrowserDemo | ||
| 4 | +// | ||
| 5 | +// Created by 波儿菜 on 2019/6/10. | ||
| 6 | +// Copyright © 2019 波儿菜. All rights reserved. | ||
| 7 | +// | ||
| 8 | + | ||
| 9 | +#import "YBImage.h" | ||
| 10 | + | ||
| 11 | +NS_ASSUME_NONNULL_BEGIN | ||
| 12 | + | ||
| 13 | +typedef NS_ENUM(NSInteger, YBIBScrollImageType) { | ||
| 14 | + YBIBScrollImageTypeNone, | ||
| 15 | + YBIBScrollImageTypeOriginal, | ||
| 16 | + YBIBScrollImageTypeCompressed, | ||
| 17 | + YBIBScrollImageTypeThumb | ||
| 18 | +}; | ||
| 19 | + | ||
| 20 | +@interface YBIBImageScrollView : UIScrollView | ||
| 21 | + | ||
| 22 | +- (void)setImage:(__kindof UIImage *)image type:(YBIBScrollImageType)type; | ||
| 23 | + | ||
| 24 | +@property (nonatomic, strong, readonly) YYAnimatedImageView *imageView; | ||
| 25 | + | ||
| 26 | +@property (nonatomic, assign) YBIBScrollImageType imageType; | ||
| 27 | + | ||
| 28 | +- (void)reset; | ||
| 29 | + | ||
| 30 | +@end | ||
| 31 | + | ||
| 32 | +NS_ASSUME_NONNULL_END |
CNLiveOrganizationValidationModule/Classes/CNLiveOrganizationValidationModule/YBImageBrowser/Image/YBIBImageScrollView.m
0 → 100644
| 1 | +// | ||
| 2 | +// YBIBImageScrollView.m | ||
| 3 | +// YBImageBrowserDemo | ||
| 4 | +// | ||
| 5 | +// Created by 波儿菜 on 2019/6/10. | ||
| 6 | +// Copyright © 2019 波儿菜. All rights reserved. | ||
| 7 | +// | ||
| 8 | + | ||
| 9 | +#import "YBIBImageScrollView.h" | ||
| 10 | + | ||
| 11 | +@interface YBIBImageScrollView () | ||
| 12 | +@property (nonatomic, strong) YYAnimatedImageView *imageView; | ||
| 13 | +@end | ||
| 14 | + | ||
| 15 | +@implementation YBIBImageScrollView | ||
| 16 | + | ||
| 17 | +#pragma mark - life cycle | ||
| 18 | + | ||
| 19 | +- (instancetype)initWithFrame:(CGRect)frame { | ||
| 20 | + self = [super initWithFrame:frame]; | ||
| 21 | + if (self) { | ||
| 22 | + self.showsHorizontalScrollIndicator = NO; | ||
| 23 | + self.showsVerticalScrollIndicator = NO; | ||
| 24 | + self.decelerationRate = UIScrollViewDecelerationRateFast; | ||
| 25 | + self.maximumZoomScale = 1; | ||
| 26 | + self.minimumZoomScale = 1; | ||
| 27 | + self.alwaysBounceHorizontal = NO; | ||
| 28 | + self.alwaysBounceVertical = NO; | ||
| 29 | + self.layer.masksToBounds = NO; | ||
| 30 | + if (@available(iOS 11.0, *)) { | ||
| 31 | + self.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever; | ||
| 32 | + } | ||
| 33 | + | ||
| 34 | + [self addSubview:self.imageView]; | ||
| 35 | + } | ||
| 36 | + return self; | ||
| 37 | +} | ||
| 38 | + | ||
| 39 | +#pragma mark - public | ||
| 40 | + | ||
| 41 | +- (void)setImage:(__kindof UIImage *)image type:(YBIBScrollImageType)type { | ||
| 42 | + self.imageView.image = image; | ||
| 43 | + self.imageType = type; | ||
| 44 | +} | ||
| 45 | + | ||
| 46 | +- (void)reset { | ||
| 47 | + self.zoomScale = 1; | ||
| 48 | + self.imageView.image = nil; | ||
| 49 | + self.imageView.frame = CGRectZero; | ||
| 50 | + self.imageType = YBIBScrollImageTypeNone; | ||
| 51 | +} | ||
| 52 | + | ||
| 53 | +#pragma mark - getters | ||
| 54 | + | ||
| 55 | +- (YYAnimatedImageView *)imageView { | ||
| 56 | + if (!_imageView) { | ||
| 57 | + _imageView = [YYAnimatedImageView new]; | ||
| 58 | + _imageView.contentMode = UIViewContentModeScaleAspectFill; | ||
| 59 | + _imageView.layer.masksToBounds = YES; | ||
| 60 | + } | ||
| 61 | + return _imageView; | ||
| 62 | +} | ||
| 63 | + | ||
| 64 | +@end |
CNLiveOrganizationValidationModule/Classes/CNLiveOrganizationValidationModule/YBImageBrowser/Image/YBIBInteractionProfile.h
0 → 100644
| 1 | +// | ||
| 2 | +// YBIBInteractionProfile.h | ||
| 3 | +// YBImageBrowserDemo | ||
| 4 | +// | ||
| 5 | +// Created by 波儿菜 on 2019/6/30. | ||
| 6 | +// Copyright © 2019 杨波. All rights reserved. | ||
| 7 | +// | ||
| 8 | + | ||
| 9 | +#import <UIKit/UIKit.h> | ||
| 10 | + | ||
| 11 | +NS_ASSUME_NONNULL_BEGIN | ||
| 12 | + | ||
| 13 | +@interface YBIBInteractionProfile : NSObject | ||
| 14 | + | ||
| 15 | +/// 是否取消手势交互动效 | ||
| 16 | +@property (nonatomic, assign) BOOL disable; | ||
| 17 | + | ||
| 18 | +/// 拖动的距离与最大高度的比例,达到这个比例就会出场 | ||
| 19 | +@property (nonatomic, assign) CGFloat dismissScale; | ||
| 20 | + | ||
| 21 | +/// 拖动的速度,达到这个值就会出场 | ||
| 22 | +@property (nonatomic, assign) CGFloat dismissVelocityY; | ||
| 23 | + | ||
| 24 | +/// 拖动动效复位时的时长 | ||
| 25 | +@property (nonatomic, assign) CGFloat restoreDuration; | ||
| 26 | + | ||
| 27 | +/// 拖动触发手势交互动效的起始距离 | ||
| 28 | +@property (nonatomic, assign) CGFloat triggerDistance; | ||
| 29 | + | ||
| 30 | +@end | ||
| 31 | + | ||
| 32 | +NS_ASSUME_NONNULL_END |
CNLiveOrganizationValidationModule/Classes/CNLiveOrganizationValidationModule/YBImageBrowser/Image/YBIBInteractionProfile.m
0 → 100644
| 1 | +// | ||
| 2 | +// YBIBInteractionProfile.m | ||
| 3 | +// YBImageBrowserDemo | ||
| 4 | +// | ||
| 5 | +// Created by 波儿菜 on 2019/6/30. | ||
| 6 | +// Copyright © 2019 杨波. All rights reserved. | ||
| 7 | +// | ||
| 8 | + | ||
| 9 | +#import "YBIBInteractionProfile.h" | ||
| 10 | + | ||
| 11 | +@implementation YBIBInteractionProfile | ||
| 12 | + | ||
| 13 | +- (instancetype)init { | ||
| 14 | + self = [super init]; | ||
| 15 | + if (self) { | ||
| 16 | + _disable = NO; | ||
| 17 | + _dismissScale = 0.22; | ||
| 18 | + _dismissVelocityY = 800; | ||
| 19 | + _restoreDuration = 0.15; | ||
| 20 | + _triggerDistance = 3; | ||
| 21 | + } | ||
| 22 | + return self; | ||
| 23 | +} | ||
| 24 | + | ||
| 25 | +@end |
CNLiveOrganizationValidationModule/Classes/CNLiveOrganizationValidationModule/YBImageBrowser/Image/YBImage.h
0 → 100644
| 1 | +// | ||
| 2 | +// YBImage.h | ||
| 3 | +// YBImageBrowserDemo | ||
| 4 | +// | ||
| 5 | +// Created by 波儿菜 on 2018/8/31. | ||
| 6 | +// Copyright © 2018年 波儿菜. All rights reserved. | ||
| 7 | +// | ||
| 8 | + | ||
| 9 | +#if __has_include(<YYImage/YYImage.h>) | ||
| 10 | +#import <YYImage/YYFrameImage.h> | ||
| 11 | +#import <YYImage/YYSpriteSheetImage.h> | ||
| 12 | +#import <YYImage/YYImageCoder.h> | ||
| 13 | +#import <YYImage/YYAnimatedImageView.h> | ||
| 14 | +#elif __has_include(<YYWebImage/YYImage.h>) | ||
| 15 | +#import <YYWebImage/YYFrameImage.h> | ||
| 16 | +#import <YYWebImage/YYSpriteSheetImage.h> | ||
| 17 | +#import <YYWebImage/YYImageCoder.h> | ||
| 18 | +#import <YYWebImage/YYAnimatedImageView.h> | ||
| 19 | +#else | ||
| 20 | +#import "YYFrameImage.h" | ||
| 21 | +#import "YYSpriteSheetImage.h" | ||
| 22 | +#import "YYImageCoder.h" | ||
| 23 | +#import "YYAnimatedImageView.h" | ||
| 24 | +#endif | ||
| 25 | + | ||
| 26 | +NS_ASSUME_NONNULL_BEGIN | ||
| 27 | + | ||
| 28 | +/// 🙄波儿菜:Decide whether should to decode by image size. ('imageSize': physical pixel) | ||
| 29 | +typedef BOOL(^YBImageDecodeDecision)(CGSize imageSize, CGFloat scale); | ||
| 30 | + | ||
| 31 | +/** | ||
| 32 | + It is a fully compatible `UIImage` subclass. It extends the UIImage | ||
| 33 | + to support animated WebP, APNG and GIF format image data decoding. It also | ||
| 34 | + support NSCoding protocol to archive and unarchive multi-frame image data. | ||
| 35 | + | ||
| 36 | + If the image is created from multi-frame image data, and you want to play the | ||
| 37 | + animation, try replace UIImageView with `YYAnimatedImageView`. | ||
| 38 | + | ||
| 39 | + 🙄波儿菜:Copied from 'YYImage' and made some extensions. | ||
| 40 | + */ | ||
| 41 | + | ||
| 42 | +@interface YBImage : UIImage <YYAnimatedImage> | ||
| 43 | + | ||
| 44 | ++ (nullable __kindof UIImage *)imageNamed:(NSString *)name; // no cache! | ||
| 45 | ++ (nullable YBImage *)imageWithContentsOfFile:(NSString *)path; | ||
| 46 | ++ (nullable YBImage *)imageWithData:(NSData *)data; | ||
| 47 | ++ (nullable YBImage *)imageWithData:(NSData *)data scale:(CGFloat)scale; | ||
| 48 | + | ||
| 49 | +/// 🙄波儿菜:Expand methodes. | ||
| 50 | +/// Start -> | ||
| 51 | ++ (nullable __kindof UIImage *)imageNamed:(NSString *)name decodeDecision:(nullable YBImageDecodeDecision)decodeDecision; | ||
| 52 | ++ (nullable YBImage *)imageWithContentsOfFile:(NSString *)path decodeDecision:(nullable YBImageDecodeDecision)decodeDecision; | ||
| 53 | ++ (nullable YBImage *)imageWithData:(NSData *)data decodeDecision:(nullable YBImageDecodeDecision)decodeDecision; | ||
| 54 | ++ (nullable YBImage *)imageWithData:(NSData *)data scale:(CGFloat)scale decodeDecision:(nullable YBImageDecodeDecision)decodeDecision; | ||
| 55 | +/// <- End | ||
| 56 | + | ||
| 57 | +/** | ||
| 58 | + If the image is created from data or file, then the value indicates the data type. | ||
| 59 | + */ | ||
| 60 | +@property (nonatomic, readonly) YYImageType animatedImageType; | ||
| 61 | + | ||
| 62 | +/** | ||
| 63 | + If the image is created from animated image data (multi-frame GIF/APNG/WebP), | ||
| 64 | + this property stores the original image data. | ||
| 65 | + */ | ||
| 66 | +@property (nullable, nonatomic, readonly) NSData *animatedImageData; | ||
| 67 | + | ||
| 68 | +/** | ||
| 69 | + The total memory usage (in bytes) if all frame images was loaded into memory. | ||
| 70 | + The value is 0 if the image is not created from a multi-frame image data. | ||
| 71 | + */ | ||
| 72 | +@property (nonatomic, readonly) NSUInteger animatedImageMemorySize; | ||
| 73 | + | ||
| 74 | +/** | ||
| 75 | + Preload all frame image to memory. | ||
| 76 | + | ||
| 77 | + @discussion Set this property to `YES` will block the calling thread to decode | ||
| 78 | + all animation frame image to memory, set to `NO` will release the preloaded frames. | ||
| 79 | + If the image is shared by lots of image views (such as emoticon), preload all | ||
| 80 | + frames will reduce the CPU cost. | ||
| 81 | + | ||
| 82 | + See `animatedImageMemorySize` for memory cost. | ||
| 83 | + */ | ||
| 84 | +@property (nonatomic) BOOL preloadAllAnimatedImageFrames; | ||
| 85 | + | ||
| 86 | + | ||
| 87 | +@end | ||
| 88 | + | ||
| 89 | +NS_ASSUME_NONNULL_END |
CNLiveOrganizationValidationModule/Classes/CNLiveOrganizationValidationModule/YBImageBrowser/Image/YBImage.m
0 → 100644
| 1 | +// | ||
| 2 | +// YBImage.m | ||
| 3 | +// YBImageBrowserDemo | ||
| 4 | +// | ||
| 5 | +// Created by 波儿菜 on 2018/8/31. | ||
| 6 | +// Copyright © 2018年 波儿菜. All rights reserved. | ||
| 7 | +// | ||
| 8 | + | ||
| 9 | +#import "YBImage.h" | ||
| 10 | +#import "YBIBImageData.h" | ||
| 11 | +#import "YBIBUtilities.h" | ||
| 12 | + | ||
| 13 | +/** | ||
| 14 | + An array of NSNumber objects, shows the best order for path scale search. | ||
| 15 | + e.g. iPhone3GS:@[@1,@2,@3] iPhone5:@[@2,@3,@1] iPhone6 Plus:@[@3,@2,@1] | ||
| 16 | + */ | ||
| 17 | +static NSArray *_NSBundlePreferredScales() { | ||
| 18 | + static NSArray *scales; | ||
| 19 | + static dispatch_once_t onceToken; | ||
| 20 | + dispatch_once(&onceToken, ^{ | ||
| 21 | + CGFloat screenScale = [UIScreen mainScreen].scale; | ||
| 22 | + if (screenScale <= 1) { | ||
| 23 | + scales = @[@1,@2,@3]; | ||
| 24 | + } else if (screenScale <= 2) { | ||
| 25 | + scales = @[@2,@3,@1]; | ||
| 26 | + } else { | ||
| 27 | + scales = @[@3,@2,@1]; | ||
| 28 | + } | ||
| 29 | + }); | ||
| 30 | + return scales; | ||
| 31 | +} | ||
| 32 | + | ||
| 33 | +/** | ||
| 34 | + Add scale modifier to the file name (without path extension), | ||
| 35 | + From @"name" to @"name@2x". | ||
| 36 | + | ||
| 37 | + e.g. | ||
| 38 | + <table> | ||
| 39 | + <tr><th>Before </th><th>After(scale:2)</th></tr> | ||
| 40 | + <tr><td>"icon" </td><td>"icon@2x" </td></tr> | ||
| 41 | + <tr><td>"icon " </td><td>"icon @2x" </td></tr> | ||
| 42 | + <tr><td>"icon.top" </td><td>"icon.top@2x" </td></tr> | ||
| 43 | + <tr><td>"/p/name" </td><td>"/p/name@2x" </td></tr> | ||
| 44 | + <tr><td>"/path/" </td><td>"/path/" </td></tr> | ||
| 45 | + </table> | ||
| 46 | + | ||
| 47 | + @param scale Resource scale. | ||
| 48 | + @return String by add scale modifier, or just return if it's not end with file name. | ||
| 49 | + */ | ||
| 50 | +static NSString *_NSStringByAppendingNameScale(NSString *string, CGFloat scale) { | ||
| 51 | + if (!string) return nil; | ||
| 52 | + if (fabs(scale - 1) <= __FLT_EPSILON__ || string.length == 0 || [string hasSuffix:@"/"]) return string.copy; | ||
| 53 | + return [string stringByAppendingFormat:@"@%@x", @(scale)]; | ||
| 54 | +} | ||
| 55 | + | ||
| 56 | +/** | ||
| 57 | + Return the path scale. | ||
| 58 | + | ||
| 59 | + e.g. | ||
| 60 | + <table> | ||
| 61 | + <tr><th>Path </th><th>Scale </th></tr> | ||
| 62 | + <tr><td>"icon.png" </td><td>1 </td></tr> | ||
| 63 | + <tr><td>"icon@2x.png" </td><td>2 </td></tr> | ||
| 64 | + <tr><td>"icon@2.5x.png" </td><td>2.5 </td></tr> | ||
| 65 | + <tr><td>"icon@2x" </td><td>1 </td></tr> | ||
| 66 | + <tr><td>"icon@2x..png" </td><td>1 </td></tr> | ||
| 67 | + <tr><td>"icon@2x.png/" </td><td>1 </td></tr> | ||
| 68 | + </table> | ||
| 69 | + */ | ||
| 70 | +static CGFloat _NSStringPathScale(NSString *string) { | ||
| 71 | + if (string.length == 0 || [string hasSuffix:@"/"]) return 1; | ||
| 72 | + NSString *name = string.stringByDeletingPathExtension; | ||
| 73 | + __block CGFloat scale = 1; | ||
| 74 | + | ||
| 75 | + NSRegularExpression *pattern = [NSRegularExpression regularExpressionWithPattern:@"@[0-9]+\\.?[0-9]*x$" options:NSRegularExpressionAnchorsMatchLines error:nil]; | ||
| 76 | + [pattern enumerateMatchesInString:name options:kNilOptions range:NSMakeRange(0, name.length) usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) { | ||
| 77 | + if (result.range.location >= 3) { | ||
| 78 | + scale = [string substringWithRange:NSMakeRange(result.range.location + 1, result.range.length - 2)].doubleValue; | ||
| 79 | + } | ||
| 80 | + }]; | ||
| 81 | + | ||
| 82 | + return scale; | ||
| 83 | +} | ||
| 84 | + | ||
| 85 | + | ||
| 86 | +@implementation YBImage { | ||
| 87 | + YYImageDecoder *_decoder; | ||
| 88 | + NSArray *_preloadedFrames; | ||
| 89 | + dispatch_semaphore_t _preloadedLock; | ||
| 90 | + NSUInteger _bytesPerFrame; | ||
| 91 | +} | ||
| 92 | + | ||
| 93 | ++ (__kindof UIImage *)imageNamed:(NSString *)name { | ||
| 94 | + return [self imageNamed:name decodeDecision:nil]; | ||
| 95 | +} | ||
| 96 | ++ (__kindof UIImage *)imageNamed:(NSString *)name decodeDecision:(nullable YBImageDecodeDecision)decodeDecision { | ||
| 97 | + if (name.length == 0) return nil; | ||
| 98 | + if ([name hasSuffix:@"/"]) return nil; | ||
| 99 | + | ||
| 100 | + NSString *res = name.stringByDeletingPathExtension; | ||
| 101 | + NSString *ext = name.pathExtension; | ||
| 102 | + NSString *path = nil; | ||
| 103 | + CGFloat scale = 1; | ||
| 104 | + | ||
| 105 | + // If no extension, guess by system supported (same as UIImage). | ||
| 106 | + NSArray *exts = ext.length > 0 ? @[ext] : @[@"", @"png", @"jpeg", @"jpg", @"gif", @"webp", @"apng"]; | ||
| 107 | + NSArray *scales = _NSBundlePreferredScales(); | ||
| 108 | + for (int s = 0; s < scales.count; s++) { | ||
| 109 | + scale = ((NSNumber *)scales[s]).floatValue; | ||
| 110 | + NSString *scaledName = _NSStringByAppendingNameScale(res, scale); | ||
| 111 | + for (NSString *e in exts) { | ||
| 112 | + path = [[NSBundle mainBundle] pathForResource:scaledName ofType:e]; | ||
| 113 | + if (path) break; | ||
| 114 | + } | ||
| 115 | + if (path) break; | ||
| 116 | + } | ||
| 117 | + if (path.length == 0) { | ||
| 118 | + // 🙄波儿菜:Assets.xcassets supported. | ||
| 119 | + return [super imageNamed:name]; | ||
| 120 | + } | ||
| 121 | + | ||
| 122 | + NSData *data = [NSData dataWithContentsOfFile:path]; | ||
| 123 | + if (data.length == 0) return nil; | ||
| 124 | + | ||
| 125 | + return [[self alloc] initWithData:data scale:scale decodeDecision:decodeDecision]; | ||
| 126 | +} | ||
| 127 | + | ||
| 128 | ++ (YBImage *)imageWithContentsOfFile:(NSString *)path { | ||
| 129 | + return [self imageWithContentsOfFile:path decodeDecision:nil]; | ||
| 130 | +} | ||
| 131 | ++ (YBImage *)imageWithContentsOfFile:(NSString *)path decodeDecision:(nullable YBImageDecodeDecision)decodeDecision { | ||
| 132 | + return [[self alloc] initWithContentsOfFile:path decodeDecision:decodeDecision]; | ||
| 133 | +} | ||
| 134 | + | ||
| 135 | ++ (YBImage *)imageWithData:(NSData *)data { | ||
| 136 | + return [self imageWithData:data decodeDecision:nil]; | ||
| 137 | +} | ||
| 138 | ++ (YBImage *)imageWithData:(NSData *)data decodeDecision:(nullable YBImageDecodeDecision)decodeDecision { | ||
| 139 | + return [[self alloc] initWithData:data decodeDecision:decodeDecision]; | ||
| 140 | +} | ||
| 141 | + | ||
| 142 | ++ (YBImage *)imageWithData:(NSData *)data scale:(CGFloat)scale { | ||
| 143 | + return [self imageWithData:data scale:scale decodeDecision:nil]; | ||
| 144 | +} | ||
| 145 | ++ (YBImage *)imageWithData:(NSData *)data scale:(CGFloat)scale decodeDecision:(nullable YBImageDecodeDecision)decodeDecision { | ||
| 146 | + return [[self alloc] initWithData:data scale:scale decodeDecision:decodeDecision]; | ||
| 147 | +} | ||
| 148 | + | ||
| 149 | +- (instancetype)initWithContentsOfFile:(NSString *)path { | ||
| 150 | + return [self initWithContentsOfFile:path decodeDecision:nil]; | ||
| 151 | +} | ||
| 152 | +- (instancetype)initWithContentsOfFile:(NSString *)path decodeDecision:(nullable YBImageDecodeDecision)decodeDecision { | ||
| 153 | + NSData *data = [NSData dataWithContentsOfFile:path]; | ||
| 154 | + return [self initWithData:data scale:_NSStringPathScale(path) decodeDecision:decodeDecision]; | ||
| 155 | +} | ||
| 156 | + | ||
| 157 | +- (instancetype)initWithData:(NSData *)data { | ||
| 158 | + return [self initWithData:data decodeDecision:nil]; | ||
| 159 | +} | ||
| 160 | +- (instancetype)initWithData:(NSData *)data decodeDecision:(nullable YBImageDecodeDecision)decodeDecision { | ||
| 161 | + return [self initWithData:data scale:1 decodeDecision:decodeDecision]; | ||
| 162 | +} | ||
| 163 | + | ||
| 164 | +- (instancetype)initWithData:(NSData *)data scale:(CGFloat)scale { | ||
| 165 | + return [self initWithData:data scale:scale decodeDecision:nil]; | ||
| 166 | +} | ||
| 167 | +- (instancetype)initWithData:(NSData *)data scale:(CGFloat)scale decodeDecision:(nullable YBImageDecodeDecision)decodeDecision { | ||
| 168 | + if (data.length == 0) return nil; | ||
| 169 | + if (scale <= 0) scale = [UIScreen mainScreen].scale; | ||
| 170 | + _preloadedLock = dispatch_semaphore_create(1); | ||
| 171 | + @autoreleasepool { | ||
| 172 | + YYImageDecoder *decoder = [YYImageDecoder decoderWithData:data scale:scale]; | ||
| 173 | + | ||
| 174 | + // 🙄波儿菜:Determine whether should to decode. | ||
| 175 | + BOOL decodeForDisplay = YES; | ||
| 176 | + if (decodeDecision) { | ||
| 177 | + decodeForDisplay = decodeDecision(CGSizeMake(decoder.width, decoder.height), decoder.scale ?: 1); | ||
| 178 | + } | ||
| 179 | + | ||
| 180 | + YYImageFrame *frame = [decoder frameAtIndex:0 decodeForDisplay:decodeForDisplay]; | ||
| 181 | + UIImage *image = frame.image; | ||
| 182 | + if (!image) return nil; | ||
| 183 | + self = [self initWithCGImage:image.CGImage scale:decoder.scale orientation:image.imageOrientation]; | ||
| 184 | + if (!self) return nil; | ||
| 185 | + _animatedImageType = decoder.type; | ||
| 186 | + if (decoder.frameCount > 1) { | ||
| 187 | + _decoder = decoder; | ||
| 188 | + _bytesPerFrame = CGImageGetBytesPerRow(image.CGImage) * CGImageGetHeight(image.CGImage); | ||
| 189 | + _animatedImageMemorySize = _bytesPerFrame * decoder.frameCount; | ||
| 190 | + } | ||
| 191 | + self.isDecodedForDisplay = YES; | ||
| 192 | + } | ||
| 193 | + return self; | ||
| 194 | +} | ||
| 195 | + | ||
| 196 | +- (NSData *)animatedImageData { | ||
| 197 | + return _decoder.data; | ||
| 198 | +} | ||
| 199 | + | ||
| 200 | +- (void)setPreloadAllAnimatedImageFrames:(BOOL)preloadAllAnimatedImageFrames { | ||
| 201 | + if (_preloadAllAnimatedImageFrames != preloadAllAnimatedImageFrames) { | ||
| 202 | + if (preloadAllAnimatedImageFrames && _decoder.frameCount > 0) { | ||
| 203 | + NSMutableArray *frames = [NSMutableArray new]; | ||
| 204 | + for (NSUInteger i = 0, max = _decoder.frameCount; i < max; i++) { | ||
| 205 | + UIImage *img = [self animatedImageFrameAtIndex:i]; | ||
| 206 | + if (img) { | ||
| 207 | + [frames addObject:img]; | ||
| 208 | + } else { | ||
| 209 | + [frames addObject:[NSNull null]]; | ||
| 210 | + } | ||
| 211 | + } | ||
| 212 | + dispatch_semaphore_wait(_preloadedLock, DISPATCH_TIME_FOREVER); | ||
| 213 | + _preloadedFrames = frames; | ||
| 214 | + dispatch_semaphore_signal(_preloadedLock); | ||
| 215 | + } else { | ||
| 216 | + dispatch_semaphore_wait(_preloadedLock, DISPATCH_TIME_FOREVER); | ||
| 217 | + _preloadedFrames = nil; | ||
| 218 | + dispatch_semaphore_signal(_preloadedLock); | ||
| 219 | + } | ||
| 220 | + } | ||
| 221 | +} | ||
| 222 | + | ||
| 223 | +#pragma mark - protocol NSCoding | ||
| 224 | + | ||
| 225 | +- (instancetype)initWithCoder:(NSCoder *)aDecoder { | ||
| 226 | + NSNumber *scale = [aDecoder decodeObjectForKey:@"YYImageScale"]; | ||
| 227 | + NSData *data = [aDecoder decodeObjectForKey:@"YYImageData"]; | ||
| 228 | + if (data.length) { | ||
| 229 | + self = [self initWithData:data scale:scale.doubleValue]; | ||
| 230 | + } else { | ||
| 231 | + self = [super initWithCoder:aDecoder]; | ||
| 232 | + } | ||
| 233 | + return self; | ||
| 234 | +} | ||
| 235 | + | ||
| 236 | +- (void)encodeWithCoder:(NSCoder *)aCoder { | ||
| 237 | + if (_decoder.data.length) { | ||
| 238 | + [aCoder encodeObject:@(self.scale) forKey:@"YYImageScale"]; | ||
| 239 | + [aCoder encodeObject:_decoder.data forKey:@"YYImageData"]; | ||
| 240 | + } else { | ||
| 241 | + [super encodeWithCoder:aCoder]; // Apple use UIImagePNGRepresentation() to encode UIImage. | ||
| 242 | + } | ||
| 243 | +} | ||
| 244 | + | ||
| 245 | +#pragma mark - protocol YYAnimatedImage | ||
| 246 | + | ||
| 247 | +- (NSUInteger)animatedImageFrameCount { | ||
| 248 | + return _decoder.frameCount; | ||
| 249 | +} | ||
| 250 | + | ||
| 251 | +- (NSUInteger)animatedImageLoopCount { | ||
| 252 | + return _decoder.loopCount; | ||
| 253 | +} | ||
| 254 | + | ||
| 255 | +- (NSUInteger)animatedImageBytesPerFrame { | ||
| 256 | + return _bytesPerFrame; | ||
| 257 | +} | ||
| 258 | + | ||
| 259 | +- (UIImage *)animatedImageFrameAtIndex:(NSUInteger)index { | ||
| 260 | + if (index >= _decoder.frameCount) return nil; | ||
| 261 | + dispatch_semaphore_wait(_preloadedLock, DISPATCH_TIME_FOREVER); | ||
| 262 | + UIImage *image = _preloadedFrames[index]; | ||
| 263 | + dispatch_semaphore_signal(_preloadedLock); | ||
| 264 | + if (image) return image == (id)[NSNull null] ? nil : image; | ||
| 265 | + return [_decoder frameAtIndex:index decodeForDisplay:YES].image; | ||
| 266 | +} | ||
| 267 | + | ||
| 268 | +- (NSTimeInterval)animatedImageDurationAtIndex:(NSUInteger)index { | ||
| 269 | + NSTimeInterval duration = [_decoder frameDurationAtIndex:index]; | ||
| 270 | + | ||
| 271 | + /* | ||
| 272 | + http://opensource.apple.com/source/WebCore/WebCore-7600.1.25/platform/graphics/cg/ImageSourceCG.cpp | ||
| 273 | + Many annoying ads specify a 0 duration to make an image flash as quickly as | ||
| 274 | + possible. We follow Safari and Firefox's behavior and use a duration of 100 ms | ||
| 275 | + for any frames that specify a duration of <= 10 ms. | ||
| 276 | + See <rdar://problem/7689300> and <http://webkit.org/b/36082> for more information. | ||
| 277 | + | ||
| 278 | + See also: http://nullsleep.tumblr.com/post/16524517190/animated-gif-minimum-frame-delay-browser. | ||
| 279 | + */ | ||
| 280 | + if (duration < 0.011f) return 0.100f; | ||
| 281 | + return duration; | ||
| 282 | +} | ||
| 283 | + | ||
| 284 | +@end |
CNLiveOrganizationValidationModule/Classes/CNLiveOrganizationValidationModule/YBImageBrowser/Protocol/YBIBCellProtocol.h
0 → 100644
| 1 | +// | ||
| 2 | +// YBIBCellProtocol.h | ||
| 3 | +// YBImageBrowserDemo | ||
| 4 | +// | ||
| 5 | +// Created by 波儿菜 on 2019/6/5. | ||
| 6 | +// Copyright © 2019 波儿菜. All rights reserved. | ||
| 7 | +// | ||
| 8 | + | ||
| 9 | +#import "YBIBGetBaseInfoProtocol.h" | ||
| 10 | +#import "YBIBOrientationReceiveProtocol.h" | ||
| 11 | +#import "YBIBOperateBrowserProtocol.h" | ||
| 12 | + | ||
| 13 | +@protocol YBIBDataProtocol; | ||
| 14 | + | ||
| 15 | +NS_ASSUME_NONNULL_BEGIN | ||
| 16 | + | ||
| 17 | +@protocol YBIBCellProtocol <YBIBGetBaseInfoProtocol, YBIBOperateBrowserProtocol, YBIBOrientationReceiveProtocol> | ||
| 18 | + | ||
| 19 | +@required | ||
| 20 | + | ||
| 21 | +/// Cell 对应的 Data | ||
| 22 | +@property (nonatomic, strong) id<YBIBDataProtocol> yb_cellData; | ||
| 23 | + | ||
| 24 | +@optional | ||
| 25 | + | ||
| 26 | +/** | ||
| 27 | + 获取前景视图,出入场时需要用这个返回值做动效 | ||
| 28 | + | ||
| 29 | + @return 前景视图 | ||
| 30 | + */ | ||
| 31 | +- (__kindof UIView *)yb_foregroundView; | ||
| 32 | + | ||
| 33 | +/** | ||
| 34 | + 页码变化了 | ||
| 35 | + */ | ||
| 36 | +- (void)yb_pageChanged; | ||
| 37 | + | ||
| 38 | +/// 当前 Cell 的页码 | ||
| 39 | +@property (nonatomic, copy) NSInteger(^yb_selfPage)(void); | ||
| 40 | + | ||
| 41 | +@end | ||
| 42 | + | ||
| 43 | +NS_ASSUME_NONNULL_END |
CNLiveOrganizationValidationModule/Classes/CNLiveOrganizationValidationModule/YBImageBrowser/Protocol/YBIBDataProtocol.h
0 → 100644
| 1 | +// | ||
| 2 | +// YBIBDataProtocol.h | ||
| 3 | +// YBImageBrowserDemo | ||
| 4 | +// | ||
| 5 | +// Created by 波儿菜 on 2019/6/5. | ||
| 6 | +// Copyright © 2019 波儿菜. All rights reserved. | ||
| 7 | +// | ||
| 8 | + | ||
| 9 | +#import "YBIBGetBaseInfoProtocol.h" | ||
| 10 | + | ||
| 11 | +NS_ASSUME_NONNULL_BEGIN | ||
| 12 | + | ||
| 13 | +@protocol YBIBDataProtocol <YBIBGetBaseInfoProtocol> | ||
| 14 | + | ||
| 15 | +@required | ||
| 16 | + | ||
| 17 | +/** | ||
| 18 | + 当前 Data 对应 Cell 的类类型 | ||
| 19 | + | ||
| 20 | + @return Class 类型 | ||
| 21 | + */ | ||
| 22 | +- (Class)yb_classOfCell; | ||
| 23 | + | ||
| 24 | +@optional | ||
| 25 | + | ||
| 26 | +/** | ||
| 27 | + 获取投影视图,当前数据模型对应外界业务的 UIView (通常为 UIImageView),做转场动效用 | ||
| 28 | + | ||
| 29 | + 这个方法会在做出入场动效时调用,若未实现时将无法进行平滑的入场 | ||
| 30 | + | ||
| 31 | + @return 投影视图 | ||
| 32 | + */ | ||
| 33 | +- (__kindof UIView *)yb_projectiveView; | ||
| 34 | + | ||
| 35 | +/** | ||
| 36 | + 通过一系列数据,计算并返回图片视图在容器中的 frame | ||
| 37 | + | ||
| 38 | + 这个方法会在做入场动效时调用,若未实现时将无法进行平滑的入场 | ||
| 39 | + | ||
| 40 | + @param containerSize 容器大小 | ||
| 41 | + @param imageSize 图片大小 (逻辑像素) | ||
| 42 | + @param orientation 图片浏览器的方向 | ||
| 43 | + @return 计算好的 frame | ||
| 44 | + */ | ||
| 45 | +- (CGRect)yb_imageViewFrameWithContainerSize:(CGSize)containerSize imageSize:(CGSize)imageSize orientation:(UIDeviceOrientation)orientation; | ||
| 46 | + | ||
| 47 | +/** | ||
| 48 | + 预加载数据,有效的预加载能提高性能,请注意管理内存 | ||
| 49 | + */ | ||
| 50 | +- (void)yb_preload; | ||
| 51 | + | ||
| 52 | +/** | ||
| 53 | + 保存到相册 | ||
| 54 | + */ | ||
| 55 | +- (void)yb_saveToPhotoAlbum; | ||
| 56 | + | ||
| 57 | +/** | ||
| 58 | + 是否允许保存到相册 | ||
| 59 | + */ | ||
| 60 | +- (BOOL)yb_allowSaveToPhotoAlbum; | ||
| 61 | + | ||
| 62 | +@end | ||
| 63 | + | ||
| 64 | +NS_ASSUME_NONNULL_END |
CNLiveOrganizationValidationModule/Classes/CNLiveOrganizationValidationModule/YBImageBrowser/Protocol/YBIBGetBaseInfoProtocol.h
0 → 100644
| 1 | +// | ||
| 2 | +// YBIBGetBaseInfoProtocol.h | ||
| 3 | +// YBImageBrowserDemo | ||
| 4 | +// | ||
| 5 | +// Created by 波儿菜 on 2019/6/23. | ||
| 6 | +// Copyright © 2019 波儿菜. All rights reserved. | ||
| 7 | +// | ||
| 8 | + | ||
| 9 | +#import <UIKit/UIKit.h> | ||
| 10 | +#import "YBIBAuxiliaryViewHandler.h" | ||
| 11 | +#import "YBIBWebImageMediator.h" | ||
| 12 | + | ||
| 13 | +NS_ASSUME_NONNULL_BEGIN | ||
| 14 | + | ||
| 15 | +@protocol YBIBGetBaseInfoProtocol <NSObject> | ||
| 16 | + | ||
| 17 | +@optional | ||
| 18 | + | ||
| 19 | +/// 当前的方向 | ||
| 20 | +@property (nonatomic, copy) UIDeviceOrientation(^yb_currentOrientation)(void); | ||
| 21 | + | ||
| 22 | +/// 根据方向获取容器大小 | ||
| 23 | +@property (nonatomic, copy) CGSize(^yb_containerSize)(UIDeviceOrientation orientation); | ||
| 24 | + | ||
| 25 | +/// 容器视图 (可在上面添加子视图) | ||
| 26 | +@property (nonatomic, weak) UIView *yb_containerView; | ||
| 27 | + | ||
| 28 | +/// 辅助视图处理器 | ||
| 29 | +@property (nonatomic, copy) id<YBIBAuxiliaryViewHandler>(^yb_auxiliaryViewHandler)(void); | ||
| 30 | + | ||
| 31 | +/// 图片下载缓存相关中介者 | ||
| 32 | +@property (nonatomic, copy) id<YBIBWebImageMediator>(^yb_webImageMediator)(void); | ||
| 33 | + | ||
| 34 | +/// 当前页码 | ||
| 35 | +@property (nonatomic, copy) NSInteger(^yb_currentPage)(void); | ||
| 36 | + | ||
| 37 | +/// 总页码数量 | ||
| 38 | +@property (nonatomic, copy) NSInteger(^yb_totalPage)(void); | ||
| 39 | + | ||
| 40 | +/// 判断当前展示的 cell 是否恰好在屏幕中间 | ||
| 41 | +@property (nonatomic, copy) BOOL(^yb_cellIsInCenter)(void); | ||
| 42 | + | ||
| 43 | +/// 是否正在转场 | ||
| 44 | +@property (nonatomic, copy) BOOL(^yb_isTransitioning)(void); | ||
| 45 | +/// 是否正在展示过程转场 | ||
| 46 | +@property (nonatomic, copy) BOOL(^yb_isShowTransitioning)(void); | ||
| 47 | +/// 是否正在隐藏过程转场 | ||
| 48 | +@property (nonatomic, copy) BOOL(^yb_isHideTransitioning)(void); | ||
| 49 | + | ||
| 50 | +/// 是否正在旋转 | ||
| 51 | +@property (nonatomic, copy) BOOL(^yb_isRotating)(void); | ||
| 52 | + | ||
| 53 | +/// 背景视图 (也就是 YBImageBrower 对象,不可在上面添加子视图。作用:一是可以更改透明度和颜色,入场和出场动效有用;二是可以用来比较内存指针,在做不同实例差异化功能时可能有用,虽然不提倡这么做) | ||
| 54 | +@property (nonatomic, weak) __kindof UIView *yb_backView; | ||
| 55 | + | ||
| 56 | +/// 集合视图 | ||
| 57 | +@property (nonatomic, copy) __kindof UICollectionView *(^yb_collectionView)(void); | ||
| 58 | + | ||
| 59 | +@end | ||
| 60 | + | ||
| 61 | +NS_ASSUME_NONNULL_END |
CNLiveOrganizationValidationModule/Classes/CNLiveOrganizationValidationModule/YBImageBrowser/Protocol/YBIBOperateBrowserProtocol.h
0 → 100644
| 1 | +// | ||
| 2 | +// YBIBOperateBrowserProtocol.h | ||
| 3 | +// YBImageBrowserDemo | ||
| 4 | +// | ||
| 5 | +// Created by 波儿菜 on 2019/7/8. | ||
| 6 | +// Copyright © 2019 杨波. All rights reserved. | ||
| 7 | +// | ||
| 8 | + | ||
| 9 | +#import <Foundation/Foundation.h> | ||
| 10 | + | ||
| 11 | +NS_ASSUME_NONNULL_BEGIN | ||
| 12 | + | ||
| 13 | +@protocol YBIBOperateBrowserProtocol <NSObject> | ||
| 14 | + | ||
| 15 | +@optional | ||
| 16 | + | ||
| 17 | +/// 隐藏图片浏览器 | ||
| 18 | +@property (nonatomic, copy) void(^yb_hideBrowser)(void); | ||
| 19 | + | ||
| 20 | +/// 是否隐藏状态栏 | ||
| 21 | +@property (nonatomic, copy) void(^yb_hideStatusBar)(BOOL); | ||
| 22 | + | ||
| 23 | +/// 是否隐藏工具视图 | ||
| 24 | +@property (nonatomic, copy) void(^yb_hideToolViews)(BOOL); | ||
| 25 | + | ||
| 26 | +@end | ||
| 27 | + | ||
| 28 | +NS_ASSUME_NONNULL_END |
CNLiveOrganizationValidationModule/Classes/CNLiveOrganizationValidationModule/YBImageBrowser/Protocol/YBIBOrientationReceiveProtocol.h
0 → 100644
| 1 | +// | ||
| 2 | +// YBIBOrientationReceiveProtocol.h | ||
| 3 | +// YBImageBrowserDemo | ||
| 4 | +// | ||
| 5 | +// Created by 波儿菜 on 2019/7/8. | ||
| 6 | +// Copyright © 2019 杨波. All rights reserved. | ||
| 7 | +// | ||
| 8 | + | ||
| 9 | +#import <UIKit/UIKit.h> | ||
| 10 | + | ||
| 11 | +NS_ASSUME_NONNULL_BEGIN | ||
| 12 | + | ||
| 13 | +@protocol YBIBOrientationReceiveProtocol <NSObject> | ||
| 14 | + | ||
| 15 | +@optional | ||
| 16 | + | ||
| 17 | +/** | ||
| 18 | + 图片浏览器的方向将要变化 | ||
| 19 | + | ||
| 20 | + @param orientation 期望的方向 | ||
| 21 | + */ | ||
| 22 | +- (void)yb_orientationWillChangeWithExpectOrientation:(UIDeviceOrientation)orientation; | ||
| 23 | + | ||
| 24 | +/** | ||
| 25 | + 图片浏览器的方向变化动效调用,实现的变化会自动转换为动画 | ||
| 26 | + | ||
| 27 | + @param orientation 期望的方向 | ||
| 28 | + */ | ||
| 29 | +- (void)yb_orientationChangeAnimationWithExpectOrientation:(UIDeviceOrientation)orientation; | ||
| 30 | + | ||
| 31 | +/** | ||
| 32 | + 图片浏览器的方向已经变化 | ||
| 33 | + | ||
| 34 | + @param orientation 当前的方向 | ||
| 35 | + */ | ||
| 36 | +- (void)yb_orientationDidChangedWithOrientation:(UIDeviceOrientation)orientation; | ||
| 37 | + | ||
| 38 | +@end | ||
| 39 | + | ||
| 40 | +NS_ASSUME_NONNULL_END |
CNLiveOrganizationValidationModule/Classes/CNLiveOrganizationValidationModule/YBImageBrowser/Protocol/YBImageBrowserDataSource.h
0 → 100644
| 1 | +// | ||
| 2 | +// YBImageBrowserDataSource.h | ||
| 3 | +// YBImageBrowserDemo | ||
| 4 | +// | ||
| 5 | +// Created by 波儿菜 on 2018/8/25. | ||
| 6 | +// Copyright © 2018年 波儿菜. All rights reserved. | ||
| 7 | +// | ||
| 8 | + | ||
| 9 | +#import "YBIBDataProtocol.h" | ||
| 10 | + | ||
| 11 | +NS_ASSUME_NONNULL_BEGIN | ||
| 12 | + | ||
| 13 | +@class YBImageBrowser; | ||
| 14 | + | ||
| 15 | +@protocol YBImageBrowserDataSource <NSObject> | ||
| 16 | + | ||
| 17 | +@required | ||
| 18 | + | ||
| 19 | +/** | ||
| 20 | + 返回数据源数量 | ||
| 21 | + | ||
| 22 | + @param imageBrowser 图片浏览器 | ||
| 23 | + @return 数量 | ||
| 24 | + */ | ||
| 25 | +- (NSInteger)yb_numberOfCellsInImageBrowser:(YBImageBrowser *)imageBrowser; | ||
| 26 | + | ||
| 27 | +/** | ||
| 28 | + 返回当前下标对应的数据 | ||
| 29 | + | ||
| 30 | + @param imageBrowser 图片浏览器 | ||
| 31 | + @param index 当前下标 | ||
| 32 | + @return 数据 | ||
| 33 | + */ | ||
| 34 | +- (id<YBIBDataProtocol>)yb_imageBrowser:(YBImageBrowser *)imageBrowser dataForCellAtIndex:(NSInteger)index; | ||
| 35 | + | ||
| 36 | +@end | ||
| 37 | + | ||
| 38 | +NS_ASSUME_NONNULL_END |
CNLiveOrganizationValidationModule/Classes/CNLiveOrganizationValidationModule/YBImageBrowser/Protocol/YBImageBrowserDelegate.h
0 → 100644
| 1 | +// | ||
| 2 | +// YBImageBrowserDelegate.h | ||
| 3 | +// YBImageBrowserDemo | ||
| 4 | +// | ||
| 5 | +// Created by 波儿菜 on 2019/6/9. | ||
| 6 | +// Copyright © 2019 波儿菜. All rights reserved. | ||
| 7 | +// | ||
| 8 | + | ||
| 9 | +#import "YBIBDataProtocol.h" | ||
| 10 | + | ||
| 11 | +NS_ASSUME_NONNULL_BEGIN | ||
| 12 | + | ||
| 13 | +@class YBImageBrowser; | ||
| 14 | + | ||
| 15 | +@protocol YBImageBrowserDelegate <NSObject> | ||
| 16 | + | ||
| 17 | +@optional | ||
| 18 | + | ||
| 19 | +/** | ||
| 20 | + 页码变化 | ||
| 21 | + | ||
| 22 | + @param imageBrowser 图片浏览器 | ||
| 23 | + @param page 当前页码 | ||
| 24 | + @param data 数据 | ||
| 25 | + */ | ||
| 26 | +- (void)yb_imageBrowser:(YBImageBrowser *)imageBrowser pageChanged:(NSInteger)page data:(id<YBIBDataProtocol>)data; | ||
| 27 | + | ||
| 28 | +/** | ||
| 29 | + 响应长按手势(若实现该方法将阻止其它地方捕获到长按事件) | ||
| 30 | + | ||
| 31 | + @param imageBrowser 图片浏览器 | ||
| 32 | + @param data 数据 | ||
| 33 | + */ | ||
| 34 | +- (void)yb_imageBrowser:(YBImageBrowser *)imageBrowser respondsToLongPressWithData:(id<YBIBDataProtocol>)data; | ||
| 35 | + | ||
| 36 | +/** | ||
| 37 | + 开始转场 | ||
| 38 | + | ||
| 39 | + @param imageBrowser 图片浏览器 | ||
| 40 | + @param isShow YES 表示入场,NO 表示出场 | ||
| 41 | + */ | ||
| 42 | +- (void)yb_imageBrowser:(YBImageBrowser *)imageBrowser beginTransitioningWithIsShow:(BOOL)isShow; | ||
| 43 | + | ||
| 44 | +/** | ||
| 45 | + 结束转场 | ||
| 46 | + | ||
| 47 | + @param imageBrowser 图片浏览器 | ||
| 48 | + @param isShow YES 表示入场,NO 表示出场 | ||
| 49 | + */ | ||
| 50 | +- (void)yb_imageBrowser:(YBImageBrowser *)imageBrowser endTransitioningWithIsShow:(BOOL)isShow; | ||
| 51 | + | ||
| 52 | +@end | ||
| 53 | + | ||
| 54 | +NS_ASSUME_NONNULL_END |
CNLiveOrganizationValidationModule/Classes/CNLiveOrganizationValidationModule/YBImageBrowser/ToolView/YBIBSheetView.h
0 → 100644
| 1 | +// | ||
| 2 | +// YBIBSheetView.h | ||
| 3 | +// YBImageBrowserDemo | ||
| 4 | +// | ||
| 5 | +// Created by 波儿菜 on 2019/7/6. | ||
| 6 | +// Copyright © 2019 杨波. All rights reserved. | ||
| 7 | +// | ||
| 8 | + | ||
| 9 | +#import <UIKit/UIKit.h> | ||
| 10 | + | ||
| 11 | +NS_ASSUME_NONNULL_BEGIN | ||
| 12 | + | ||
| 13 | +@protocol YBIBDataProtocol; | ||
| 14 | + | ||
| 15 | +typedef void(^YBIBSheetActionBlock)(id<YBIBDataProtocol> data); | ||
| 16 | + | ||
| 17 | +@interface YBIBSheetAction : NSObject | ||
| 18 | + | ||
| 19 | +/// 显示的名字 | ||
| 20 | +@property (nonatomic, copy) NSString *name; | ||
| 21 | + | ||
| 22 | +/// 点击回调闭包 | ||
| 23 | +@property (nonatomic, copy, nullable) YBIBSheetActionBlock action; | ||
| 24 | + | ||
| 25 | ++ (instancetype)actionWithName:(NSString *)name action:(_Nullable YBIBSheetActionBlock)action; | ||
| 26 | + | ||
| 27 | +@end | ||
| 28 | + | ||
| 29 | + | ||
| 30 | +@interface YBIBSheetView : UIView | ||
| 31 | + | ||
| 32 | +/// 数据源 (可自定义添加) | ||
| 33 | +@property (nonatomic, strong) NSMutableArray<YBIBSheetAction *> *actions; | ||
| 34 | + | ||
| 35 | +/// 列表 Cell 的高度 | ||
| 36 | +@property (nonatomic, assign) CGFloat cellHeight; | ||
| 37 | + | ||
| 38 | +/// 列表最大高度与容器高度的比例 | ||
| 39 | +@property (nonatomic, assign) CGFloat maxHeightScale; | ||
| 40 | + | ||
| 41 | +/// 取消的文本 | ||
| 42 | +@property (nonatomic, copy) NSString *cancelText; | ||
| 43 | + | ||
| 44 | +/// 显示动画持续时间 | ||
| 45 | +@property (nonatomic, assign) NSTimeInterval showDuration; | ||
| 46 | + | ||
| 47 | +/// 隐藏动画持续时间 | ||
| 48 | +@property (nonatomic, assign) NSTimeInterval hideDuration; | ||
| 49 | + | ||
| 50 | +/// 背景透明度 | ||
| 51 | +@property (nonatomic, assign) CGFloat backAlpha; | ||
| 52 | + | ||
| 53 | +/** | ||
| 54 | + 展示 | ||
| 55 | + | ||
| 56 | + @param view 指定父视图 | ||
| 57 | + @param orientation 当前方向 | ||
| 58 | + */ | ||
| 59 | +- (void)showToView:(UIView *)view orientation:(UIDeviceOrientation)orientation; | ||
| 60 | + | ||
| 61 | +/** | ||
| 62 | + 隐藏 | ||
| 63 | + | ||
| 64 | + @param animation 是否带动画 | ||
| 65 | + */ | ||
| 66 | +- (void)hideWithAnimation:(BOOL)animation; | ||
| 67 | + | ||
| 68 | +/// 获取当前数据的闭包 | ||
| 69 | +@property (nonatomic, copy) id<YBIBDataProtocol>(^currentdata)(void); | ||
| 70 | + | ||
| 71 | +@end | ||
| 72 | + | ||
| 73 | +NS_ASSUME_NONNULL_END |
CNLiveOrganizationValidationModule/Classes/CNLiveOrganizationValidationModule/YBImageBrowser/ToolView/YBIBSheetView.m
0 → 100644
| 1 | +// | ||
| 2 | +// YBIBSheetView.m | ||
| 3 | +// YBImageBrowserDemo | ||
| 4 | +// | ||
| 5 | +// Created by 波儿菜 on 2019/7/6. | ||
| 6 | +// Copyright © 2019 杨波. All rights reserved. | ||
| 7 | +// | ||
| 8 | + | ||
| 9 | +#import "YBIBSheetView.h" | ||
| 10 | +#import "YBIBUtilities.h" | ||
| 11 | +#import "YBIBCopywriter.h" | ||
| 12 | + | ||
| 13 | + | ||
| 14 | +@implementation YBIBSheetAction | ||
| 15 | ++ (instancetype)actionWithName:(NSString *)name action:(YBIBSheetActionBlock)action { | ||
| 16 | + YBIBSheetAction *sheetAction = [YBIBSheetAction new]; | ||
| 17 | + sheetAction.name = name; | ||
| 18 | + sheetAction.action = action; | ||
| 19 | + return sheetAction; | ||
| 20 | +} | ||
| 21 | +@end | ||
| 22 | + | ||
| 23 | + | ||
| 24 | +@interface YBIBSheetCell : UITableViewCell | ||
| 25 | +@property (nonatomic, strong) UILabel *titleLabel; | ||
| 26 | +@property (nonatomic, strong) CALayer *line; | ||
| 27 | +@end | ||
| 28 | +@implementation YBIBSheetCell | ||
| 29 | +- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier { | ||
| 30 | + if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) { | ||
| 31 | + self.selectionStyle = UITableViewCellSelectionStyleNone; | ||
| 32 | + _titleLabel = [UILabel new]; | ||
| 33 | + _titleLabel.textColor = UIColor.darkTextColor; | ||
| 34 | + _titleLabel.font = [UIFont fontWithName:@"Avenir-Medium" size:16]; | ||
| 35 | + _titleLabel.textAlignment = NSTextAlignmentCenter; | ||
| 36 | + _line = [CALayer new]; | ||
| 37 | + _line.backgroundColor = UIColor.groupTableViewBackgroundColor.CGColor; | ||
| 38 | + [self.contentView addSubview:_titleLabel]; | ||
| 39 | + [self.contentView.layer addSublayer:_line]; | ||
| 40 | + } | ||
| 41 | + return self; | ||
| 42 | +} | ||
| 43 | +- (void)layoutSubviews { | ||
| 44 | + [super layoutSubviews]; | ||
| 45 | + CGFloat width = self.contentView.bounds.size.width, height = self.contentView.bounds.size.height; | ||
| 46 | + CGFloat lineHeight = 0.5; | ||
| 47 | + _line.frame = CGRectMake(0, height - lineHeight, width, lineHeight); | ||
| 48 | + CGFloat offset = 15; | ||
| 49 | + _titleLabel.frame = CGRectMake(offset, 0, width - offset * 2, height); | ||
| 50 | +} | ||
| 51 | +@end | ||
| 52 | + | ||
| 53 | + | ||
| 54 | +static CGFloat kOffsetSpace = 5; | ||
| 55 | + | ||
| 56 | +@interface YBIBSheetView () <UITableViewDelegate, UITableViewDataSource> | ||
| 57 | +@property (nonatomic, strong) UITableView *tableView; | ||
| 58 | +@end | ||
| 59 | + | ||
| 60 | +@implementation YBIBSheetView { | ||
| 61 | + CGRect _tableShowFrame; | ||
| 62 | + CGRect _tableHideFrame; | ||
| 63 | +} | ||
| 64 | + | ||
| 65 | +#pragma mark - life cycle | ||
| 66 | + | ||
| 67 | +- (instancetype)initWithFrame:(CGRect)frame { | ||
| 68 | + self = [super initWithFrame:frame]; | ||
| 69 | + if (self) { | ||
| 70 | + _cancelText = [YBIBCopywriter sharedCopywriter].cancel; | ||
| 71 | + _maxHeightScale = 0.7; | ||
| 72 | + _showDuration = 0.2; | ||
| 73 | + _hideDuration = 0.1; | ||
| 74 | + _cellHeight = 50; | ||
| 75 | + _backAlpha = 0.3; | ||
| 76 | + _actions = [NSMutableArray array]; | ||
| 77 | + | ||
| 78 | + [self addSubview:self.tableView]; | ||
| 79 | + } | ||
| 80 | + return self; | ||
| 81 | +} | ||
| 82 | + | ||
| 83 | +#pragma mark - public | ||
| 84 | + | ||
| 85 | +- (void)showToView:(UIView *)view orientation:(UIDeviceOrientation)orientation { | ||
| 86 | + if (self.actions.count == 0) return; | ||
| 87 | + | ||
| 88 | + [view addSubview:self]; | ||
| 89 | + self.frame = view.bounds; | ||
| 90 | + | ||
| 91 | + UIEdgeInsets padding = YBIBPaddingByBrowserOrientation(orientation); | ||
| 92 | + | ||
| 93 | + CGFloat footerHeight = padding.bottom; | ||
| 94 | + CGFloat tableHeight = self.cellHeight * (self.actions.count + 1) + kOffsetSpace + footerHeight; | ||
| 95 | + | ||
| 96 | + _tableShowFrame = self.frame; | ||
| 97 | + _tableShowFrame.size.height = MIN(self.maxHeightScale * self.bounds.size.height, tableHeight); | ||
| 98 | + _tableShowFrame.origin.y = self.bounds.size.height - _tableShowFrame.size.height; | ||
| 99 | + | ||
| 100 | + _tableHideFrame = _tableShowFrame; | ||
| 101 | + _tableHideFrame.origin.y = self.bounds.size.height; | ||
| 102 | + | ||
| 103 | + self.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0]; | ||
| 104 | + self.tableView.frame = _tableHideFrame; | ||
| 105 | + self.tableView.tableFooterView.bounds = CGRectMake(0, 0, self.tableView.frame.size.width, footerHeight); | ||
| 106 | + [UIView animateWithDuration:self.showDuration animations:^{ | ||
| 107 | + self.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:self->_backAlpha]; | ||
| 108 | + self.tableView.frame = self->_tableShowFrame; | ||
| 109 | + }]; | ||
| 110 | +} | ||
| 111 | + | ||
| 112 | +- (void)hideWithAnimation:(BOOL)animation { | ||
| 113 | + if (!self.superview) return; | ||
| 114 | + | ||
| 115 | + void(^animationsBlock)(void) = ^{ | ||
| 116 | + self.tableView.frame = self->_tableHideFrame; | ||
| 117 | + self.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0]; | ||
| 118 | + }; | ||
| 119 | + void(^completionBlock)(BOOL n) = ^(BOOL n){ | ||
| 120 | + [self removeFromSuperview]; | ||
| 121 | + }; | ||
| 122 | + if (animation) { | ||
| 123 | + [UIView animateWithDuration:self.hideDuration animations:animationsBlock completion:completionBlock]; | ||
| 124 | + } else { | ||
| 125 | + animationsBlock(); | ||
| 126 | + completionBlock(NO); | ||
| 127 | + } | ||
| 128 | +} | ||
| 129 | + | ||
| 130 | +#pragma mark - touch | ||
| 131 | + | ||
| 132 | +- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { | ||
| 133 | + CGPoint point = [touches.anyObject locationInView:self]; | ||
| 134 | + if (!CGRectContainsPoint(self.tableView.frame, point)) { | ||
| 135 | + [self hideWithAnimation:YES]; | ||
| 136 | + } | ||
| 137 | +} | ||
| 138 | + | ||
| 139 | +#pragma mark - <UITableViewDataSource> | ||
| 140 | + | ||
| 141 | +- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { | ||
| 142 | + return 2; | ||
| 143 | +} | ||
| 144 | + | ||
| 145 | +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { | ||
| 146 | + return section == 0 ? self.actions.count : 1; | ||
| 147 | +} | ||
| 148 | + | ||
| 149 | +- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { | ||
| 150 | + return self.cellHeight; | ||
| 151 | +} | ||
| 152 | + | ||
| 153 | +- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section { | ||
| 154 | + return section == 0 ? CGFLOAT_MIN : kOffsetSpace; | ||
| 155 | +} | ||
| 156 | + | ||
| 157 | +- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section { | ||
| 158 | + return CGFLOAT_MIN; | ||
| 159 | +} | ||
| 160 | + | ||
| 161 | +- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { | ||
| 162 | + YBIBSheetCell *cell = [tableView dequeueReusableCellWithIdentifier:NSStringFromClass(YBIBSheetCell.self)]; | ||
| 163 | + if (indexPath.section == 0) { | ||
| 164 | + cell.line.hidden = NO; | ||
| 165 | + YBIBSheetAction *action = self.actions[indexPath.row]; | ||
| 166 | + cell.titleLabel.text = action.name; | ||
| 167 | + } else { | ||
| 168 | + cell.line.hidden = YES; | ||
| 169 | + cell.titleLabel.text = self.cancelText; | ||
| 170 | + } | ||
| 171 | + return cell; | ||
| 172 | +} | ||
| 173 | + | ||
| 174 | +#pragma mark - <UITableViewDelegate> | ||
| 175 | + | ||
| 176 | +- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { | ||
| 177 | + if (indexPath.section == 0) { | ||
| 178 | + YBIBSheetAction *action = self.actions[indexPath.row]; | ||
| 179 | + if (action.action) action.action(self.currentdata()); | ||
| 180 | + } else { | ||
| 181 | + [self hideWithAnimation:YES]; | ||
| 182 | + } | ||
| 183 | +} | ||
| 184 | + | ||
| 185 | +#pragma mark - getters | ||
| 186 | + | ||
| 187 | +- (UITableView *)tableView { | ||
| 188 | + if (!_tableView) { | ||
| 189 | + _tableView = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStyleGrouped]; | ||
| 190 | + _tableView.delegate = self; | ||
| 191 | + _tableView.dataSource = self; | ||
| 192 | + _tableView.estimatedRowHeight = 44; | ||
| 193 | + _tableView.estimatedSectionFooterHeight = 0; | ||
| 194 | + _tableView.estimatedSectionHeaderHeight = 0; | ||
| 195 | + _tableView.backgroundColor = [UIColor clearColor]; | ||
| 196 | + _tableView.alwaysBounceVertical = NO; | ||
| 197 | + _tableView.separatorStyle = UITableViewCellSeparatorStyleNone; | ||
| 198 | + if (@available(iOS 11.0, *)) { | ||
| 199 | + _tableView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever; | ||
| 200 | + } | ||
| 201 | + UIView *footer = [UIView new]; | ||
| 202 | + footer.backgroundColor = UIColor.whiteColor; | ||
| 203 | + _tableView.tableFooterView = footer; | ||
| 204 | + [_tableView registerClass:YBIBSheetCell.self forCellReuseIdentifier:NSStringFromClass(YBIBSheetCell.self)]; | ||
| 205 | + } | ||
| 206 | + return _tableView; | ||
| 207 | +} | ||
| 208 | + | ||
| 209 | +@end |
CNLiveOrganizationValidationModule/Classes/CNLiveOrganizationValidationModule/YBImageBrowser/ToolView/YBIBToolViewHandler.h
0 → 100644
| 1 | +// | ||
| 2 | +// YBIBToolViewHandler.h | ||
| 3 | +// YBImageBrowserDemo | ||
| 4 | +// | ||
| 5 | +// Created by 波儿菜 on 2019/7/7. | ||
| 6 | +// Copyright © 2019 杨波. All rights reserved. | ||
| 7 | +// | ||
| 8 | + | ||
| 9 | +#import "YBIBSheetView.h" | ||
| 10 | +#import "YBIBTopView.h" | ||
| 11 | +#import "YBIBDataProtocol.h" | ||
| 12 | +#import "YBIBOrientationReceiveProtocol.h" | ||
| 13 | +#import "YBIBOperateBrowserProtocol.h" | ||
| 14 | + | ||
| 15 | +NS_ASSUME_NONNULL_BEGIN | ||
| 16 | + | ||
| 17 | +@protocol YBIBToolViewHandler <YBIBGetBaseInfoProtocol, YBIBOperateBrowserProtocol, YBIBOrientationReceiveProtocol> | ||
| 18 | + | ||
| 19 | +@required | ||
| 20 | + | ||
| 21 | +/** | ||
| 22 | + 容器视图准备好了,可进行子视图的添加和布局 | ||
| 23 | + */ | ||
| 24 | +- (void)yb_containerViewIsReadied; | ||
| 25 | + | ||
| 26 | +/** | ||
| 27 | + 隐藏视图 | ||
| 28 | + | ||
| 29 | + @param hide 是否隐藏 | ||
| 30 | + */ | ||
| 31 | +- (void)yb_hide:(BOOL)hide; | ||
| 32 | + | ||
| 33 | +@optional | ||
| 34 | + | ||
| 35 | +/// 当前数据 | ||
| 36 | +@property (nonatomic, copy) id<YBIBDataProtocol>(^yb_currentData)(void); | ||
| 37 | + | ||
| 38 | +/** | ||
| 39 | + 页码变化了 | ||
| 40 | + */ | ||
| 41 | +- (void)yb_pageChanged; | ||
| 42 | + | ||
| 43 | +/** | ||
| 44 | + 偏移量变化了 | ||
| 45 | + | ||
| 46 | + @param offsetX 当前偏移量 | ||
| 47 | + */ | ||
| 48 | +- (void)yb_offsetXChanged:(CGFloat)offsetX; | ||
| 49 | + | ||
| 50 | +/** | ||
| 51 | + 响应长按手势 | ||
| 52 | + */ | ||
| 53 | +- (void)yb_respondsToLongPress; | ||
| 54 | + | ||
| 55 | +@end | ||
| 56 | + | ||
| 57 | +@interface YBIBToolViewHandler : NSObject <YBIBToolViewHandler> | ||
| 58 | + | ||
| 59 | +/// 弹出表单视图 | ||
| 60 | +@property (nonatomic, strong, readonly) YBIBSheetView *sheetView; | ||
| 61 | + | ||
| 62 | +/// 顶部显示页码视图 | ||
| 63 | +@property (nonatomic, strong, readonly) YBIBTopView *topView; | ||
| 64 | + | ||
| 65 | +@end | ||
| 66 | + | ||
| 67 | +NS_ASSUME_NONNULL_END |
CNLiveOrganizationValidationModule/Classes/CNLiveOrganizationValidationModule/YBImageBrowser/ToolView/YBIBToolViewHandler.m
0 → 100644
| 1 | +// | ||
| 2 | +// YBIBToolViewHandler.m | ||
| 3 | +// YBImageBrowserDemo | ||
| 4 | +// | ||
| 5 | +// Created by 波儿菜 on 2019/7/7. | ||
| 6 | +// Copyright © 2019 杨波. All rights reserved. | ||
| 7 | +// | ||
| 8 | + | ||
| 9 | +#import "YBIBToolViewHandler.h" | ||
| 10 | +#import "YBIBCopywriter.h" | ||
| 11 | +#import "YBIBUtilities.h" | ||
| 12 | + | ||
| 13 | +@interface YBIBToolViewHandler () | ||
| 14 | +@property (nonatomic, strong) YBIBSheetView *sheetView; | ||
| 15 | +@property (nonatomic, strong) YBIBSheetAction *saveAction; | ||
| 16 | +@property (nonatomic, strong) YBIBTopView *topView; | ||
| 17 | +@end | ||
| 18 | + | ||
| 19 | +@implementation YBIBToolViewHandler | ||
| 20 | + | ||
| 21 | +#pragma mark - <YBIBToolViewHandler> | ||
| 22 | + | ||
| 23 | +@synthesize yb_containerView = _yb_containerView; | ||
| 24 | +@synthesize yb_containerSize = _yb_containerSize; | ||
| 25 | +@synthesize yb_currentPage = _yb_currentPage; | ||
| 26 | +@synthesize yb_totalPage = _yb_totalPage; | ||
| 27 | +@synthesize yb_currentOrientation = _yb_currentOrientation; | ||
| 28 | +@synthesize yb_currentData = _yb_currentData; | ||
| 29 | + | ||
| 30 | +- (void)yb_containerViewIsReadied { | ||
| 31 | + [self.yb_containerView addSubview:self.topView]; | ||
| 32 | + [self layoutWithExpectOrientation:self.yb_currentOrientation()]; | ||
| 33 | +} | ||
| 34 | + | ||
| 35 | +- (void)yb_pageChanged { | ||
| 36 | + if (self.topView.operationType == YBIBTopViewOperationTypeSave) { | ||
| 37 | + self.topView.operationButton.hidden = [self currentDataShouldHideSaveButton]; | ||
| 38 | + } | ||
| 39 | + [self.topView setPage:self.yb_currentPage() totalPage:self.yb_totalPage()]; | ||
| 40 | +} | ||
| 41 | + | ||
| 42 | +- (void)yb_respondsToLongPress { | ||
| 43 | + [self showSheetView]; | ||
| 44 | +} | ||
| 45 | + | ||
| 46 | +- (void)yb_hide:(BOOL)hide { | ||
| 47 | + self.topView.hidden = hide; | ||
| 48 | + [self.sheetView hideWithAnimation:NO]; | ||
| 49 | +} | ||
| 50 | + | ||
| 51 | +- (void)yb_orientationWillChangeWithExpectOrientation:(UIDeviceOrientation)orientation { | ||
| 52 | + [self.sheetView hideWithAnimation:NO]; | ||
| 53 | +} | ||
| 54 | + | ||
| 55 | +- (void)yb_orientationChangeAnimationWithExpectOrientation:(UIDeviceOrientation)orientation { | ||
| 56 | + [self layoutWithExpectOrientation:orientation]; | ||
| 57 | +} | ||
| 58 | + | ||
| 59 | +#pragma mark - private | ||
| 60 | + | ||
| 61 | +- (BOOL)currentDataShouldHideSaveButton { | ||
| 62 | + id<YBIBDataProtocol> data = self.yb_currentData(); | ||
| 63 | + BOOL allow = [data respondsToSelector:@selector(yb_allowSaveToPhotoAlbum)] && [data yb_allowSaveToPhotoAlbum]; | ||
| 64 | + BOOL can = [data respondsToSelector:@selector(yb_saveToPhotoAlbum)]; | ||
| 65 | + return !(allow && can); | ||
| 66 | +} | ||
| 67 | + | ||
| 68 | +- (void)layoutWithExpectOrientation:(UIDeviceOrientation)orientation { | ||
| 69 | + CGSize containerSize = self.yb_containerSize(orientation); | ||
| 70 | + UIEdgeInsets padding = YBIBPaddingByBrowserOrientation(orientation); | ||
| 71 | + | ||
| 72 | + self.topView.frame = CGRectMake(padding.left, padding.top, containerSize.width - padding.left - padding.right, [YBIBTopView defaultHeight]); | ||
| 73 | +} | ||
| 74 | + | ||
| 75 | +- (void)showSheetView { | ||
| 76 | + if ([self currentDataShouldHideSaveButton]) { | ||
| 77 | + [self.sheetView.actions removeObject:self.saveAction]; | ||
| 78 | + } else { | ||
| 79 | + if (![self.sheetView.actions containsObject:self.saveAction]) { | ||
| 80 | + [self.sheetView.actions addObject:self.saveAction]; | ||
| 81 | + } | ||
| 82 | + } | ||
| 83 | + [self.sheetView showToView:self.yb_containerView orientation:self.yb_currentOrientation()]; | ||
| 84 | +} | ||
| 85 | + | ||
| 86 | +#pragma mark - getters | ||
| 87 | + | ||
| 88 | +- (YBIBSheetView *)sheetView { | ||
| 89 | + if (!_sheetView) { | ||
| 90 | + _sheetView = [YBIBSheetView new]; | ||
| 91 | + __weak typeof(self) wSelf = self; | ||
| 92 | + [_sheetView setCurrentdata:^id<YBIBDataProtocol>{ | ||
| 93 | + __strong typeof(wSelf) self = wSelf; | ||
| 94 | + if (!self) return nil; | ||
| 95 | + return self.yb_currentData(); | ||
| 96 | + }]; | ||
| 97 | + } | ||
| 98 | + return _sheetView; | ||
| 99 | +} | ||
| 100 | + | ||
| 101 | +- (YBIBSheetAction *)saveAction { | ||
| 102 | + if (!_saveAction) { | ||
| 103 | + __weak typeof(self) wSelf = self; | ||
| 104 | + _saveAction = [YBIBSheetAction actionWithName:[YBIBCopywriter sharedCopywriter].saveToPhotoAlbum action:^(id<YBIBDataProtocol> data) { | ||
| 105 | + __strong typeof(wSelf) self = wSelf; | ||
| 106 | + if (!self) return; | ||
| 107 | + if ([data respondsToSelector:@selector(yb_saveToPhotoAlbum)]) { | ||
| 108 | + [data yb_saveToPhotoAlbum]; | ||
| 109 | + } | ||
| 110 | + [self.sheetView hideWithAnimation:YES]; | ||
| 111 | + }]; | ||
| 112 | + } | ||
| 113 | + return _saveAction; | ||
| 114 | +} | ||
| 115 | + | ||
| 116 | +- (YBIBTopView *)topView { | ||
| 117 | + if (!_topView) { | ||
| 118 | + _topView = [YBIBTopView new]; | ||
| 119 | + _topView.operationType = YBIBTopViewOperationTypeMore; | ||
| 120 | + __weak typeof(self) wSelf = self; | ||
| 121 | + [_topView setClickOperation:^(YBIBTopViewOperationType type) { | ||
| 122 | + __strong typeof(wSelf) self = wSelf; | ||
| 123 | + if (!self) return; | ||
| 124 | + switch (type) { | ||
| 125 | + case YBIBTopViewOperationTypeSave: { | ||
| 126 | + id<YBIBDataProtocol> data = self.yb_currentData(); | ||
| 127 | + if ([data respondsToSelector:@selector(yb_saveToPhotoAlbum)]) { | ||
| 128 | + [data yb_saveToPhotoAlbum]; | ||
| 129 | + } | ||
| 130 | + } | ||
| 131 | + break; | ||
| 132 | + case YBIBTopViewOperationTypeMore: { | ||
| 133 | + [self showSheetView]; | ||
| 134 | + } | ||
| 135 | + break; | ||
| 136 | + default: | ||
| 137 | + break; | ||
| 138 | + } | ||
| 139 | + }]; | ||
| 140 | + } | ||
| 141 | + return _topView; | ||
| 142 | +} | ||
| 143 | + | ||
| 144 | +@end |
CNLiveOrganizationValidationModule/Classes/CNLiveOrganizationValidationModule/YBImageBrowser/ToolView/YBIBTopView.h
0 → 100644
| 1 | +// | ||
| 2 | +// YBIBTopView.h | ||
| 3 | +// YBImageBrowserDemo | ||
| 4 | +// | ||
| 5 | +// Created by 波儿菜 on 2019/7/6. | ||
| 6 | +// Copyright © 2019 杨波. All rights reserved. | ||
| 7 | +// | ||
| 8 | + | ||
| 9 | +#import <UIKit/UIKit.h> | ||
| 10 | + | ||
| 11 | +NS_ASSUME_NONNULL_BEGIN | ||
| 12 | + | ||
| 13 | +typedef NS_ENUM(NSInteger, YBIBTopViewOperationType) { | ||
| 14 | + YBIBTopViewOperationTypeSave, //保存 | ||
| 15 | + YBIBTopViewOperationTypeMore //更多 | ||
| 16 | +}; | ||
| 17 | + | ||
| 18 | +@interface YBIBTopView : UIView | ||
| 19 | + | ||
| 20 | +/// 页码标签 | ||
| 21 | +@property (nonatomic, strong, readonly) UILabel *pageLabel; | ||
| 22 | + | ||
| 23 | +/// 操作按钮(自定义:直接修改图片或文字,然后添加点击事件) | ||
| 24 | +@property (nonatomic, strong, readonly) UIButton *operationButton; | ||
| 25 | + | ||
| 26 | +/// 按钮类型 | ||
| 27 | +@property (nonatomic, assign) YBIBTopViewOperationType operationType; | ||
| 28 | + | ||
| 29 | +/** | ||
| 30 | + 设置页码 | ||
| 31 | + | ||
| 32 | + @param page 当前页码 | ||
| 33 | + @param totalPage 总页码数 | ||
| 34 | + */ | ||
| 35 | +- (void)setPage:(NSInteger)page totalPage:(NSInteger)totalPage; | ||
| 36 | + | ||
| 37 | +/// 点击操作按钮的回调 | ||
| 38 | +@property (nonatomic, copy) void(^clickOperation)(YBIBTopViewOperationType type); | ||
| 39 | + | ||
| 40 | ++ (CGFloat)defaultHeight; | ||
| 41 | + | ||
| 42 | +@end | ||
| 43 | + | ||
| 44 | +NS_ASSUME_NONNULL_END |
CNLiveOrganizationValidationModule/Classes/CNLiveOrganizationValidationModule/YBImageBrowser/ToolView/YBIBTopView.m
0 → 100644
| 1 | +// | ||
| 2 | +// YBIBTopView.m | ||
| 3 | +// YBImageBrowserDemo | ||
| 4 | +// | ||
| 5 | +// Created by 波儿菜 on 2019/7/6. | ||
| 6 | +// Copyright © 2019 杨波. All rights reserved. | ||
| 7 | +// | ||
| 8 | + | ||
| 9 | +#import "YBIBTopView.h" | ||
| 10 | +#import "YBIBIconManager.h" | ||
| 11 | +#import "YBIBUtilities.h" | ||
| 12 | + | ||
| 13 | +@interface YBIBTopView () | ||
| 14 | +@property (nonatomic, strong) UILabel *pageLabel; | ||
| 15 | +@property (nonatomic, strong) UIButton *operationButton; | ||
| 16 | +@end | ||
| 17 | + | ||
| 18 | +@implementation YBIBTopView | ||
| 19 | + | ||
| 20 | +#pragma mark - life cycle | ||
| 21 | + | ||
| 22 | +- (instancetype)initWithFrame:(CGRect)frame { | ||
| 23 | + self = [super initWithFrame:frame]; | ||
| 24 | + if (self) { | ||
| 25 | + [self addSubview:self.pageLabel]; | ||
| 26 | + [self addSubview:self.operationButton]; | ||
| 27 | + | ||
| 28 | + [self setOperationType:YBIBTopViewOperationTypeMore]; | ||
| 29 | + } | ||
| 30 | + return self; | ||
| 31 | +} | ||
| 32 | + | ||
| 33 | +- (void)layoutSubviews { | ||
| 34 | + [super layoutSubviews]; | ||
| 35 | + CGFloat height = self.bounds.size.height, width = self.bounds.size.width; | ||
| 36 | + self.pageLabel.frame = CGRectMake(16, 0, width / 3, height); | ||
| 37 | + CGFloat buttonWidth = 54; | ||
| 38 | + self.operationButton.frame = CGRectMake(width - buttonWidth, 0, buttonWidth, height); | ||
| 39 | +} | ||
| 40 | + | ||
| 41 | +#pragma mark - public | ||
| 42 | + | ||
| 43 | ++ (CGFloat)defaultHeight { | ||
| 44 | + return 50; | ||
| 45 | +} | ||
| 46 | + | ||
| 47 | +- (void)setPage:(NSInteger)page totalPage:(NSInteger)totalPage { | ||
| 48 | + if (totalPage <= 1) { | ||
| 49 | + self.pageLabel.hidden = YES; | ||
| 50 | + } else { | ||
| 51 | + self.pageLabel.hidden = NO; | ||
| 52 | + | ||
| 53 | + NSString *text = [NSString stringWithFormat:@"%ld/%ld", page + (NSInteger)1, totalPage]; | ||
| 54 | + NSShadow *shadow = [NSShadow new]; | ||
| 55 | + shadow.shadowBlurRadius = 4; | ||
| 56 | + shadow.shadowOffset = CGSizeMake(0, 1); | ||
| 57 | + shadow.shadowColor = UIColor.darkGrayColor; | ||
| 58 | + NSMutableAttributedString *attr = [[NSMutableAttributedString alloc] initWithString:text attributes:@{NSShadowAttributeName:shadow}]; | ||
| 59 | + self.pageLabel.attributedText = attr; | ||
| 60 | + } | ||
| 61 | +} | ||
| 62 | + | ||
| 63 | +#pragma mark - event | ||
| 64 | + | ||
| 65 | +- (void)clickOperationButton:(UIButton *)button { | ||
| 66 | + if (self.clickOperation) self.clickOperation(self.operationType); | ||
| 67 | +} | ||
| 68 | + | ||
| 69 | +#pragma mark - getters & setters | ||
| 70 | + | ||
| 71 | +- (void)setOperationType:(YBIBTopViewOperationType)operationType { | ||
| 72 | + _operationType = operationType; | ||
| 73 | + | ||
| 74 | + UIImage *image = nil; | ||
| 75 | + switch (operationType) { | ||
| 76 | + case YBIBTopViewOperationTypeSave: | ||
| 77 | + image = [YBIBIconManager sharedManager].toolSaveImage(); | ||
| 78 | + break; | ||
| 79 | + case YBIBTopViewOperationTypeMore: | ||
| 80 | + image = [YBIBIconManager sharedManager].toolMoreImage(); | ||
| 81 | + break; | ||
| 82 | + } | ||
| 83 | + | ||
| 84 | + [self.operationButton setImage:image forState:UIControlStateNormal]; | ||
| 85 | +} | ||
| 86 | + | ||
| 87 | +- (UILabel *)pageLabel { | ||
| 88 | + if (!_pageLabel) { | ||
| 89 | + _pageLabel = [UILabel new]; | ||
| 90 | + _pageLabel.textColor = [UIColor whiteColor]; | ||
| 91 | + _pageLabel.font = [UIFont boldSystemFontOfSize:16]; | ||
| 92 | + _pageLabel.textAlignment = NSTextAlignmentLeft; | ||
| 93 | + _pageLabel.adjustsFontSizeToFitWidth = YES; | ||
| 94 | + } | ||
| 95 | + return _pageLabel; | ||
| 96 | +} | ||
| 97 | + | ||
| 98 | +- (UIButton *)operationButton { | ||
| 99 | + if (!_operationButton) { | ||
| 100 | + _operationButton = [UIButton buttonWithType:UIButtonTypeCustom]; | ||
| 101 | + _operationButton.titleLabel.font = [UIFont boldSystemFontOfSize:16]; | ||
| 102 | + _operationButton.titleLabel.adjustsFontSizeToFitWidth = YES; | ||
| 103 | + [_operationButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal]; | ||
| 104 | + [_operationButton addTarget:self action:@selector(clickOperationButton:) forControlEvents:UIControlEventTouchUpInside]; | ||
| 105 | + _operationButton.layer.shadowColor = UIColor.darkGrayColor.CGColor; | ||
| 106 | + _operationButton.layer.shadowOffset = CGSizeMake(0, 1); | ||
| 107 | + _operationButton.layer.shadowOpacity = 1; | ||
| 108 | + _operationButton.layer.shadowRadius = 4; | ||
| 109 | + } | ||
| 110 | + return _operationButton; | ||
| 111 | +} | ||
| 112 | + | ||
| 113 | +@end |
CNLiveOrganizationValidationModule/Classes/CNLiveOrganizationValidationModule/YBImageBrowser/WebImageMediator/YBIBDefaultWebImageMediator.h
0 → 100644
| 1 | +// | ||
| 2 | +// YBIBDefaultWebImageMediator.h | ||
| 3 | +// YBImageBrowserDemo | ||
| 4 | +// | ||
| 5 | +// Created by 波儿菜 on 2019/8/27. | ||
| 6 | +// Copyright © 2019 杨波. All rights reserved. | ||
| 7 | +// | ||
| 8 | + | ||
| 9 | +#import "YBIBWebImageMediator.h" | ||
| 10 | + | ||
| 11 | +NS_ASSUME_NONNULL_BEGIN | ||
| 12 | + | ||
| 13 | +@interface YBIBDefaultWebImageMediator : NSObject <YBIBWebImageMediator> | ||
| 14 | + | ||
| 15 | +@end | ||
| 16 | + | ||
| 17 | +NS_ASSUME_NONNULL_END |
CNLiveOrganizationValidationModule/Classes/CNLiveOrganizationValidationModule/YBImageBrowser/WebImageMediator/YBIBDefaultWebImageMediator.m
0 → 100644
| 1 | +// | ||
| 2 | +// YBIBDefaultWebImageMediator.m | ||
| 3 | +// YBImageBrowserDemo | ||
| 4 | +// | ||
| 5 | +// Created by 波儿菜 on 2019/8/27. | ||
| 6 | +// Copyright © 2019 杨波. All rights reserved. | ||
| 7 | +// | ||
| 8 | + | ||
| 9 | +#import "YBIBDefaultWebImageMediator.h" | ||
| 10 | +#import "YBIBUtilities.h" | ||
| 11 | +#if __has_include(<SDWebImage/SDWebImage.h>) | ||
| 12 | +#import <SDWebImage/SDWebImage.h> | ||
| 13 | +#else | ||
| 14 | +#import "SDWebImage.h" | ||
| 15 | +#endif | ||
| 16 | + | ||
| 17 | +@implementation YBIBDefaultWebImageMediator | ||
| 18 | + | ||
| 19 | +#pragma mark - <YBIBWebImageMediator> | ||
| 20 | + | ||
| 21 | +- (id)yb_downloadImageWithURL:(NSURL *)URL requestModifier:(nullable YBIBWebImageRequestModifierBlock)requestModifier progress:(nonnull YBIBWebImageProgressBlock)progress success:(nonnull YBIBWebImageSuccessBlock)success failed:(nonnull YBIBWebImageFailedBlock)failed { | ||
| 22 | + if (!URL) return nil; | ||
| 23 | + | ||
| 24 | + SDWebImageContext *context = nil; | ||
| 25 | + if (requestModifier) { | ||
| 26 | + SDWebImageDownloaderRequestModifier *modifier = [SDWebImageDownloaderRequestModifier requestModifierWithBlock:requestModifier]; | ||
| 27 | + context = @{SDWebImageContextDownloadRequestModifier:modifier}; | ||
| 28 | + } | ||
| 29 | + | ||
| 30 | + SDWebImageDownloaderOptions options = SDWebImageDownloaderLowPriority | SDWebImageDownloaderAvoidDecodeImage; | ||
| 31 | + | ||
| 32 | + SDWebImageDownloadToken *token = [[SDWebImageDownloader sharedDownloader] downloadImageWithURL:URL options:options context:context progress:^(NSInteger receivedSize, NSInteger expectedSize, NSURL * _Nullable targetURL) { | ||
| 33 | + if (progress) progress(receivedSize, expectedSize); | ||
| 34 | + } completed:^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, BOOL finished) { | ||
| 35 | + if (error) { | ||
| 36 | + if (failed) failed(error, finished); | ||
| 37 | + } else { | ||
| 38 | + if (success) success(data, finished); | ||
| 39 | + } | ||
| 40 | + }]; | ||
| 41 | + return token; | ||
| 42 | +} | ||
| 43 | + | ||
| 44 | +- (void)yb_cancelTaskWithDownloadToken:(id)token { | ||
| 45 | + if (token && [token isKindOfClass:SDWebImageDownloadToken.class]) { | ||
| 46 | + [((SDWebImageDownloadToken *)token) cancel]; | ||
| 47 | + } | ||
| 48 | +} | ||
| 49 | + | ||
| 50 | +- (void)yb_storeToDiskWithImageData:(NSData *)data forKey:(NSURL *)key { | ||
| 51 | + if (!key) return; | ||
| 52 | + NSString *cacheKey = [SDWebImageManager.sharedManager cacheKeyForURL:key]; | ||
| 53 | + if (!cacheKey) return; | ||
| 54 | + | ||
| 55 | + YBIB_DISPATCH_ASYNC(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{ | ||
| 56 | + [[SDImageCache sharedImageCache] storeImageDataToDisk:data forKey:cacheKey]; | ||
| 57 | + }) | ||
| 58 | +} | ||
| 59 | + | ||
| 60 | +- (void)yb_queryCacheOperationForKey:(NSURL *)key completed:(YBIBWebImageCacheQueryCompletedBlock)completed { | ||
| 61 | +#define QUERY_CACHE_FAILED if (completed) {completed(nil, nil); return;} | ||
| 62 | + if (!key) QUERY_CACHE_FAILED | ||
| 63 | + NSString *cacheKey = [SDWebImageManager.sharedManager cacheKeyForURL:key]; | ||
| 64 | + if (!cacheKey) QUERY_CACHE_FAILED | ||
| 65 | +#undef QUERY_CACHE_FAILED | ||
| 66 | + | ||
| 67 | + // 'NSData' of image must be read to ensure decoding correctly. | ||
| 68 | + SDImageCacheOptions options = SDImageCacheQueryMemoryData | SDImageCacheAvoidDecodeImage; | ||
| 69 | + [[SDImageCache sharedImageCache] queryCacheOperationForKey:cacheKey options:options done:^(UIImage * _Nullable image, NSData * _Nullable data, SDImageCacheType cacheType) { | ||
| 70 | + if (completed) completed(image, data); | ||
| 71 | + }]; | ||
| 72 | +} | ||
| 73 | + | ||
| 74 | +@end |
CNLiveOrganizationValidationModule/Classes/CNLiveOrganizationValidationModule/YBImageBrowser/WebImageMediator/YBIBWebImageMediator.h
0 → 100644
| 1 | +// | ||
| 2 | +// YBIBWebImageMediator.h | ||
| 3 | +// YBImageBrowserDemo | ||
| 4 | +// | ||
| 5 | +// Created by 波儿菜 on 2019/8/27. | ||
| 6 | +// Copyright © 2019 杨波. All rights reserved. | ||
| 7 | +// | ||
| 8 | + | ||
| 9 | +#import <UIKit/UIKit.h> | ||
| 10 | + | ||
| 11 | +NS_ASSUME_NONNULL_BEGIN | ||
| 12 | + | ||
| 13 | +typedef NSURLRequest * _Nullable (^YBIBWebImageRequestModifierBlock)(NSURLRequest *request); | ||
| 14 | +typedef void(^YBIBWebImageProgressBlock)(NSInteger receivedSize, NSInteger expectedSize); | ||
| 15 | +typedef void(^YBIBWebImageSuccessBlock)(NSData * _Nullable imageData, BOOL finished); | ||
| 16 | +typedef void(^YBIBWebImageFailedBlock)(NSError * _Nullable error, BOOL finished); | ||
| 17 | +typedef void(^YBIBWebImageCacheQueryCompletedBlock)(UIImage * _Nullable image, NSData * _Nullable imageData); | ||
| 18 | + | ||
| 19 | +@protocol YBIBWebImageMediator <NSObject> | ||
| 20 | + | ||
| 21 | +@required | ||
| 22 | + | ||
| 23 | +/** | ||
| 24 | + 下载图片 | ||
| 25 | + | ||
| 26 | + @param URL 图片地址 | ||
| 27 | + @param requestModifier 修改默认 NSURLRequest 的闭包 | ||
| 28 | + @param progress 进度回调 | ||
| 29 | + @param success 成功回调 | ||
| 30 | + @param failed 失败回调 | ||
| 31 | + @return 下载 token (可为空) | ||
| 32 | + */ | ||
| 33 | +- (id)yb_downloadImageWithURL:(NSURL *)URL requestModifier:(nullable YBIBWebImageRequestModifierBlock)requestModifier progress:(YBIBWebImageProgressBlock)progress success:(YBIBWebImageSuccessBlock)success failed:(YBIBWebImageFailedBlock)failed; | ||
| 34 | + | ||
| 35 | +/** | ||
| 36 | + 缓存图片数据到磁盘 | ||
| 37 | + | ||
| 38 | + @param data 图片数据 | ||
| 39 | + @param key 缓存标识 | ||
| 40 | + */ | ||
| 41 | +- (void)yb_storeToDiskWithImageData:(nullable NSData *)data forKey:(NSURL *)key; | ||
| 42 | + | ||
| 43 | +/** | ||
| 44 | + 读取图片数据 | ||
| 45 | + | ||
| 46 | + @param key 缓存标识 | ||
| 47 | + @param completed 读取回调 | ||
| 48 | + */ | ||
| 49 | +- (void)yb_queryCacheOperationForKey:(NSURL *)key completed:(YBIBWebImageCacheQueryCompletedBlock)completed; | ||
| 50 | + | ||
| 51 | +@optional | ||
| 52 | + | ||
| 53 | +/** | ||
| 54 | + 取消下载 | ||
| 55 | + | ||
| 56 | + @param token 下载 token | ||
| 57 | + */ | ||
| 58 | +- (void)yb_cancelTaskWithDownloadToken:(id)token; | ||
| 59 | + | ||
| 60 | +@end | ||
| 61 | + | ||
| 62 | +NS_ASSUME_NONNULL_END |
CNLiveOrganizationValidationModule/Classes/CNLiveOrganizationValidationModule/YBImageBrowser/YBImageBrowser.h
0 → 100644
| 1 | +// | ||
| 2 | +// YBImageBrowser.h | ||
| 3 | +// YBImageBrowserDemo | ||
| 4 | +// | ||
| 5 | +// Created by 波儿菜 on 2019/6/5. | ||
| 6 | +// Copyright © 2019 波儿菜. All rights reserved. | ||
| 7 | +// | ||
| 8 | + | ||
| 9 | +#import "YBIBCollectionView.h" | ||
| 10 | +#import "YBImageBrowserDataSource.h" | ||
| 11 | +#import "YBImageBrowserDelegate.h" | ||
| 12 | +#import "YBIBDataProtocol.h" | ||
| 13 | +#import "YBIBCellProtocol.h" | ||
| 14 | +#import "YBIBAnimatedTransition.h" | ||
| 15 | +#import "YBIBAuxiliaryViewHandler.h" | ||
| 16 | +#import "YBIBToolViewHandler.h" | ||
| 17 | +#import "YBIBWebImageMediator.h" | ||
| 18 | +#import "YBIBImageData.h" | ||
| 19 | + | ||
| 20 | +NS_ASSUME_NONNULL_BEGIN | ||
| 21 | + | ||
| 22 | +@interface YBImageBrowser : UIView | ||
| 23 | + | ||
| 24 | +/// 数据源数组 | ||
| 25 | +@property (nonatomic, copy) NSArray<id<YBIBDataProtocol>> *dataSourceArray; | ||
| 26 | + | ||
| 27 | +/// 数据源代理 | ||
| 28 | +@property (nonatomic, weak) id<YBImageBrowserDataSource> dataSource; | ||
| 29 | + | ||
| 30 | +/// 状态回调代理 | ||
| 31 | +@property (nonatomic, weak) id<YBImageBrowserDelegate> delegate; | ||
| 32 | + | ||
| 33 | +/** | ||
| 34 | + 展示图片浏览器 | ||
| 35 | + | ||
| 36 | + @param view 指定父视图(view 的大小不能为 CGSizeZero,但允许变化) | ||
| 37 | + @param containerSize 容器大小(当 view 的大小允许变化时,必须指定确切的 containerSize) | ||
| 38 | + */ | ||
| 39 | +- (void)showToView:(UIView *)view containerSize:(CGSize)containerSize; | ||
| 40 | +- (void)showToView:(UIView *)view; | ||
| 41 | +- (void)show; | ||
| 42 | + | ||
| 43 | +/** | ||
| 44 | + 隐藏图片浏览器(不建议外部持有图片浏览器重复使用) | ||
| 45 | + */ | ||
| 46 | +- (void)hide; | ||
| 47 | + | ||
| 48 | +/// 当前页码 | ||
| 49 | +@property (nonatomic, assign) NSInteger currentPage; | ||
| 50 | + | ||
| 51 | +/// 分页间距 | ||
| 52 | +@property (nonatomic, assign) CGFloat distanceBetweenPages; | ||
| 53 | + | ||
| 54 | +/// 当前图片浏览器的方向 | ||
| 55 | +@property (nonatomic, assign, readonly) UIDeviceOrientation currentOrientation; | ||
| 56 | + | ||
| 57 | +/// 图片浏览器支持的方向 (仅当前控制器不支持旋转时有效,否则将跟随控制器旋转) | ||
| 58 | +@property (nonatomic, assign) UIInterfaceOrientationMask supportedOrientations; | ||
| 59 | + | ||
| 60 | +/// 是否自动隐藏 id<YBIBImageData> 设置的映射视图,默认为 YES | ||
| 61 | +@property (nonatomic, assign) BOOL autoHideProjectiveView; | ||
| 62 | + | ||
| 63 | +/// 是否正在转场 | ||
| 64 | +@property (nonatomic, assign, readonly, getter=isTransitioning) BOOL transitioning; | ||
| 65 | +/// 是否正在进行展示过程转场 | ||
| 66 | +@property (nonatomic, assign, readonly, getter=isShowTransitioning) BOOL showTransitioning; | ||
| 67 | +/// 是否正在进行隐藏过程转场 | ||
| 68 | +@property (nonatomic, assign, readonly, getter=isHideTransitioning) BOOL hideTransitioning; | ||
| 69 | + | ||
| 70 | +/// 预加载数量 (默认为 2,低内存设备默认为 0) | ||
| 71 | +@property (nonatomic, assign) NSUInteger preloadCount; | ||
| 72 | + | ||
| 73 | +/** | ||
| 74 | + 重载数据,请保证数据源被正确修改 | ||
| 75 | + */ | ||
| 76 | +- (void)reloadData; | ||
| 77 | + | ||
| 78 | +/** | ||
| 79 | + 获取当前展示的数据对象 | ||
| 80 | + | ||
| 81 | + @return 数据对象 | ||
| 82 | + */ | ||
| 83 | +- (id<YBIBDataProtocol>)currentData; | ||
| 84 | + | ||
| 85 | +/// 是否隐藏状态栏,默认为 YES(该值为 YES 时需要在 info.plist 中添加 View controller-based status bar appearance : NO 才能生效) | ||
| 86 | +@property (nonatomic, assign) BOOL shouldHideStatusBar; | ||
| 87 | + | ||
| 88 | +/// 工具视图处理器 | ||
| 89 | +/// 赋值可自定义,实现者可以直接用 UIView,或者创建一个中介者管理一系列的 UIView。 | ||
| 90 | +/// 内部消息是按照数组下标顺序调度的,所以如果有多个处理器注意添加 UIView 的视图层级。 | ||
| 91 | +@property (nonatomic, copy) NSArray<id<YBIBToolViewHandler>> *toolViewHandlers; | ||
| 92 | +/// 默认工具视图处理器 | ||
| 93 | +@property (nonatomic, weak, readonly) YBIBToolViewHandler *defaultToolViewHandler; | ||
| 94 | + | ||
| 95 | +/// Toast/Loading 处理器 (赋值可自定义) | ||
| 96 | +@property (nonatomic, strong) id<YBIBAuxiliaryViewHandler> auxiliaryViewHandler; | ||
| 97 | + | ||
| 98 | +/// 转场实现类 (赋值可自定义) | ||
| 99 | +@property (nonatomic, strong) id<YBIBAnimatedTransition> animatedTransition; | ||
| 100 | +/// 默认转场实现类 (可配置其属性) | ||
| 101 | +@property (nonatomic, weak, readonly) YBIBAnimatedTransition *defaultAnimatedTransition; | ||
| 102 | + | ||
| 103 | +/// 图片下载缓存相关的中介者(赋值可自定义) | ||
| 104 | +@property (nonatomic, strong) id<YBIBWebImageMediator> webImageMediator; | ||
| 105 | + | ||
| 106 | +/// 核心集合视图 | ||
| 107 | +@property (nonatomic, strong, readonly) YBIBCollectionView *collectionView; | ||
| 108 | + | ||
| 109 | +@end | ||
| 110 | + | ||
| 111 | +NS_ASSUME_NONNULL_END |
CNLiveOrganizationValidationModule/Classes/CNLiveOrganizationValidationModule/YBImageBrowser/YBImageBrowser.m
0 → 100644
| 1 | +// | ||
| 2 | +// YBImageBrowser.m | ||
| 3 | +// YBImageBrowserDemo | ||
| 4 | +// | ||
| 5 | +// Created by 波儿菜 on 2019/6/5. | ||
| 6 | +// Copyright © 2019 波儿菜. All rights reserved. | ||
| 7 | +// | ||
| 8 | + | ||
| 9 | +#import "YBImageBrowser.h" | ||
| 10 | +#import "YBIBUtilities.h" | ||
| 11 | +#import "YBIBCellProtocol.h" | ||
| 12 | +#import "YBIBDataMediator.h" | ||
| 13 | +#import "YBIBScreenRotationHandler.h" | ||
| 14 | +#import "NSObject+YBImageBrowser.h" | ||
| 15 | +#import "YBImageBrowser+Internal.h" | ||
| 16 | +#if __has_include("YBIBDefaultWebImageMediator.h") | ||
| 17 | +#import "YBIBDefaultWebImageMediator.h" | ||
| 18 | +#endif | ||
| 19 | + | ||
| 20 | +@interface YBImageBrowser () <UICollectionViewDelegate, UICollectionViewDataSource> | ||
| 21 | +@property (nonatomic, strong) YBIBCollectionView *collectionView; | ||
| 22 | +@property (nonatomic, strong) YBIBDataMediator *dataMediator; | ||
| 23 | +@property (nonatomic, strong) YBIBScreenRotationHandler *rotationHandler; | ||
| 24 | +@end | ||
| 25 | + | ||
| 26 | +@implementation YBImageBrowser { | ||
| 27 | + BOOL _originStatusBarHidden; | ||
| 28 | +} | ||
| 29 | + | ||
| 30 | +#pragma mark - life cycle | ||
| 31 | + | ||
| 32 | +- (void)dealloc { | ||
| 33 | + self.hiddenProjectiveView = nil; | ||
| 34 | + [self showStatusBar]; | ||
| 35 | +} | ||
| 36 | + | ||
| 37 | +- (instancetype)initWithFrame:(CGRect)frame { | ||
| 38 | + self = [super initWithFrame:frame]; | ||
| 39 | + if (self) { | ||
| 40 | + self.backgroundColor = UIColor.blackColor; | ||
| 41 | + UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(respondsToLongPress:)]; | ||
| 42 | + [self addGestureRecognizer:longPress]; | ||
| 43 | + [self initValue]; | ||
| 44 | + } | ||
| 45 | + return self; | ||
| 46 | +} | ||
| 47 | + | ||
| 48 | +- (void)initValue { | ||
| 49 | + _transitioning = _showTransitioning = _hideTransitioning = NO; | ||
| 50 | + _defaultAnimatedTransition = _animatedTransition = [YBIBAnimatedTransition new]; | ||
| 51 | + _toolViewHandlers = @[[YBIBToolViewHandler new]]; | ||
| 52 | + _defaultToolViewHandler = _toolViewHandlers[0]; | ||
| 53 | + _auxiliaryViewHandler = [YBIBAuxiliaryViewHandler new]; | ||
| 54 | + _shouldHideStatusBar = YES; | ||
| 55 | + _autoHideProjectiveView = YES; | ||
| 56 | +#if __has_include("YBIBDefaultWebImageMediator.h") | ||
| 57 | + _webImageMediator = [YBIBDefaultWebImageMediator new]; | ||
| 58 | +#endif | ||
| 59 | +} | ||
| 60 | + | ||
| 61 | +#pragma mark - private | ||
| 62 | + | ||
| 63 | +- (void)build { | ||
| 64 | + [self addSubview:self.collectionView]; | ||
| 65 | + self.collectionView.frame = self.bounds; | ||
| 66 | + self.collectionView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; | ||
| 67 | + | ||
| 68 | + [self addSubview:self.containerView]; | ||
| 69 | + self.containerView.frame = self.bounds; | ||
| 70 | + self.containerView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; | ||
| 71 | + | ||
| 72 | + [self buildToolView]; | ||
| 73 | + | ||
| 74 | + [self layoutIfNeeded]; | ||
| 75 | + | ||
| 76 | + [self collectionViewScrollToPage:self.currentPage]; | ||
| 77 | + [self.rotationHandler startObserveDeviceOrientation]; | ||
| 78 | +} | ||
| 79 | + | ||
| 80 | +- (void)buildToolView { | ||
| 81 | + for (id<YBIBToolViewHandler> handler in self.toolViewHandlers) { | ||
| 82 | + [self implementGetBaseInfoProtocol:handler]; | ||
| 83 | + [self implementOperateBrowserProtocol:handler]; | ||
| 84 | + __weak typeof(self) wSelf = self; | ||
| 85 | + if ([handler respondsToSelector:@selector(setYb_currentData:)]) { | ||
| 86 | + [handler setYb_currentData:^id<YBIBDataProtocol>{ | ||
| 87 | + __strong typeof(wSelf) self = wSelf; | ||
| 88 | + if (!self) return nil; | ||
| 89 | + return self.currentData; | ||
| 90 | + }]; | ||
| 91 | + } | ||
| 92 | + [handler yb_containerViewIsReadied]; | ||
| 93 | + [handler yb_hide:NO]; | ||
| 94 | + } | ||
| 95 | +} | ||
| 96 | + | ||
| 97 | +- (void)rebuild { | ||
| 98 | + self.hiddenProjectiveView = nil; | ||
| 99 | + [self showStatusBar]; | ||
| 100 | + [self.containerView removeFromSuperview]; | ||
| 101 | + _containerView = nil; | ||
| 102 | + [self.collectionView removeFromSuperview]; | ||
| 103 | + _collectionView = nil; | ||
| 104 | + [self.dataMediator clear]; | ||
| 105 | + [self.rotationHandler clear]; | ||
| 106 | +} | ||
| 107 | + | ||
| 108 | +- (void)collectionViewScrollToPage:(NSInteger)page { | ||
| 109 | + [self.collectionView scrollToPage:page]; | ||
| 110 | + [self pageNumberChanged]; | ||
| 111 | +} | ||
| 112 | + | ||
| 113 | +- (void)pageNumberChanged { | ||
| 114 | + id<YBIBDataProtocol> data = self.currentData; | ||
| 115 | + UIView *projectiveView = nil; | ||
| 116 | + if ([data respondsToSelector:@selector(yb_projectiveView)]) { | ||
| 117 | + projectiveView = [data yb_projectiveView]; | ||
| 118 | + } | ||
| 119 | + self.hiddenProjectiveView = projectiveView; | ||
| 120 | + | ||
| 121 | + if (self.delegate && [self.delegate respondsToSelector:@selector(yb_imageBrowser:pageChanged:data:)]) { | ||
| 122 | + [self.delegate yb_imageBrowser:self pageChanged:self.currentPage data:data]; | ||
| 123 | + } | ||
| 124 | + for (id<YBIBToolViewHandler> handler in self.toolViewHandlers) { | ||
| 125 | + if ([handler respondsToSelector:@selector(yb_pageChanged)]) { | ||
| 126 | + [handler yb_pageChanged]; | ||
| 127 | + } | ||
| 128 | + } | ||
| 129 | + NSArray *visibleCells = self.collectionView.visibleCells; | ||
| 130 | + for (UICollectionViewCell<YBIBCellProtocol> *cell in visibleCells) { | ||
| 131 | + if ([cell respondsToSelector:@selector(yb_pageChanged)]) { | ||
| 132 | + [cell yb_pageChanged]; | ||
| 133 | + } | ||
| 134 | + } | ||
| 135 | +} | ||
| 136 | + | ||
| 137 | +- (void)showStatusBar { | ||
| 138 | + if (self.shouldHideStatusBar) { | ||
| 139 | + [UIApplication sharedApplication].statusBarHidden = _originStatusBarHidden; | ||
| 140 | + } | ||
| 141 | +} | ||
| 142 | + | ||
| 143 | +- (void)hideStatusBar { | ||
| 144 | + if (self.shouldHideStatusBar) { | ||
| 145 | + [UIApplication sharedApplication].statusBarHidden = YES; | ||
| 146 | + } | ||
| 147 | +} | ||
| 148 | + | ||
| 149 | +#pragma mark - public | ||
| 150 | + | ||
| 151 | +- (void)show { | ||
| 152 | + [self showToView:[UIApplication sharedApplication].keyWindow]; | ||
| 153 | +} | ||
| 154 | + | ||
| 155 | +- (void)showToView:(UIView *)view { | ||
| 156 | + [self showToView:view containerSize:view.bounds.size]; | ||
| 157 | +} | ||
| 158 | + | ||
| 159 | +- (void)showToView:(UIView *)view containerSize:(CGSize)containerSize { | ||
| 160 | + [self.rotationHandler startObserveStatusBarOrientation]; | ||
| 161 | + | ||
| 162 | + [view addSubview:self]; | ||
| 163 | + self.frame = view.bounds; | ||
| 164 | + self.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; | ||
| 165 | + | ||
| 166 | + _originStatusBarHidden = [UIApplication sharedApplication].isStatusBarHidden; | ||
| 167 | + | ||
| 168 | + [self.rotationHandler configContainerSize:containerSize]; | ||
| 169 | + | ||
| 170 | + [self.dataMediator preloadWithPage:self.currentPage]; | ||
| 171 | + | ||
| 172 | + __kindof UIView *startView; | ||
| 173 | + UIImage *startImage; | ||
| 174 | + CGRect endFrame = CGRectZero; | ||
| 175 | + id<YBIBDataProtocol> data = [self.dataMediator dataForCellAtIndex:self.currentPage]; | ||
| 176 | + if ([data respondsToSelector:@selector(yb_projectiveView)]) { | ||
| 177 | + startView = data.yb_projectiveView; | ||
| 178 | + self.hiddenProjectiveView = startView; | ||
| 179 | + if ([startView isKindOfClass:UIImageView.class]) { | ||
| 180 | + startImage = ((UIImageView *)startView).image; | ||
| 181 | + } else { | ||
| 182 | + startImage = YBIBSnapshotView(startView); | ||
| 183 | + } | ||
| 184 | + } | ||
| 185 | + if ([data respondsToSelector:@selector(yb_imageViewFrameWithContainerSize:imageSize:orientation:)]) { | ||
| 186 | + endFrame = [data yb_imageViewFrameWithContainerSize:self.bounds.size imageSize:startImage.size orientation:self.rotationHandler.currentOrientation]; | ||
| 187 | + } | ||
| 188 | + | ||
| 189 | + [self setTransitioning:YES isShow:YES]; | ||
| 190 | + [self.animatedTransition yb_showTransitioningWithContainer:self startView:startView startImage:startImage endFrame:endFrame orientation:self.rotationHandler.currentOrientation completion:^{ | ||
| 191 | + [self hideStatusBar]; | ||
| 192 | + [self build]; | ||
| 193 | + [self setTransitioning:NO isShow:YES]; | ||
| 194 | + }]; | ||
| 195 | +} | ||
| 196 | + | ||
| 197 | +- (void)hide { | ||
| 198 | + __kindof UIView *startView; | ||
| 199 | + __kindof UIView *endView; | ||
| 200 | + UICollectionViewCell<YBIBCellProtocol> *cell = (UICollectionViewCell<YBIBCellProtocol> *)self.collectionView.centerCell; | ||
| 201 | + if ([cell respondsToSelector:@selector(yb_foregroundView)]) { | ||
| 202 | + startView = cell.yb_foregroundView; | ||
| 203 | + } | ||
| 204 | + if ([cell.yb_cellData respondsToSelector:@selector(yb_projectiveView)]) { | ||
| 205 | + endView = cell.yb_cellData.yb_projectiveView; | ||
| 206 | + } | ||
| 207 | + | ||
| 208 | + for (id<YBIBToolViewHandler> handler in self.toolViewHandlers) { | ||
| 209 | + [handler yb_hide:YES]; | ||
| 210 | + } | ||
| 211 | + [self showStatusBar]; | ||
| 212 | + | ||
| 213 | + [self setTransitioning:YES isShow:NO]; | ||
| 214 | + [self.animatedTransition yb_hideTransitioningWithContainer:self startView:startView endView:endView orientation:self.rotationHandler.currentOrientation completion:^{ | ||
| 215 | + [self rebuild]; | ||
| 216 | + [self removeFromSuperview]; | ||
| 217 | + [self setTransitioning:NO isShow:NO]; | ||
| 218 | + }]; | ||
| 219 | +} | ||
| 220 | + | ||
| 221 | +- (void)reloadData { | ||
| 222 | + [self.dataMediator clear]; | ||
| 223 | + NSInteger page = self.currentPage; | ||
| 224 | + [self.collectionView reloadData]; | ||
| 225 | + self.currentPage = page; | ||
| 226 | +} | ||
| 227 | + | ||
| 228 | +- (id<YBIBDataProtocol>)currentData { | ||
| 229 | + return [self.dataMediator dataForCellAtIndex:self.currentPage]; | ||
| 230 | +} | ||
| 231 | + | ||
| 232 | +#pragma mark - internal | ||
| 233 | + | ||
| 234 | +- (void)setHiddenProjectiveView:(NSObject *)hiddenProjectiveView { | ||
| 235 | + if (_hiddenProjectiveView && [_hiddenProjectiveView respondsToSelector:@selector(setAlpha:)]) { | ||
| 236 | + CGFloat originAlpha = _hiddenProjectiveView.ybib_originAlpha; | ||
| 237 | + if (originAlpha != 1) { | ||
| 238 | + [_hiddenProjectiveView setValue:@(1) forKey:@"alpha"]; | ||
| 239 | + [UIView animateWithDuration:0.2 animations:^{ | ||
| 240 | + [self->_hiddenProjectiveView setValue:@(originAlpha) forKey:@"alpha"]; | ||
| 241 | + }]; | ||
| 242 | + } else { | ||
| 243 | + [_hiddenProjectiveView setValue:@(originAlpha) forKey:@"alpha"]; | ||
| 244 | + } | ||
| 245 | + } | ||
| 246 | + _hiddenProjectiveView = hiddenProjectiveView; | ||
| 247 | + | ||
| 248 | + if (!self.autoHideProjectiveView) return; | ||
| 249 | + | ||
| 250 | + if (hiddenProjectiveView && [hiddenProjectiveView respondsToSelector:@selector(setAlpha:)]) { | ||
| 251 | + hiddenProjectiveView.ybib_originAlpha = ((NSNumber *)[hiddenProjectiveView valueForKey:@"alpha"]).floatValue; | ||
| 252 | + [hiddenProjectiveView setValue:@(0) forKey:@"alpha"]; | ||
| 253 | + } | ||
| 254 | +} | ||
| 255 | + | ||
| 256 | +- (void)implementOperateBrowserProtocol:(id<YBIBOperateBrowserProtocol>)obj { | ||
| 257 | + __weak typeof(self) wSelf = self; | ||
| 258 | + if ([obj respondsToSelector:@selector(setYb_hideBrowser:)]) { | ||
| 259 | + [obj setYb_hideBrowser:^{ | ||
| 260 | + __strong typeof(wSelf) self = wSelf; | ||
| 261 | + if (!self) return; | ||
| 262 | + [self hide]; | ||
| 263 | + }]; | ||
| 264 | + } | ||
| 265 | + if ([obj respondsToSelector:@selector(setYb_hideStatusBar:)]) { | ||
| 266 | + [obj setYb_hideStatusBar:^(BOOL hide) { | ||
| 267 | + __strong typeof(wSelf) self = wSelf; | ||
| 268 | + if (!self) return; | ||
| 269 | + hide ? [self hideStatusBar] : [self showStatusBar]; | ||
| 270 | + }]; | ||
| 271 | + } | ||
| 272 | + if ([obj respondsToSelector:@selector(setYb_hideToolViews:)]) { | ||
| 273 | + [obj setYb_hideToolViews:^(BOOL hide) { | ||
| 274 | + __strong typeof(wSelf) self = wSelf; | ||
| 275 | + if (!self) return; | ||
| 276 | + for (id<YBIBToolViewHandler> handler in self.toolViewHandlers) { | ||
| 277 | + [handler yb_hide:hide]; | ||
| 278 | + } | ||
| 279 | + }]; | ||
| 280 | + } | ||
| 281 | +} | ||
| 282 | + | ||
| 283 | +- (void)implementGetBaseInfoProtocol:(id<YBIBGetBaseInfoProtocol>)obj { | ||
| 284 | + __weak typeof(self) wSelf = self; | ||
| 285 | + if ([obj respondsToSelector:@selector(setYb_currentOrientation:)]) { | ||
| 286 | + [obj setYb_currentOrientation:^UIDeviceOrientation{ | ||
| 287 | + __strong typeof(wSelf) self = wSelf; | ||
| 288 | + if (!self) return UIDeviceOrientationPortrait; | ||
| 289 | + return self.rotationHandler.currentOrientation; | ||
| 290 | + }]; | ||
| 291 | + } | ||
| 292 | + if ([obj respondsToSelector:@selector(setYb_containerSize:)]) { | ||
| 293 | + [obj setYb_containerSize:^CGSize(UIDeviceOrientation orientation) { | ||
| 294 | + __strong typeof(wSelf) self = wSelf; | ||
| 295 | + if (!self) return CGSizeZero; | ||
| 296 | + return [self.rotationHandler containerSizeWithOrientation:orientation]; | ||
| 297 | + }]; | ||
| 298 | + } | ||
| 299 | + if ([obj respondsToSelector:@selector(setYb_auxiliaryViewHandler:)]) { | ||
| 300 | + [obj setYb_auxiliaryViewHandler:^id<YBIBAuxiliaryViewHandler>{ | ||
| 301 | + __strong typeof(wSelf) self = wSelf; | ||
| 302 | + if (!self) return nil; | ||
| 303 | + return self.auxiliaryViewHandler; | ||
| 304 | + }]; | ||
| 305 | + } | ||
| 306 | + if ([obj respondsToSelector:@selector(setYb_webImageMediator:)]) { | ||
| 307 | + [obj setYb_webImageMediator:^id<YBIBWebImageMediator> { | ||
| 308 | + __strong typeof(wSelf) self = wSelf; | ||
| 309 | + if (!self) return nil; | ||
| 310 | + NSAssert(self.webImageMediator, @"'webImageMediator' should not be nil."); | ||
| 311 | + return self.webImageMediator; | ||
| 312 | + }]; | ||
| 313 | + } | ||
| 314 | + if ([obj respondsToSelector:@selector(setYb_currentPage:)]) { | ||
| 315 | + [obj setYb_currentPage:^NSInteger{ | ||
| 316 | + __strong typeof(wSelf) self = wSelf; | ||
| 317 | + if (!self) return 0; | ||
| 318 | + return self.currentPage; | ||
| 319 | + }]; | ||
| 320 | + } | ||
| 321 | + if ([obj respondsToSelector:@selector(setYb_totalPage:)]) { | ||
| 322 | + [obj setYb_totalPage:^NSInteger{ | ||
| 323 | + __strong typeof(wSelf) self = wSelf; | ||
| 324 | + if (!self) return 0; | ||
| 325 | + return [self.dataMediator numberOfCells]; | ||
| 326 | + }]; | ||
| 327 | + } | ||
| 328 | + if ([obj respondsToSelector:@selector(setYb_backView:)]) { | ||
| 329 | + obj.yb_backView = self; | ||
| 330 | + } | ||
| 331 | + if ([obj respondsToSelector:@selector(setYb_containerView:)]) { | ||
| 332 | + obj.yb_containerView = self.containerView; | ||
| 333 | + } | ||
| 334 | + if ([obj respondsToSelector:@selector(setYb_collectionView:)]) { | ||
| 335 | + [obj setYb_collectionView:^__kindof UICollectionView *{ | ||
| 336 | + __strong typeof(wSelf) self = wSelf; | ||
| 337 | + if (!self) return nil; | ||
| 338 | + return self.collectionView; | ||
| 339 | + }]; | ||
| 340 | + } | ||
| 341 | + if ([obj respondsToSelector:@selector(setYb_cellIsInCenter:)]) { | ||
| 342 | + [obj setYb_cellIsInCenter:^BOOL{ | ||
| 343 | + __strong typeof(wSelf) self = wSelf; | ||
| 344 | + CGFloat pageF = self.collectionView.contentOffset.x / self.collectionView.bounds.size.width; | ||
| 345 | + // '0.001' is admissible error. | ||
| 346 | + return ABS(pageF - (NSInteger)pageF) <= 0.001; | ||
| 347 | + }]; | ||
| 348 | + } | ||
| 349 | + if ([obj respondsToSelector:@selector(setYb_isTransitioning:)]) { | ||
| 350 | + [obj setYb_isTransitioning:^BOOL{ | ||
| 351 | + __strong typeof(wSelf) self = wSelf; | ||
| 352 | + if (!self) return NO; | ||
| 353 | + return self.isTransitioning; | ||
| 354 | + }]; | ||
| 355 | + } | ||
| 356 | + if ([obj respondsToSelector:@selector(setYb_isShowTransitioning:)]) { | ||
| 357 | + [obj setYb_isShowTransitioning:^BOOL{ | ||
| 358 | + __strong typeof(wSelf) self = wSelf; | ||
| 359 | + if (!self) return NO; | ||
| 360 | + return self.isShowTransitioning; | ||
| 361 | + }]; | ||
| 362 | + } | ||
| 363 | + if ([obj respondsToSelector:@selector(setYb_isHideTransitioning:)]) { | ||
| 364 | + [obj setYb_isHideTransitioning:^BOOL{ | ||
| 365 | + __strong typeof(wSelf) self = wSelf; | ||
| 366 | + if (!self) return NO; | ||
| 367 | + return self.isHideTransitioning; | ||
| 368 | + }]; | ||
| 369 | + } | ||
| 370 | + if ([obj respondsToSelector:@selector(setYb_isRotating:)]) { | ||
| 371 | + [obj setYb_isRotating:^BOOL{ | ||
| 372 | + __strong typeof(wSelf) self = wSelf; | ||
| 373 | + if (!self) return NO; | ||
| 374 | + return self.rotationHandler.isRotating; | ||
| 375 | + }]; | ||
| 376 | + } | ||
| 377 | +} | ||
| 378 | + | ||
| 379 | +#pragma mark - <UICollectionViewDataSource> | ||
| 380 | + | ||
| 381 | +- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { | ||
| 382 | + return [self.dataMediator numberOfCells]; | ||
| 383 | +} | ||
| 384 | + | ||
| 385 | +- (UICollectionViewCell *)collectionView:(YBIBCollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { | ||
| 386 | + id<YBIBDataProtocol> data = [self.dataMediator dataForCellAtIndex:indexPath.row]; | ||
| 387 | + | ||
| 388 | + UICollectionViewCell<YBIBCellProtocol> *cell = [collectionView dequeueReusableCellWithReuseIdentifier:[collectionView reuseIdentifierForCellClass:data.yb_classOfCell] forIndexPath:indexPath]; | ||
| 389 | + | ||
| 390 | + [self implementGetBaseInfoProtocol:cell]; | ||
| 391 | + [self implementOperateBrowserProtocol:cell]; | ||
| 392 | + | ||
| 393 | + if ([cell respondsToSelector:@selector(setYb_selfPage:)]) { | ||
| 394 | + [cell setYb_selfPage:^NSInteger{ | ||
| 395 | + return indexPath.row; | ||
| 396 | + }]; | ||
| 397 | + } | ||
| 398 | + | ||
| 399 | + cell.yb_cellData = data; | ||
| 400 | + | ||
| 401 | + if ([cell respondsToSelector:@selector(yb_pageChanged)]) { | ||
| 402 | + [cell yb_pageChanged]; | ||
| 403 | + } | ||
| 404 | + | ||
| 405 | + [self.dataMediator preloadWithPage:indexPath.row]; | ||
| 406 | + | ||
| 407 | + return cell; | ||
| 408 | +} | ||
| 409 | + | ||
| 410 | +#pragma mark - <UICollectionViewDelegate> | ||
| 411 | + | ||
| 412 | +- (void)scrollViewDidScroll:(UIScrollView *)scrollView { | ||
| 413 | + CGFloat pageF = scrollView.contentOffset.x / scrollView.bounds.size.width; | ||
| 414 | + NSInteger page = (NSInteger)(pageF + 0.5); | ||
| 415 | + | ||
| 416 | + for (id<YBIBToolViewHandler> handler in self.toolViewHandlers) { | ||
| 417 | + if ([handler respondsToSelector:@selector(yb_offsetXChanged:)]) { | ||
| 418 | + [handler yb_offsetXChanged:pageF]; | ||
| 419 | + } | ||
| 420 | + } | ||
| 421 | + | ||
| 422 | + if (!scrollView.isDecelerating && !scrollView.isDragging) { | ||
| 423 | + // Return if not scrolled by finger. | ||
| 424 | + return; | ||
| 425 | + } | ||
| 426 | + if (page < 0 || page > [self.dataMediator numberOfCells] - 1) return; | ||
| 427 | + if (self.rotationHandler.isRotating) return; | ||
| 428 | + | ||
| 429 | + if (page != _currentPage) { | ||
| 430 | + _currentPage = page; | ||
| 431 | + [self pageNumberChanged]; | ||
| 432 | + } | ||
| 433 | +} | ||
| 434 | + | ||
| 435 | +#pragma mark - event | ||
| 436 | + | ||
| 437 | +- (void)respondsToLongPress:(UILongPressGestureRecognizer *)sender { | ||
| 438 | + if (sender.state == UIGestureRecognizerStateBegan) { | ||
| 439 | + if ([self.delegate respondsToSelector:@selector(yb_imageBrowser:respondsToLongPressWithData:)]) { | ||
| 440 | + [self.delegate yb_imageBrowser:self respondsToLongPressWithData:[self currentData]]; | ||
| 441 | + } else { | ||
| 442 | + for (id<YBIBToolViewHandler> handler in self.toolViewHandlers) { | ||
| 443 | + if ([handler respondsToSelector:@selector(yb_respondsToLongPress)]) { | ||
| 444 | + [handler yb_respondsToLongPress]; | ||
| 445 | + } | ||
| 446 | + } | ||
| 447 | + } | ||
| 448 | + } | ||
| 449 | +} | ||
| 450 | + | ||
| 451 | +#pragma mark - getters & setters | ||
| 452 | + | ||
| 453 | +- (YBIBContainerView *)containerView { | ||
| 454 | + if (!_containerView) { | ||
| 455 | + _containerView = [YBIBContainerView new]; | ||
| 456 | + _containerView.backgroundColor = UIColor.clearColor; | ||
| 457 | + _containerView.layer.masksToBounds = YES; | ||
| 458 | + } | ||
| 459 | + return _containerView; | ||
| 460 | +} | ||
| 461 | + | ||
| 462 | +- (YBIBCollectionView *)collectionView { | ||
| 463 | + if (!_collectionView) { | ||
| 464 | + _collectionView = [YBIBCollectionView new]; | ||
| 465 | + _collectionView.delegate = self; | ||
| 466 | + _collectionView.dataSource = self; | ||
| 467 | + } | ||
| 468 | + return _collectionView; | ||
| 469 | +} | ||
| 470 | +- (void)setCurrentPage:(NSInteger)currentPage { | ||
| 471 | + NSInteger maxPage = self.dataMediator.numberOfCells - 1; | ||
| 472 | + if (currentPage > maxPage) { | ||
| 473 | + currentPage = maxPage; | ||
| 474 | + } | ||
| 475 | + _currentPage = currentPage; | ||
| 476 | + if (self.collectionView.superview) { | ||
| 477 | + [self collectionViewScrollToPage:currentPage]; | ||
| 478 | + } | ||
| 479 | +} | ||
| 480 | +- (void)setDistanceBetweenPages:(CGFloat)distanceBetweenPages { | ||
| 481 | + self.collectionView.layout.distanceBetweenPages = distanceBetweenPages; | ||
| 482 | +} | ||
| 483 | +- (CGFloat)distanceBetweenPages { | ||
| 484 | + return self.collectionView.layout.distanceBetweenPages; | ||
| 485 | +} | ||
| 486 | + | ||
| 487 | +- (void)setTransitioning:(BOOL)transitioning isShow:(BOOL)isShow { | ||
| 488 | + _transitioning = transitioning; | ||
| 489 | + _showTransitioning = transitioning && isShow; | ||
| 490 | + _hideTransitioning = transitioning && !isShow; | ||
| 491 | + | ||
| 492 | + // Make 'self.userInteractionEnabled' always 'YES' to block external interaction. | ||
| 493 | + self.containerView.userInteractionEnabled = !transitioning; | ||
| 494 | + self.collectionView.userInteractionEnabled = !transitioning; | ||
| 495 | + | ||
| 496 | + if (transitioning) { | ||
| 497 | + if ([self.delegate respondsToSelector:@selector(yb_imageBrowser:beginTransitioningWithIsShow:)]) { | ||
| 498 | + [self.delegate yb_imageBrowser:self beginTransitioningWithIsShow:isShow]; | ||
| 499 | + } | ||
| 500 | + } else { | ||
| 501 | + if ([self.delegate respondsToSelector:@selector(yb_imageBrowser:endTransitioningWithIsShow:)]) { | ||
| 502 | + [self.delegate yb_imageBrowser:self endTransitioningWithIsShow:isShow]; | ||
| 503 | + } | ||
| 504 | + } | ||
| 505 | +} | ||
| 506 | + | ||
| 507 | +- (YBIBDataMediator *)dataMediator { | ||
| 508 | + if (!_dataMediator) { | ||
| 509 | + _dataMediator = [[YBIBDataMediator alloc] initWithBrowser:self]; | ||
| 510 | + _dataMediator.dataCacheCountLimit = YBIBLowMemory() ? 9 : 27; | ||
| 511 | + _dataMediator.preloadCount = YBIBLowMemory() ? 0 : 2; | ||
| 512 | + } | ||
| 513 | + return _dataMediator; | ||
| 514 | +} | ||
| 515 | +- (void)setPreloadCount:(NSUInteger)preloadCount { | ||
| 516 | + self.dataMediator.preloadCount = preloadCount; | ||
| 517 | +} | ||
| 518 | +- (NSUInteger)preloadCount { | ||
| 519 | + return self.dataMediator.preloadCount; | ||
| 520 | +} | ||
| 521 | + | ||
| 522 | +- (YBIBScreenRotationHandler *)rotationHandler { | ||
| 523 | + if (!_rotationHandler) { | ||
| 524 | + _rotationHandler = [[YBIBScreenRotationHandler alloc] initWithBrowser:self]; | ||
| 525 | + } | ||
| 526 | + return _rotationHandler; | ||
| 527 | +} | ||
| 528 | +- (void)setSupportedOrientations:(UIInterfaceOrientationMask)supportedOrientations { | ||
| 529 | + self.rotationHandler.supportedOrientations = supportedOrientations; | ||
| 530 | +} | ||
| 531 | +- (UIInterfaceOrientationMask)supportedOrientations { | ||
| 532 | + return self.rotationHandler.supportedOrientations; | ||
| 533 | +} | ||
| 534 | +- (UIDeviceOrientation)currentOrientation { | ||
| 535 | + return self.rotationHandler.currentOrientation; | ||
| 536 | +} | ||
| 537 | + | ||
| 538 | +@end |
Example/CNLiveOrganizationValidationModule.xcodeproj/project.pbxproj
| @@ -386,8 +386,6 @@ | @@ -386,8 +386,6 @@ | ||
| 386 | "${BUILT_PRODUCTS_DIR}/SGPagingView/SGPagingView.framework", | 386 | "${BUILT_PRODUCTS_DIR}/SGPagingView/SGPagingView.framework", |
| 387 | "${BUILT_PRODUCTS_DIR}/SSZipArchive/SSZipArchive.framework", | 387 | "${BUILT_PRODUCTS_DIR}/SSZipArchive/SSZipArchive.framework", |
| 388 | "${BUILT_PRODUCTS_DIR}/WebViewJavascriptBridge/WebViewJavascriptBridge.framework", | 388 | "${BUILT_PRODUCTS_DIR}/WebViewJavascriptBridge/WebViewJavascriptBridge.framework", |
| 389 | - "${BUILT_PRODUCTS_DIR}/YBImageBrowser/YBImageBrowser.framework", | ||
| 390 | - "${BUILT_PRODUCTS_DIR}/YYImage/YYImage.framework", | ||
| 391 | "${BUILT_PRODUCTS_DIR}/YYKit/YYKit.framework", | 389 | "${BUILT_PRODUCTS_DIR}/YYKit/YYKit.framework", |
| 392 | "${BUILT_PRODUCTS_DIR}/libksygpulive_ks3/libksygpulive.framework", | 390 | "${BUILT_PRODUCTS_DIR}/libksygpulive_ks3/libksygpulive.framework", |
| 393 | "${BUILT_PRODUCTS_DIR}/lottie-ios/Lottie.framework", | 391 | "${BUILT_PRODUCTS_DIR}/lottie-ios/Lottie.framework", |
| @@ -430,8 +428,6 @@ | @@ -430,8 +428,6 @@ | ||
| 430 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SGPagingView.framework", | 428 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SGPagingView.framework", |
| 431 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SSZipArchive.framework", | 429 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SSZipArchive.framework", |
| 432 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/WebViewJavascriptBridge.framework", | 430 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/WebViewJavascriptBridge.framework", |
| 433 | - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/YBImageBrowser.framework", | ||
| 434 | - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/YYImage.framework", | ||
| 435 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/YYKit.framework", | 431 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/YYKit.framework", |
| 436 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/libksygpulive.framework", | 432 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/libksygpulive.framework", |
| 437 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Lottie.framework", | 433 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Lottie.framework", |