提交
21
Pods/YBImageBrowser/LICENSE
generated
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2019 杨波
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
229
Pods/YBImageBrowser/README.md
generated
Normal file
@@ -0,0 +1,229 @@
|
||||

|
||||
|
||||
[](https://cocoapods.org/pods/YBImageBrowser)
|
||||
[](https://github.com/indulgeIn/YBImageBrowser)
|
||||
[](https://github.com/indulgeIn/YBImageBrowser)
|
||||
|
||||
**iOS 图片浏览器,功能强大,易于拓展,性能优化和内存控制让其运行更加的流畅和稳健。**
|
||||
|
||||
##### 相关文章:
|
||||
##### [YBImageBrowser 重构心得:如何优化架构、性能、内存?](https://www.jianshu.com/p/ef53d0094437)
|
||||
##### [避免 iOS 组件依赖冲突的小技巧](https://www.jianshu.com/p/0e3283275300)
|
||||
|
||||
|
||||
## 注意事项
|
||||
|
||||
#### 关于 3.x 版本 (使用 2.x 版本请切换到 store_2.x 分支)
|
||||
|
||||
为了彻底解决 2.x 版本的设计缺陷和代码漏洞,特花费大量业余时间进行了 3.x 深度重构,所以没办法做到向下兼容,希望社区朋友们能体谅,根据情况进行版本迁移。
|
||||
3.x 版本有着更科学的架构,更极致的性能提升,更严格的内存控制,使用起来会更得心应手,也便于将来的迭代优化。
|
||||
|
||||
#### 提问须知
|
||||
|
||||
考虑到笔者的精力问题,遇到问题请先查看 API、效仿 Demo、阅读 README、搜索 Issues。请不要提出与组件无关的问题,比如 CocoaPods 的错误,如果是 BUG 或 Feature 最好是提 Issue。
|
||||
|
||||
# 目录
|
||||
|
||||
* [预览](#预览)
|
||||
* [特性](#特性)
|
||||
* [安装](#安装)
|
||||
* [用法](#用法)
|
||||
* [常见问题](#常见问题)
|
||||
|
||||
|
||||
# 预览
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
# 特性
|
||||
|
||||
- 支持 GIF,APNG,WebP 等本地和网络图片类型(由 YYImage、SDWebImage 提供支持)。
|
||||
- 支持系统相册图片和视频。
|
||||
- 支持简单的视频播放。
|
||||
- 支持高清图浏览。
|
||||
- 支持图片预处理(比如添加水印)。
|
||||
- 支持根据图片的大小判断是否需要预先解码(精确控制内存)。
|
||||
- 支持图片压缩、裁剪的界限设定。
|
||||
- 支持修改下载图片的 NSURLRequest。
|
||||
- 支持主动旋转或跟随控制器旋转。
|
||||
- 支持自定义图标。
|
||||
- 支持自定义 Toast/Loading。
|
||||
- 支持自定义文案(默认提供中文和英文)。
|
||||
- 支持自定义工具视图(比如查看原图功能)。
|
||||
- 支持自定义 Cell(比如添加一个广告模块)。
|
||||
- 支持添加到其它父视图上使用(比如加到控制器上)。
|
||||
- 支持转场动效、图片布局等深度定制。
|
||||
- 支持数据重载、局部更新。
|
||||
- 支持低粒度的内存控制和性能调优。
|
||||
- 极致的性能优化和严格的内存控制让其运行更加的流畅和稳健。
|
||||
|
||||
|
||||
# 安装
|
||||
|
||||
## CocoaPods
|
||||
|
||||
支持分库导入,核心部分就是图片浏览功能,视频播放作为拓展功能按需导入。
|
||||
|
||||
1. 在 Podfile 中添加:
|
||||
```
|
||||
pod 'YBImageBrowser'
|
||||
pod 'YBImageBrowser/Video' //视频功能需添加
|
||||
```
|
||||
2. 执行 `pod install` 或 `pod update`。
|
||||
3. 导入 `<YBImageBrowser/YBImageBrowser.h>`,视频功能需导入`<YBImageBrowser/YBIBVideoData.h>`。
|
||||
4. 注意:如果你需要支持 WebP,可以在 Podfile 中添加 `pod 'YYImage/WebP'`。
|
||||
|
||||
若搜索不到库,可执行`pod repo update`,或使用 `rm ~/Library/Caches/CocoaPods/search_index.json` 移除本地索引然后再执行安装,或更新一下 CocoaPods 版本。
|
||||
|
||||
#### 去除 SDWebImage 的依赖(版本需 >= 3.0.4)
|
||||
|
||||
Podfile 相应的配置变为:
|
||||
```
|
||||
pod 'YBImageBrowser/NOSD'
|
||||
pod 'YBImageBrowser/VideoNOSD' //视频功能需添加
|
||||
```
|
||||
这时你必须定义一个类实现`YBIBWebImageMediator`协议,并赋值给`YBImageBrowser`类的`webImageMediator`属性(可以参考 `YBIBDefaultWebImageMediator`的实现)。
|
||||
|
||||
|
||||
## 手动导入
|
||||
|
||||
1. 下载 YBImageBrowser 文件夹所有内容并且拖入你的工程中,视频功能还需下载 Video 文件夹所有内容。
|
||||
2. 链接以下 frameworks:
|
||||
* SDWebImage
|
||||
* YYImage
|
||||
3. 导入 `YBImageBrowser.h`,视频功能需导入`YBIBVideoData.h`
|
||||
4. 注意:如果你需要支持 WebP,可以在 Podfile 中添加 `pod 'YYImage/WebP'`,或者到手动下载 [YYImage 仓库](https://github.com/ibireme/YYImage) 的 webP 支持文件。
|
||||
|
||||
|
||||
|
||||
|
||||
# 用法
|
||||
|
||||
初始化`YBImageBrowser`并且赋值数据源`id<YBIBDataProtocol>`,默认提供`YBIBImageData` (图片) 和`YBIBVideoData` (视频) 两种数据源。
|
||||
|
||||
图片处理是组件的核心,笔者精力有限,视频播放做得很轻量,若有更高的要求最好是自定义 Cell,望体谅。
|
||||
|
||||
Demo 中提供了很多示例代码,演示较复杂的拓展方式,所以若需要深度定制最好是下载 Demo 查看。
|
||||
|
||||
建议不对`YBImageBrowser`进行复用,目前还存在一些逻辑漏洞。
|
||||
|
||||
|
||||
## 基本使用
|
||||
|
||||
```
|
||||
// 本地图片
|
||||
YBIBImageData *data0 = [YBIBImageData new];
|
||||
data0.imageName = ...;
|
||||
data0.projectiveView = ...;
|
||||
|
||||
// 网络图片
|
||||
YBIBImageData *data1 = [YBIBImageData new];
|
||||
data1.imageURL = ...;
|
||||
data1.projectiveView = ...;
|
||||
|
||||
// 视频
|
||||
YBIBVideoData *data2 = [YBIBVideoData new];
|
||||
data2.videoURL = ...;
|
||||
data2.projectiveView = ...;
|
||||
|
||||
YBImageBrowser *browser = [YBImageBrowser new];
|
||||
browser.dataSourceArray = @[data0, data1, data2];
|
||||
browser.currentPage = ...;
|
||||
[browser show];
|
||||
```
|
||||
|
||||
|
||||
## 设置支持的旋转方向
|
||||
|
||||
当图片浏览器依托的 UIViewController 仅支持一个方向:
|
||||
|
||||
这种情况通过`YBImageBrowser.new.supportedOrientations`设置图片浏览器支持的旋转方向。
|
||||
|
||||
否则:
|
||||
|
||||
上面的属性将失效,图片浏览器会跟随控制器的旋转而旋转,由于各种原因这种情况的旋转过渡有瑕疵,建议不使用这种方式。
|
||||
|
||||
|
||||
## 自定义图标
|
||||
|
||||
修改`YBIBIconManager.sharedManager`实例的属性。
|
||||
|
||||
|
||||
## 自定义文案
|
||||
|
||||
修改`YBIBCopywriter.sharedCopywriter`实例的属性。
|
||||
|
||||
|
||||
## 自定义 Toast / Loading
|
||||
|
||||
实现`YBIBAuxiliaryViewHandler`协议,并且赋值给`YBImageBrowser.new.auxiliaryViewHandler`属性,可参考和协议同名的默认实现类。
|
||||
|
||||
|
||||
## 自定义工具视图(ToolView)
|
||||
|
||||
默认实现的`YBImageBrowser.new.defaultToolViewHandler`处理器可以做一些属性配置,当满足不了业务需求时,最好是进行自定义,参考默认实现或 Demo 中“查看原图”功能实现。
|
||||
|
||||
定义一个或多个类实现`YBIBToolViewHandler`协议,并且装入`YBImageBrowser.new.toolViewHandlers`数组属性。建议使用一个中介者来实现这个协议,然后所有的工具视图都由这个中介者来管理,当然也可以让每一个自定义的工具 UIView 都实现`YBIBToolViewHandler`协议,请根据具体需求取舍。
|
||||
|
||||
|
||||
## 自定义 Cell
|
||||
|
||||
当默认提供的`YBIBImageData` (图片) 和`YBIBVideoData` (视频) 满足不了需求时,可自定义拓展 Cell,参考默认实现或 Demo 中的示例代码。
|
||||
|
||||
定义一个实现`YBIBCellProtocol`协议的`UICollectionViewCell`类和一个实现`YBIBDataProtocol`协议的数据类,当要求不高时实现必选协议方法就能跑起来了,若对交互有要求就相对比较复杂,最好是参考默认的交互动效实现。
|
||||
|
||||
在某些场景下,甚至可以直接继承项目中的 Cell 来做自定义。
|
||||
|
||||
|
||||
# 常见问题
|
||||
|
||||
## SDWebImage Pods 版本兼容问题
|
||||
|
||||
SDWebImage 有两种情况会出现兼容问题:该库对 SDWebImage 采用模糊向上依赖,但将来 SDWebImage 可能没做好向下兼容;当其它库依赖 SDWebImage 更低或更高 API 不兼容版本。对于这种情况,可以尝试以下方式解决:
|
||||
- Podfile 中采用去除 SDWebImage 依赖的方式导入,只需要实现一个中介者(见[安装](#安装)部分)。
|
||||
- 更改其它库对 SDWebImage 的依赖版本。
|
||||
- 手动导入 YBImageBrowser,然后修改`YBIBDefaultWebImageMediator`文件。
|
||||
|
||||
为什么不去除依赖 SDWebImage 自己实现?时间成本太高。
|
||||
为什么不拖入 SDWebImage 修改类名?会扩大组件的体积,若外部有 SDWebImage 就存在一份多余代码。
|
||||
|
||||
## 依赖的 YYImage 与项目依赖的 YYKit 冲突
|
||||
|
||||
实际上 YYKit 有把各个组件拆分出来,建议项目中分开导入:
|
||||
```
|
||||
pod 'YYModel'
|
||||
pod 'YYCache'
|
||||
pod 'YYImage'
|
||||
pod 'YYWebImage'
|
||||
pod 'YYText'
|
||||
...
|
||||
```
|
||||
而且这样更灵活便于取舍。
|
||||
|
||||
## 低内存设备 OOM 问题
|
||||
|
||||
组件内部会降低在低内存设备上的性能,减小内存占用,但若高清图过多,可能需要手动去控制(以下是硬件消耗很低的状态):
|
||||
|
||||
```
|
||||
YBIBImageData *data = YBIBImageData.new;
|
||||
// 取消预解码
|
||||
data.shouldPreDecodeAsync = NO;
|
||||
// 直接设大触发裁剪比例,绘制更小的裁剪区域压力更小,不过可能会让用户感觉奇怪,放很大才开始裁剪显示高清局部(这个属性很多时候不需要显式设置,内部会动态计算)
|
||||
data.cuttingZoomScale = 10;
|
||||
|
||||
YBImageBrowser *browser = YBImageBrowser.new;
|
||||
// 调低图片的缓存数量
|
||||
browser.ybib_imageCache.imageCacheCountLimit = 1;
|
||||
// 预加载数量设为 0
|
||||
browser.preloadCount = 0;
|
||||
```
|
||||
|
||||
## 视频播放功能简陋
|
||||
|
||||
关于大家提的关于视频的需求,有些成本过高,笔者精力有限望体谅。若组件默认的视频播放器满足不了需求,就自定义一个 Cell 吧,把成熟的播放器集成到组件中肯定更加的稳定。
|
||||
|
||||
## 关于 Swift 版本
|
||||
|
||||
考虑时间成本,目前没有写 Swift 版本的计划。
|
||||
44
Pods/YBImageBrowser/Video/YBIBVideoActionBar.h
generated
Normal file
@@ -0,0 +1,44 @@
|
||||
//
|
||||
// YBIBVideoActionBar.h
|
||||
// YBImageBrowserDemo
|
||||
//
|
||||
// Created by 波儿菜 on 2019/7/11.
|
||||
// Copyright © 2019 杨波. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class YBIBVideoActionBar;
|
||||
|
||||
@protocol YBIBVideoActionBarDelegate <NSObject>
|
||||
@required
|
||||
|
||||
- (void)yb_videoActionBar:(YBIBVideoActionBar *)actionBar clickPlayButton:(UIButton *)playButton;
|
||||
|
||||
- (void)yb_videoActionBar:(YBIBVideoActionBar *)actionBar clickPauseButton:(UIButton *)pauseButton;
|
||||
|
||||
- (void)yb_videoActionBar:(YBIBVideoActionBar *)actionBar changeValue:(float)value;
|
||||
|
||||
@end
|
||||
|
||||
@interface YBIBVideoActionBar : UIView
|
||||
|
||||
@property (nonatomic, weak) id<YBIBVideoActionBarDelegate> delegate;
|
||||
|
||||
- (void)setMaxValue:(float)value;
|
||||
|
||||
- (void)setCurrentValue:(float)value;
|
||||
|
||||
- (void)pause;
|
||||
|
||||
- (void)play;
|
||||
|
||||
+ (CGFloat)defaultHeight;
|
||||
|
||||
@property (nonatomic, assign, readonly) BOOL isTouchInside;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
191
Pods/YBImageBrowser/Video/YBIBVideoActionBar.m
generated
Normal file
@@ -0,0 +1,191 @@
|
||||
//
|
||||
// YBIBVideoActionBar.m
|
||||
// YBImageBrowserDemo
|
||||
//
|
||||
// Created by 波儿菜 on 2019/7/11.
|
||||
// Copyright © 2019 杨波. All rights reserved.
|
||||
//
|
||||
|
||||
#import "YBIBVideoActionBar.h"
|
||||
#import "YBIBIconManager.h"
|
||||
|
||||
|
||||
@interface YBVideoBrowseActionSlider : UISlider
|
||||
@end
|
||||
@implementation YBVideoBrowseActionSlider
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
if (self) {
|
||||
[self setThumbImage:YBIBIconManager.sharedManager.videoDragCircleImage() forState:UIControlStateNormal];
|
||||
self.minimumTrackTintColor = UIColor.whiteColor;
|
||||
self.maximumTrackTintColor = [UIColor.whiteColor colorWithAlphaComponent:0.5];
|
||||
self.layer.shadowColor = UIColor.darkGrayColor.CGColor;
|
||||
self.layer.shadowOffset = CGSizeMake(0, 1);
|
||||
self.layer.shadowOpacity = 1;
|
||||
self.layer.shadowRadius = 4;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
- (CGRect)trackRectForBounds:(CGRect)bounds {
|
||||
CGRect frame = [super trackRectForBounds:bounds];
|
||||
return CGRectMake(frame.origin.x, frame.origin.y, frame.size.width, 2);
|
||||
}
|
||||
- (CGRect)thumbRectForBounds:(CGRect)bounds trackRect:(CGRect)rect value:(float)value {
|
||||
CGRect frame = [super thumbRectForBounds:bounds trackRect:rect value:value];
|
||||
return CGRectMake(frame.origin.x - 10, frame.origin.y - 10, frame.size.width + 20, frame.size.height + 20);
|
||||
}
|
||||
@end
|
||||
|
||||
|
||||
@interface YBIBVideoActionBar ()
|
||||
@property (nonatomic, strong) UIButton *playButton;
|
||||
@property (nonatomic, strong) UILabel *preTimeLabel;
|
||||
@property (nonatomic, strong) UILabel *sufTimeLabel;
|
||||
@property (nonatomic, strong) YBVideoBrowseActionSlider *slider;
|
||||
@end
|
||||
|
||||
@implementation YBIBVideoActionBar {
|
||||
BOOL _dragging;
|
||||
}
|
||||
|
||||
#pragma mark - life cycle
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
if (self) {
|
||||
_dragging = NO;
|
||||
[self addSubview:self.playButton];
|
||||
[self addSubview:self.preTimeLabel];
|
||||
[self addSubview:self.sufTimeLabel];
|
||||
[self addSubview:self.slider];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)layoutSubviews {
|
||||
[super layoutSubviews];
|
||||
CGFloat width = self.bounds.size.width, height = self.bounds.size.height, labelWidth = 55, buttonWidth = 44, labelOffset = 10;
|
||||
CGFloat imageWidth = YBIBIconManager.sharedManager.videoPlayImage().size.width;
|
||||
CGFloat offset = (buttonWidth - imageWidth) * 0.5;
|
||||
|
||||
self.playButton.frame = CGRectMake(10, 0, buttonWidth, height);
|
||||
self.preTimeLabel.frame = CGRectMake(CGRectGetMaxX(self.playButton.frame) + labelOffset - offset, 0, labelWidth, height);
|
||||
self.sufTimeLabel.frame = CGRectMake(width - labelWidth - labelOffset, 0, labelWidth, height);
|
||||
self.slider.frame = CGRectMake(CGRectGetMaxX(self.preTimeLabel.frame), 0, CGRectGetMinX(self.sufTimeLabel.frame) - CGRectGetMaxX(self.preTimeLabel.frame), height);
|
||||
}
|
||||
|
||||
#pragma mark - public
|
||||
|
||||
+ (CGFloat)defaultHeight {
|
||||
return 44;
|
||||
}
|
||||
|
||||
- (void)setMaxValue:(float)value {
|
||||
self.slider.maximumValue = value;
|
||||
self.sufTimeLabel.attributedText = [self.class timeformatFromSeconds:value];
|
||||
}
|
||||
|
||||
- (void)setCurrentValue:(float)value {
|
||||
if (!_dragging) {
|
||||
[self.slider setValue:value animated:YES];
|
||||
}
|
||||
self.preTimeLabel.attributedText = [self.class timeformatFromSeconds:value];
|
||||
}
|
||||
|
||||
- (void)pause {
|
||||
self.playButton.selected = NO;
|
||||
}
|
||||
|
||||
- (void)play {
|
||||
_dragging = NO;
|
||||
self.playButton.selected = YES;
|
||||
self.slider.userInteractionEnabled = YES;
|
||||
}
|
||||
|
||||
#pragma mark - private
|
||||
|
||||
+ (NSAttributedString *)timeformatFromSeconds:(NSInteger)seconds {
|
||||
NSInteger hour = seconds / 3600, min = (seconds % 3600) / 60, sec = seconds % 60;
|
||||
NSString *text = seconds > 3600 ? [NSString stringWithFormat:@"%02ld:%02ld:%02ld", (long)hour, (long)min, (long)sec] : [NSString stringWithFormat:@"%02ld:%02ld", (long)min, (long)sec];
|
||||
|
||||
NSShadow *shadow = [NSShadow new];
|
||||
shadow.shadowBlurRadius = 4;
|
||||
shadow.shadowOffset = CGSizeMake(0, 1);
|
||||
shadow.shadowColor = UIColor.darkGrayColor;
|
||||
NSAttributedString *attr = [[NSMutableAttributedString alloc] initWithString:text attributes:@{NSShadowAttributeName:shadow, NSFontAttributeName:[UIFont boldSystemFontOfSize:11]}];
|
||||
return attr;
|
||||
}
|
||||
|
||||
#pragma mark - touch event
|
||||
|
||||
- (void)clickPlayButton:(UIButton *)button {
|
||||
button.userInteractionEnabled = NO;
|
||||
if (button.selected) {
|
||||
[self.delegate yb_videoActionBar:self clickPauseButton:button];
|
||||
} else {
|
||||
[self.delegate yb_videoActionBar:self clickPlayButton:button];
|
||||
}
|
||||
button.userInteractionEnabled = YES;
|
||||
}
|
||||
|
||||
- (void)respondsToSliderTouchFinished:(UISlider *)slider {
|
||||
[self.delegate yb_videoActionBar:self changeValue:slider.value];
|
||||
}
|
||||
|
||||
- (void)respondsToSliderTouchDown:(UISlider *)slider {
|
||||
_dragging = YES;
|
||||
slider.userInteractionEnabled = NO;
|
||||
}
|
||||
|
||||
#pragma mark - getters
|
||||
|
||||
- (UIButton *)playButton {
|
||||
if (!_playButton) {
|
||||
_playButton = [UIButton buttonWithType:UIButtonTypeCustom];
|
||||
[_playButton setImage:YBIBIconManager.sharedManager.videoPlayImage() forState:UIControlStateNormal];
|
||||
[_playButton setImage:YBIBIconManager.sharedManager.videoPauseImage() forState:UIControlStateSelected];
|
||||
[_playButton addTarget:self action:@selector(clickPlayButton:) forControlEvents:UIControlEventTouchUpInside];
|
||||
_playButton.layer.shadowColor = UIColor.darkGrayColor.CGColor;
|
||||
_playButton.layer.shadowOffset = CGSizeMake(0, 1);
|
||||
_playButton.layer.shadowOpacity = 1;
|
||||
_playButton.layer.shadowRadius = 4;
|
||||
}
|
||||
return _playButton;
|
||||
}
|
||||
|
||||
- (UILabel *)preTimeLabel {
|
||||
if (!_preTimeLabel) {
|
||||
_preTimeLabel = [UILabel new];
|
||||
_preTimeLabel.attributedText = [self.class timeformatFromSeconds:0];
|
||||
_preTimeLabel.adjustsFontSizeToFitWidth = YES;
|
||||
_preTimeLabel.textAlignment = NSTextAlignmentCenter;
|
||||
_preTimeLabel.textColor = [UIColor.whiteColor colorWithAlphaComponent:0.9];
|
||||
}
|
||||
return _preTimeLabel;
|
||||
}
|
||||
|
||||
- (UILabel *)sufTimeLabel {
|
||||
if (!_sufTimeLabel) {
|
||||
_sufTimeLabel = [UILabel new];
|
||||
_sufTimeLabel.attributedText = [self.class timeformatFromSeconds:0];
|
||||
_sufTimeLabel.adjustsFontSizeToFitWidth = YES;
|
||||
_sufTimeLabel.textAlignment = NSTextAlignmentCenter;
|
||||
_sufTimeLabel.textColor = [UIColor.whiteColor colorWithAlphaComponent:0.9];
|
||||
}
|
||||
return _sufTimeLabel;
|
||||
}
|
||||
|
||||
- (YBVideoBrowseActionSlider *)slider {
|
||||
if (!_slider) {
|
||||
_slider = [YBVideoBrowseActionSlider new];
|
||||
[_slider addTarget:self action:@selector(respondsToSliderTouchFinished:) forControlEvents:UIControlEventTouchUpInside|UIControlEventTouchCancel|UIControlEventTouchUpOutside];
|
||||
[_slider addTarget:self action:@selector(respondsToSliderTouchDown:) forControlEvents:UIControlEventTouchDown];
|
||||
}
|
||||
return _slider;
|
||||
}
|
||||
|
||||
- (BOOL)isTouchInside {
|
||||
return self.slider.isTouchInside;
|
||||
}
|
||||
|
||||
@end
|
||||
20
Pods/YBImageBrowser/Video/YBIBVideoCell+Internal.h
generated
Normal file
@@ -0,0 +1,20 @@
|
||||
//
|
||||
// YBIBVideoCell+Internal.h
|
||||
// YBImageBrowserDemo
|
||||
//
|
||||
// Created by 波儿菜 on 2019/12/23.
|
||||
// Copyright © 2019 杨波. All rights reserved.
|
||||
//
|
||||
|
||||
#import "YBIBVideoCell.h"
|
||||
#import "YBIBVideoView.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface YBIBVideoCell ()
|
||||
|
||||
@property (nonatomic, strong) YBIBVideoView *videoView;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
17
Pods/YBImageBrowser/Video/YBIBVideoCell.h
generated
Normal file
@@ -0,0 +1,17 @@
|
||||
//
|
||||
// YBIBVideoCell.h
|
||||
// YBImageBrowserDemo
|
||||
//
|
||||
// Created by 波儿菜 on 2019/7/10.
|
||||
// Copyright © 2019 杨波. All rights reserved.
|
||||
//
|
||||
|
||||
#import "YBIBCellProtocol.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface YBIBVideoCell : UICollectionViewCell <YBIBCellProtocol>
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
448
Pods/YBImageBrowser/Video/YBIBVideoCell.m
generated
Normal file
@@ -0,0 +1,448 @@
|
||||
//
|
||||
// YBIBVideoCell.m
|
||||
// YBImageBrowserDemo
|
||||
//
|
||||
// Created by 波儿菜 on 2019/7/10.
|
||||
// Copyright © 2019 杨波. All rights reserved.
|
||||
//
|
||||
|
||||
#import "YBIBVideoCell.h"
|
||||
#import "YBIBVideoData.h"
|
||||
#import "YBIBVideoData+Internal.h"
|
||||
#import "YBIBCopywriter.h"
|
||||
#import "YBIBIconManager.h"
|
||||
#import <objc/runtime.h>
|
||||
#import "YBIBVideoCell+Internal.h"
|
||||
|
||||
@interface NSObject (YBIBVideoPlayingRecord)
|
||||
- (void)ybib_videoPlayingAdd:(NSObject *)obj;
|
||||
- (void)ybib_videoPlayingRemove:(NSObject *)obj;
|
||||
- (BOOL)ybib_noVideoPlaying;
|
||||
@end
|
||||
@implementation NSObject (YBIBVideoPlayingRecord)
|
||||
- (NSMutableSet *)ybib_videoPlayingSet {
|
||||
static void *kRecordKey = &kRecordKey;
|
||||
NSMutableSet *set = objc_getAssociatedObject(self, kRecordKey);
|
||||
if (!set) {
|
||||
set = [NSMutableSet set];
|
||||
objc_setAssociatedObject(self, kRecordKey, set, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
||||
}
|
||||
return set;
|
||||
}
|
||||
- (void)ybib_videoPlayingAdd:(NSObject *)obj {
|
||||
[[self ybib_videoPlayingSet] addObject:[NSString stringWithFormat:@"%p", obj]];
|
||||
}
|
||||
- (void)ybib_videoPlayingRemove:(NSObject *)obj {
|
||||
[[self ybib_videoPlayingSet] removeObject:[NSString stringWithFormat:@"%p", obj]];
|
||||
}
|
||||
- (BOOL)ybib_noVideoPlaying {
|
||||
return [self ybib_videoPlayingSet].count == 0;
|
||||
}
|
||||
@end
|
||||
|
||||
|
||||
@interface YBIBVideoCell () <YBIBVideoDataDelegate, YBIBVideoViewDelegate, UIGestureRecognizerDelegate>
|
||||
@end
|
||||
|
||||
@implementation YBIBVideoCell {
|
||||
CGPoint _interactStartPoint;
|
||||
BOOL _interacting;
|
||||
}
|
||||
|
||||
#pragma mark - life cycle
|
||||
|
||||
- (void)dealloc {
|
||||
[self.yb_backView ybib_videoPlayingRemove:self];
|
||||
}
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
if (self) {
|
||||
[self initValue];
|
||||
[self.contentView addSubview:self.videoView];
|
||||
[self addGesture];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)layoutSubviews {
|
||||
[super layoutSubviews];
|
||||
self.videoView.frame = self.bounds;
|
||||
}
|
||||
|
||||
- (void)initValue {
|
||||
_interactStartPoint = CGPointZero;
|
||||
_interacting = NO;
|
||||
}
|
||||
|
||||
- (void)prepareForReuse {
|
||||
((YBIBVideoData *)self.yb_cellData).delegate = nil;
|
||||
self.videoView.thumbImageView.image = nil;
|
||||
[self hideAuxiliaryView];
|
||||
[self.videoView reset];
|
||||
self.videoView.asset = nil;
|
||||
[super prepareForReuse];
|
||||
}
|
||||
|
||||
#pragma mark - private
|
||||
|
||||
- (void)hideAuxiliaryView {
|
||||
[self.yb_auxiliaryViewHandler() yb_hideLoadingWithContainer:self];
|
||||
[self.yb_auxiliaryViewHandler() yb_hideToastWithContainer:self];
|
||||
}
|
||||
|
||||
- (void)updateImageLayoutWithOrientation:(UIDeviceOrientation)orientation previousImageSize:(CGSize)previousImageSize {
|
||||
YBIBVideoData *data = self.yb_cellData;
|
||||
UIImage *image = self.videoView.thumbImageView.image;
|
||||
CGSize imageSize = image.size;
|
||||
|
||||
CGRect imageViewFrame = [data yb_imageViewFrameWithContainerSize:self.yb_containerSize(orientation) imageSize:imageSize orientation:orientation];
|
||||
|
||||
CGFloat scale;
|
||||
if (previousImageSize.width > 0 && previousImageSize.height > 0) {
|
||||
scale = imageSize.width / imageSize.height - previousImageSize.width / previousImageSize.height;
|
||||
} else {
|
||||
scale = 0;
|
||||
}
|
||||
// '0.001' is admissible error.
|
||||
if (ABS(scale) <= 0.001) {
|
||||
self.videoView.thumbImageView.frame = imageViewFrame;
|
||||
} else {
|
||||
[UIView animateWithDuration:0.25 animations:^{
|
||||
self.videoView.thumbImageView.frame = imageViewFrame;
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)hideBrowser {
|
||||
((YBIBVideoData *)self.yb_cellData).delegate = nil;
|
||||
self.videoView.thumbImageView.hidden = NO;
|
||||
self.videoView.autoPlayCount = 0;
|
||||
[self.videoView reset];
|
||||
[self.videoView hideToolBar:YES];
|
||||
[self.videoView hidePlayButton];
|
||||
self.yb_hideBrowser();
|
||||
_interacting = NO;
|
||||
}
|
||||
|
||||
- (void)hideToolViews:(BOOL)hide {
|
||||
if (hide) {
|
||||
self.yb_hideToolViews(YES);
|
||||
} else {
|
||||
if ([self.yb_backView ybib_noVideoPlaying]) {
|
||||
self.yb_hideToolViews(NO);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - <YBIBCellProtocol>
|
||||
|
||||
@synthesize yb_currentOrientation = _yb_currentOrientation;
|
||||
@synthesize yb_containerSize = _yb_containerSize;
|
||||
@synthesize yb_backView = _yb_backView;
|
||||
@synthesize yb_collectionView = _yb_collectionView;
|
||||
@synthesize yb_isTransitioning = _yb_isTransitioning;
|
||||
@synthesize yb_auxiliaryViewHandler = _yb_auxiliaryViewHandler;
|
||||
@synthesize yb_hideStatusBar = _yb_hideStatusBar;
|
||||
@synthesize yb_hideBrowser = _yb_hideBrowser;
|
||||
@synthesize yb_hideToolViews = _yb_hideToolViews;
|
||||
@synthesize yb_cellData = _yb_cellData;
|
||||
@synthesize yb_currentPage = _yb_currentPage;
|
||||
@synthesize yb_selfPage = _yb_selfPage;
|
||||
@synthesize yb_cellIsInCenter = _yb_cellIsInCenter;
|
||||
@synthesize yb_isRotating = _yb_isRotating;
|
||||
|
||||
- (void)setYb_cellData:(id<YBIBDataProtocol>)yb_cellData {
|
||||
_yb_cellData = yb_cellData;
|
||||
YBIBVideoData *data = (YBIBVideoData *)yb_cellData;
|
||||
data.delegate = self;
|
||||
|
||||
UIDeviceOrientation orientation = self.yb_currentOrientation();
|
||||
CGSize containerSize = self.yb_containerSize(orientation);
|
||||
[self.videoView updateLayoutWithExpectOrientation:orientation containerSize:containerSize];
|
||||
self.videoView.autoPlayCount = data.autoPlayCount;
|
||||
self.videoView.topBar.cancelButton.hidden = data.shouldHideForkButton;
|
||||
}
|
||||
|
||||
- (void)yb_orientationWillChangeWithExpectOrientation:(UIDeviceOrientation)orientation {
|
||||
if (_interacting) [self restoreGestureInteractionWithDuration:0];
|
||||
}
|
||||
|
||||
- (void)yb_orientationChangeAnimationWithExpectOrientation:(UIDeviceOrientation)orientation {
|
||||
[self updateImageLayoutWithOrientation:orientation previousImageSize:self.videoView.thumbImageView.image.size];
|
||||
CGSize containerSize = self.yb_containerSize(orientation);
|
||||
[self.videoView updateLayoutWithExpectOrientation:orientation containerSize:containerSize];
|
||||
}
|
||||
|
||||
- (UIView *)yb_foregroundView {
|
||||
return self.videoView.thumbImageView;
|
||||
}
|
||||
|
||||
- (void)yb_pageChanged {
|
||||
if (self.yb_currentPage() != self.yb_selfPage()) {
|
||||
[self.videoView reset];
|
||||
[self hideToolViews:NO];
|
||||
[self.yb_auxiliaryViewHandler() yb_hideLoadingWithContainer:self];
|
||||
if (_interacting) [self restoreGestureInteractionWithDuration:0];
|
||||
self.videoView.needAutoPlay = NO;
|
||||
} else {
|
||||
self.videoView.needAutoPlay = YES;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - <YBIBVideoDataDelegate>
|
||||
|
||||
- (void)yb_startLoadingAVAssetFromPHAssetForData:(YBIBVideoData *)data {}
|
||||
|
||||
- (void)yb_finishLoadingAVAssetFromPHAssetForData:(YBIBVideoData *)data {}
|
||||
|
||||
- (void)yb_startLoadingFirstFrameForData:(YBIBVideoData *)data {
|
||||
if (!self.videoView.thumbImageView.image) {
|
||||
[self.yb_auxiliaryViewHandler() yb_showLoadingWithContainer:self];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)yb_finishLoadingFirstFrameForData:(YBIBVideoData *)data {
|
||||
[self.yb_auxiliaryViewHandler() yb_hideLoadingWithContainer:self];
|
||||
}
|
||||
|
||||
- (void)yb_videoData:(YBIBVideoData *)data downloadingWithProgress:(CGFloat)progress {
|
||||
[self.yb_auxiliaryViewHandler() yb_showLoadingWithContainer:self progress:progress];
|
||||
}
|
||||
|
||||
- (void)yb_finishDownloadingForData:(YBIBVideoData *)data {
|
||||
[self.yb_auxiliaryViewHandler() yb_hideLoadingWithContainer:self];
|
||||
}
|
||||
|
||||
- (void)yb_videoData:(YBIBVideoData *)data readyForAVAsset:(AVAsset *)asset {
|
||||
self.videoView.asset = asset;
|
||||
}
|
||||
|
||||
- (void)yb_videoData:(YBIBVideoData *)data readyForThumbImage:(UIImage *)image {
|
||||
if (!self.videoView.isPlaying) {
|
||||
self.videoView.thumbImageView.hidden = NO;
|
||||
}
|
||||
|
||||
if (!self.videoView.thumbImageView.image) {
|
||||
CGSize previousSize = self.videoView.thumbImageView.image.size;
|
||||
self.videoView.thumbImageView.image = image;
|
||||
[self updateImageLayoutWithOrientation:self.yb_currentOrientation() previousImageSize:previousSize];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)yb_videoIsInvalidForData:(YBIBVideoData *)data {
|
||||
[self.yb_auxiliaryViewHandler() yb_hideLoadingWithContainer:self];
|
||||
NSString *imageIsInvalid = [YBIBCopywriter sharedCopywriter].videoIsInvalid;
|
||||
if (self.videoView.thumbImageView.image) {
|
||||
[self.yb_auxiliaryViewHandler() yb_showIncorrectToastWithContainer:self text:imageIsInvalid];
|
||||
} else {
|
||||
[self.yb_auxiliaryViewHandler() yb_showLoadingWithContainer:self text:imageIsInvalid];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - <YBIBVideoViewDelegate>
|
||||
|
||||
- (BOOL)yb_isFreezingForVideoView:(YBIBVideoView *)view {
|
||||
return self.yb_isTransitioning();
|
||||
}
|
||||
|
||||
- (void)yb_preparePlayForVideoView:(YBIBVideoView *)view {
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.4 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||
if (!view.isPlaying && !view.isPlayFailed && self.yb_selfPage() == self.yb_currentPage()) {
|
||||
[self.yb_auxiliaryViewHandler() yb_showLoadingWithContainer:self];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
- (void)yb_startPlayForVideoView:(YBIBVideoView *)view {
|
||||
self.videoView.thumbImageView.hidden = YES;
|
||||
[self.yb_backView ybib_videoPlayingAdd:self];
|
||||
[self.yb_auxiliaryViewHandler() yb_hideLoadingWithContainer:self];
|
||||
[self hideToolViews:YES];
|
||||
}
|
||||
|
||||
- (void)yb_didPlayToEndTimeForVideoView:(YBIBVideoView *)view {
|
||||
YBIBVideoData *data = (YBIBVideoData *)self.yb_cellData;
|
||||
if (data.repeatPlayCount == NSUIntegerMax) {
|
||||
[view preparPlay];
|
||||
} else if (data.repeatPlayCount > 0) {
|
||||
--data.repeatPlayCount;
|
||||
[view preparPlay];
|
||||
} else {
|
||||
[self hideToolViews:NO];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)yb_finishPlayForVideoView:(YBIBVideoView *)view {
|
||||
[self.yb_backView ybib_videoPlayingRemove:self];
|
||||
[self hideToolViews:NO];
|
||||
}
|
||||
|
||||
- (void)yb_playFailedForVideoView:(YBIBVideoView *)view {
|
||||
[self.yb_auxiliaryViewHandler() yb_hideLoadingWithContainer:self];
|
||||
[self.yb_auxiliaryViewHandler() yb_showIncorrectToastWithContainer:self text:YBIBCopywriter.sharedCopywriter.videoError];
|
||||
}
|
||||
|
||||
- (void)yb_respondsToTapGestureForVideoView:(YBIBVideoView *)view {
|
||||
if (self.yb_isRotating()) return;
|
||||
|
||||
YBIBVideoData *data = self.yb_cellData;
|
||||
if (data.singleTouchBlock) {
|
||||
data.singleTouchBlock(data);
|
||||
} else {
|
||||
[self hideBrowser];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)yb_cancelledForVideoView:(YBIBVideoView *)view {
|
||||
if (self.yb_isRotating()) return;
|
||||
|
||||
[self hideBrowser];
|
||||
}
|
||||
|
||||
- (CGSize)yb_containerSizeForVideoView:(YBIBVideoView *)view {
|
||||
return self.yb_containerSize(self.yb_currentOrientation());
|
||||
}
|
||||
|
||||
- (void)yb_autoPlayCountChanged:(NSUInteger)count {
|
||||
YBIBVideoData *data = (YBIBVideoData *)self.yb_cellData;
|
||||
data.autoPlayCount = count;
|
||||
}
|
||||
|
||||
#pragma mark - <UIGestureRecognizerDelegate>
|
||||
|
||||
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
|
||||
return YES;
|
||||
}
|
||||
|
||||
#pragma mark - gesture
|
||||
|
||||
- (void)addGesture {
|
||||
UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(respondsToPanGesture:)];
|
||||
panGesture.cancelsTouchesInView = NO;
|
||||
panGesture.delegate = self;
|
||||
[self.videoView.tapGesture requireGestureRecognizerToFail:panGesture];
|
||||
[self.videoView addGestureRecognizer:panGesture];
|
||||
}
|
||||
|
||||
- (void)respondsToPanGesture:(UIPanGestureRecognizer *)pan {
|
||||
if (self.yb_isRotating()) return;
|
||||
if ((!self.videoView.thumbImageView.image && !self.videoView.isPlaying)) return;
|
||||
|
||||
YBIBInteractionProfile *profile = ((YBIBVideoData *)self.yb_cellData).interactionProfile;
|
||||
if (profile.disable) return;
|
||||
|
||||
CGPoint point = [pan locationInView:self];
|
||||
CGSize containerSize = self.yb_containerSize(self.yb_currentOrientation());
|
||||
|
||||
if (pan.state == UIGestureRecognizerStateBegan) {
|
||||
_interactStartPoint = point;
|
||||
} else if (pan.state == UIGestureRecognizerStateCancelled || pan.state == UIGestureRecognizerStateEnded || pan.state == UIGestureRecognizerStateRecognized || pan.state == UIGestureRecognizerStateFailed) {
|
||||
|
||||
// End
|
||||
if (_interacting) {
|
||||
CGPoint velocity = [pan velocityInView:self.videoView];
|
||||
|
||||
BOOL velocityArrive = ABS(velocity.y) > profile.dismissVelocityY;
|
||||
BOOL distanceArrive = ABS(point.y - _interactStartPoint.y) > containerSize.height * profile.dismissScale;
|
||||
|
||||
BOOL shouldDismiss = distanceArrive || velocityArrive;
|
||||
if (shouldDismiss) {
|
||||
[self hideBrowser];
|
||||
} else {
|
||||
[self restoreGestureInteractionWithDuration:profile.restoreDuration];
|
||||
}
|
||||
}
|
||||
|
||||
} else if (pan.state == UIGestureRecognizerStateChanged) {
|
||||
|
||||
if (_interacting) {
|
||||
|
||||
// Change
|
||||
self.videoView.center = point;
|
||||
CGFloat scale = 1 - ABS(point.y - _interactStartPoint.y) / (containerSize.height * 1.2);
|
||||
if (scale > 1) scale = 1;
|
||||
if (scale < 0.35) scale = 0.35;
|
||||
self.videoView.transform = CGAffineTransformMakeScale(scale, scale);
|
||||
|
||||
CGFloat alpha = 1 - ABS(point.y - _interactStartPoint.y) / (containerSize.height * 0.7);
|
||||
if (alpha > 1) alpha = 1;
|
||||
if (alpha < 0) alpha = 0;
|
||||
self.yb_backView.backgroundColor = [self.yb_backView.backgroundColor colorWithAlphaComponent:alpha];
|
||||
|
||||
} else {
|
||||
|
||||
// Start
|
||||
if (CGPointEqualToPoint(_interactStartPoint, CGPointZero) || self.yb_currentPage() != self.yb_selfPage() || !self.yb_cellIsInCenter() || self.videoView.actionBar.isTouchInside) return;
|
||||
|
||||
CGPoint velocityPoint = [pan velocityInView:self.videoView];
|
||||
CGFloat triggerDistance = profile.triggerDistance;
|
||||
|
||||
BOOL distanceArrive = ABS(point.y - _interactStartPoint.y) > triggerDistance && (ABS(point.x - _interactStartPoint.x) < triggerDistance && ABS(velocityPoint.x) < 500);
|
||||
|
||||
BOOL shouldStart = distanceArrive;
|
||||
if (!shouldStart) return;
|
||||
|
||||
[self.videoView hideToolBar:YES];
|
||||
|
||||
_interactStartPoint = point;
|
||||
|
||||
CGRect startFrame = self.videoView.bounds;
|
||||
CGFloat anchorX = (point.x - startFrame.origin.x) / startFrame.size.width,
|
||||
anchorY = (point.y - startFrame.origin.y) / startFrame.size.height;
|
||||
self.videoView.layer.anchorPoint = CGPointMake(anchorX, anchorY);
|
||||
self.videoView.userInteractionEnabled = NO;
|
||||
self.videoView.center = point;
|
||||
|
||||
[self hideToolViews:YES];
|
||||
self.yb_hideStatusBar(NO);
|
||||
self.yb_collectionView().scrollEnabled = NO;
|
||||
|
||||
_interacting = YES;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)restoreGestureInteractionWithDuration:(NSTimeInterval)duration {
|
||||
[self.videoView hideToolBar:NO];
|
||||
|
||||
CGSize containerSize = self.yb_containerSize(self.yb_currentOrientation());
|
||||
|
||||
void (^animations)(void) = ^{
|
||||
self.yb_backView.backgroundColor = [self.yb_backView.backgroundColor colorWithAlphaComponent:1];
|
||||
|
||||
CGPoint anchorPoint = self.videoView.layer.anchorPoint;
|
||||
self.videoView.center = CGPointMake(containerSize.width * anchorPoint.x, containerSize.height * anchorPoint.y);
|
||||
self.videoView.transform = CGAffineTransformIdentity;
|
||||
};
|
||||
void (^completion)(BOOL finished) = ^(BOOL finished){
|
||||
self.videoView.layer.anchorPoint = CGPointMake(0.5, 0.5);
|
||||
self.videoView.center = CGPointMake(containerSize.width * 0.5, containerSize.height * 0.5);
|
||||
self.videoView.userInteractionEnabled = YES;
|
||||
|
||||
self.yb_hideStatusBar(YES);
|
||||
self.yb_collectionView().scrollEnabled = YES;
|
||||
if (!self.videoView.isPlaying) [self hideToolViews:NO];;
|
||||
|
||||
self->_interactStartPoint = CGPointZero;
|
||||
self->_interacting = NO;
|
||||
};
|
||||
if (duration <= 0) {
|
||||
animations();
|
||||
completion(NO);
|
||||
} else {
|
||||
[UIView animateWithDuration:duration animations:animations completion:completion];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - getters & setters
|
||||
|
||||
- (YBIBVideoView *)videoView {
|
||||
if (!_videoView) {
|
||||
_videoView = [YBIBVideoView new];
|
||||
_videoView.delegate = self;
|
||||
}
|
||||
return _videoView;
|
||||
}
|
||||
|
||||
@end
|
||||
50
Pods/YBImageBrowser/Video/YBIBVideoData+Internal.h
generated
Normal file
@@ -0,0 +1,50 @@
|
||||
//
|
||||
// YBIBVideoData+Internal.h
|
||||
// YBImageBrowserDemo
|
||||
//
|
||||
// Created by 波儿菜 on 2019/7/11.
|
||||
// Copyright © 2019 杨波. All rights reserved.
|
||||
//
|
||||
|
||||
#import "YBIBVideoData.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class YBIBVideoData;
|
||||
|
||||
@protocol YBIBVideoDataDelegate <NSObject>
|
||||
@required
|
||||
|
||||
- (void)yb_startLoadingAVAssetFromPHAssetForData:(YBIBVideoData *)data;
|
||||
|
||||
- (void)yb_finishLoadingAVAssetFromPHAssetForData:(YBIBVideoData *)data;
|
||||
|
||||
- (void)yb_startLoadingFirstFrameForData:(YBIBVideoData *)data;
|
||||
|
||||
- (void)yb_finishLoadingFirstFrameForData:(YBIBVideoData *)data;
|
||||
|
||||
- (void)yb_videoData:(YBIBVideoData *)data downloadingWithProgress:(CGFloat)progress;
|
||||
|
||||
- (void)yb_finishDownloadingForData:(YBIBVideoData *)data;
|
||||
|
||||
- (void)yb_videoData:(YBIBVideoData *)data readyForThumbImage:(UIImage *)image;
|
||||
|
||||
- (void)yb_videoData:(YBIBVideoData *)data readyForAVAsset:(AVAsset *)asset;
|
||||
|
||||
- (void)yb_videoIsInvalidForData:(YBIBVideoData *)data;
|
||||
|
||||
@end
|
||||
|
||||
@interface YBIBVideoData ()
|
||||
|
||||
@property (nonatomic, assign, getter=isLoadingAVAssetFromPHAsset) BOOL loadingAVAssetFromPHAsset;
|
||||
|
||||
@property (nonatomic, assign, getter=isLoadingFirstFrame) BOOL loadingFirstFrame;
|
||||
|
||||
@property (nonatomic, assign, getter=isDownloading) BOOL downloading;
|
||||
|
||||
@property (nonatomic, weak) id<YBIBVideoDataDelegate> delegate;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
64
Pods/YBImageBrowser/Video/YBIBVideoData.h
generated
Normal file
@@ -0,0 +1,64 @@
|
||||
//
|
||||
// YBIBVideoData.h
|
||||
// YBImageBrowserDemo
|
||||
//
|
||||
// Created by 波儿菜 on 2019/7/10.
|
||||
// Copyright © 2019 杨波. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Photos/Photos.h>
|
||||
#import "YBIBDataProtocol.h"
|
||||
#import "YBIBInteractionProfile.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class YBIBVideoData;
|
||||
|
||||
/// 单击事件的处理闭包
|
||||
typedef void (^YBIBVideoSingleTouchBlock)(YBIBVideoData *videoData);
|
||||
|
||||
|
||||
/**
|
||||
图片数据类,承担配置数据和处理数据的责任
|
||||
*/
|
||||
@interface YBIBVideoData : NSObject <YBIBDataProtocol>
|
||||
|
||||
/// 视频 URL
|
||||
@property (nonatomic, copy, nullable) NSURL *videoURL;
|
||||
|
||||
/// 相册视频资源
|
||||
@property (nonatomic, strong, nullable) PHAsset *videoPHAsset;
|
||||
|
||||
/// 视频 AVAsset (通常使用 AVURLAsset)
|
||||
@property (nonatomic, strong, nullable) AVAsset *videoAVAsset;
|
||||
|
||||
/// 投影视图,当前数据模型对应外界业务的 UIView (通常为 UIImageView),做转场动效用
|
||||
@property (nonatomic, weak, nullable) __kindof UIView *projectiveView;
|
||||
|
||||
/// 预览图/缩约图,若 projectiveView 存在且是 UIImageView 类型将会自动获取缩约图
|
||||
@property (nonatomic, strong, nullable) UIImage *thumbImage;
|
||||
|
||||
/// 是否允许保存到相册
|
||||
@property (nonatomic, assign) BOOL allowSaveToPhotoAlbum;
|
||||
|
||||
/// 自动播放次数,默认为 0,NSUIntegerMax 表示无限次
|
||||
@property (nonatomic, assign) NSUInteger autoPlayCount;
|
||||
|
||||
/// 重复播放次数,默认为 0,NSUIntegerMax 表示无限次
|
||||
@property (nonatomic, assign) NSUInteger repeatPlayCount;
|
||||
|
||||
/// 预留属性可随意使用
|
||||
@property (nonatomic, strong, nullable) id extraData;
|
||||
|
||||
/// 手势交互动效配置文件
|
||||
@property (nonatomic, strong) YBIBInteractionProfile *interactionProfile;
|
||||
|
||||
/// 单击的处理(视频未播放时),默认是退出图片浏览器
|
||||
@property (nonatomic, copy, nullable) YBIBVideoSingleTouchBlock singleTouchBlock;
|
||||
|
||||
/// 是否要隐藏播放时的叉叉(取消)按钮
|
||||
@property (nonatomic, assign) BOOL shouldHideForkButton;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
296
Pods/YBImageBrowser/Video/YBIBVideoData.m
generated
Normal file
@@ -0,0 +1,296 @@
|
||||
//
|
||||
// YBIBVideoData.m
|
||||
// YBImageBrowserDemo
|
||||
//
|
||||
// Created by 波儿菜 on 2019/7/10.
|
||||
// Copyright © 2019 杨波. All rights reserved.
|
||||
//
|
||||
|
||||
#import "YBIBVideoData.h"
|
||||
#import "YBIBVideoCell.h"
|
||||
#import "YBIBVideoData+Internal.h"
|
||||
#import "YBIBUtilities.h"
|
||||
#import "YBIBPhotoAlbumManager.h"
|
||||
#import "YBIBCopywriter.h"
|
||||
|
||||
extern CGImageRef YYCGImageCreateDecodedCopy(CGImageRef imageRef, BOOL decodeForDisplay);
|
||||
|
||||
@interface YBIBVideoData () <NSURLSessionDelegate>
|
||||
@end
|
||||
|
||||
@implementation YBIBVideoData {
|
||||
NSURLSessionDownloadTask *_downloadTask;
|
||||
}
|
||||
|
||||
#pragma mark - life cycle
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
[self initValue];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)initValue {
|
||||
_loadingFirstFrame = NO;
|
||||
_loadingAVAssetFromPHAsset = NO;
|
||||
_downloading = NO;
|
||||
_interactionProfile = [YBIBInteractionProfile new];
|
||||
_repeatPlayCount = 0;
|
||||
_autoPlayCount = 0;
|
||||
_shouldHideForkButton = NO;
|
||||
_allowSaveToPhotoAlbum = YES;
|
||||
}
|
||||
|
||||
#pragma mark - load data
|
||||
|
||||
- (void)loadData {
|
||||
// Always load 'thumbImage'.
|
||||
[self loadThumbImage];
|
||||
|
||||
if (self.videoAVAsset) {
|
||||
[self.delegate yb_videoData:self readyForAVAsset:self.videoAVAsset];
|
||||
} else if (self.videoPHAsset) {
|
||||
[self loadAVAssetFromPHAsset];
|
||||
} else {
|
||||
[self.delegate yb_videoIsInvalidForData:self];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)loadAVAssetFromPHAsset {
|
||||
if (!self.videoPHAsset) return;
|
||||
if (self.isLoadingAVAssetFromPHAsset) {
|
||||
self.loadingAVAssetFromPHAsset = YES;
|
||||
return;
|
||||
}
|
||||
|
||||
self.loadingAVAssetFromPHAsset = YES;
|
||||
[YBIBPhotoAlbumManager getAVAssetWithPHAsset:self.videoPHAsset completion:^(AVAsset * _Nullable asset) {
|
||||
YBIB_DISPATCH_ASYNC_MAIN(^{
|
||||
self.loadingAVAssetFromPHAsset = NO;
|
||||
|
||||
self.videoAVAsset = asset;
|
||||
[self.delegate yb_videoData:self readyForAVAsset:self.videoAVAsset];
|
||||
[self loadThumbImage];
|
||||
})
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)loadThumbImage {
|
||||
if (self.thumbImage) {
|
||||
[self.delegate yb_videoData:self readyForThumbImage:self.thumbImage];
|
||||
} else if (self.projectiveView && [self.projectiveView isKindOfClass:UIImageView.self] && ((UIImageView *)self.projectiveView).image) {
|
||||
self.thumbImage = ((UIImageView *)self.projectiveView).image;
|
||||
[self.delegate yb_videoData:self readyForThumbImage:self.thumbImage];
|
||||
} else {
|
||||
[self loadThumbImage_firstFrame];
|
||||
}
|
||||
}
|
||||
- (void)loadThumbImage_firstFrame {
|
||||
if (!self.videoAVAsset) return;
|
||||
if (self.isLoadingFirstFrame) {
|
||||
self.loadingFirstFrame = YES;
|
||||
return;
|
||||
}
|
||||
|
||||
self.loadingFirstFrame = YES;
|
||||
CGSize containerSize = self.yb_containerSize(self.yb_currentOrientation());
|
||||
CGSize maximumSize = containerSize;
|
||||
|
||||
__weak typeof(self) wSelf = self;
|
||||
YBIB_DISPATCH_ASYNC(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
|
||||
AVAssetImageGenerator *generator = [AVAssetImageGenerator assetImageGeneratorWithAsset:self.videoAVAsset];
|
||||
generator.appliesPreferredTrackTransform = YES;
|
||||
generator.maximumSize = maximumSize;
|
||||
NSError *error = nil;
|
||||
CGImageRef cgImage = [generator copyCGImageAtTime:CMTimeMake(0, 1) actualTime:NULL error:&error];
|
||||
CGImageRef decodedImage = YYCGImageCreateDecodedCopy(cgImage, YES);
|
||||
UIImage *resultImage = [UIImage imageWithCGImage:decodedImage];
|
||||
if (cgImage) CGImageRelease(cgImage);
|
||||
if (decodedImage) CGImageRelease(decodedImage);
|
||||
|
||||
YBIB_DISPATCH_ASYNC_MAIN(^{
|
||||
__strong typeof(wSelf) self = wSelf;
|
||||
if (!self) return;
|
||||
self.loadingFirstFrame = NO;
|
||||
if (!error && resultImage) {
|
||||
self.thumbImage = resultImage;
|
||||
[self.delegate yb_videoData:self readyForThumbImage:self.thumbImage];
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
#pragma mark - <YBIBDataProtocol>
|
||||
|
||||
@synthesize yb_currentOrientation = _yb_currentOrientation;
|
||||
@synthesize yb_containerView = _yb_containerView;
|
||||
@synthesize yb_containerSize = _yb_containerSize;
|
||||
@synthesize yb_isHideTransitioning = _yb_isHideTransitioning;
|
||||
@synthesize yb_auxiliaryViewHandler = _yb_auxiliaryViewHandler;
|
||||
|
||||
- (nonnull Class)yb_classOfCell {
|
||||
return YBIBVideoCell.self;
|
||||
}
|
||||
|
||||
- (UIView *)yb_projectiveView {
|
||||
return self.projectiveView;
|
||||
}
|
||||
|
||||
- (CGRect)yb_imageViewFrameWithContainerSize:(CGSize)containerSize imageSize:(CGSize)imageSize orientation:(UIDeviceOrientation)orientation {
|
||||
if (containerSize.width <= 0 || containerSize.height <= 0 || imageSize.width <= 0 || imageSize.height <= 0) return CGRectZero;
|
||||
CGFloat x = 0, y = 0, width = 0, height = 0;
|
||||
if (imageSize.width / imageSize.height >= containerSize.width / containerSize.height) {
|
||||
width = containerSize.width;
|
||||
height = containerSize.width * (imageSize.height / imageSize.width);
|
||||
x = 0;
|
||||
y = (containerSize.height - height) / 2.0;
|
||||
} else {
|
||||
height = containerSize.height;
|
||||
width = containerSize.height * (imageSize.width / imageSize.height);
|
||||
x = (containerSize.width - width) / 2.0;
|
||||
y = 0;
|
||||
}
|
||||
return CGRectMake(x, y, width, height);
|
||||
}
|
||||
|
||||
- (void)yb_preload {
|
||||
if (!self.delegate) {
|
||||
[self loadData];
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)yb_allowSaveToPhotoAlbum {
|
||||
return self.allowSaveToPhotoAlbum;
|
||||
}
|
||||
|
||||
- (void)yb_saveToPhotoAlbum {
|
||||
void(^unableToSave)(void) = ^(){
|
||||
[self.yb_auxiliaryViewHandler() yb_showIncorrectToastWithContainer:self.yb_containerView text:[YBIBCopywriter sharedCopywriter].unableToSave];
|
||||
};
|
||||
|
||||
if (self.videoAVAsset && [self.videoAVAsset isKindOfClass:AVURLAsset.class]) {
|
||||
AVURLAsset *asset = (AVURLAsset *)self.videoAVAsset;
|
||||
NSURL *URL = asset.URL;
|
||||
if ([URL.scheme isEqualToString:@"file"]) {
|
||||
NSString *path = URL.path;
|
||||
if (UIVideoAtPathIsCompatibleWithSavedPhotosAlbum(path)) {
|
||||
UISaveVideoAtPathToSavedPhotosAlbum(path, self, @selector(UISaveVideoAtPathToSavedPhotosAlbum_videoPath:didFinishSavingWithError:contextInfo:), nil);
|
||||
} else {
|
||||
unableToSave();
|
||||
}
|
||||
} else if ([URL.scheme containsString:@"http"]) {
|
||||
[self downloadWithURL:URL];
|
||||
} else {
|
||||
unableToSave();
|
||||
}
|
||||
} else {
|
||||
unableToSave();
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - private
|
||||
|
||||
- (void)UISaveVideoAtPathToSavedPhotosAlbum_videoPath:(NSString *)videoPath didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo{
|
||||
if (error) {
|
||||
[self.yb_auxiliaryViewHandler() yb_showIncorrectToastWithContainer:self.yb_containerView text:[YBIBCopywriter sharedCopywriter].saveToPhotoAlbumFailed];
|
||||
} else {
|
||||
[self.yb_auxiliaryViewHandler() yb_showCorrectToastWithContainer:self.yb_containerView text:[YBIBCopywriter sharedCopywriter].saveToPhotoAlbumSuccess];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)downloadWithURL:(NSURL *)URL {
|
||||
if (self.isDownloading) {
|
||||
self.downloading = YES;
|
||||
return;
|
||||
}
|
||||
|
||||
self.downloading = YES;
|
||||
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
|
||||
NSURLSession *session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:[NSOperationQueue mainQueue]];
|
||||
_downloadTask = [session downloadTaskWithURL:URL];
|
||||
[_downloadTask resume];
|
||||
}
|
||||
|
||||
#pragma mark - <NSURLSessionDelegate>
|
||||
|
||||
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
|
||||
didWriteData:(int64_t)bytesWritten
|
||||
totalBytesWritten:(int64_t)totalBytesWritten
|
||||
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite {
|
||||
CGFloat progress = totalBytesWritten / (double)totalBytesExpectedToWrite;
|
||||
if (progress < 0) progress = 0;
|
||||
if (progress > 1) progress = 1;
|
||||
[self.delegate yb_videoData:self downloadingWithProgress:progress];
|
||||
}
|
||||
|
||||
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
|
||||
didCompleteWithError:(nullable NSError *)error {
|
||||
if (error) {
|
||||
[self.yb_auxiliaryViewHandler() yb_showIncorrectToastWithContainer:self.yb_containerView text:[YBIBCopywriter sharedCopywriter].downloadFailed];
|
||||
}
|
||||
self.downloading = NO;
|
||||
}
|
||||
|
||||
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
|
||||
didFinishDownloadingToURL:(NSURL *)location {
|
||||
NSString *cache = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
|
||||
NSString *file = [cache stringByAppendingPathComponent:downloadTask.response.suggestedFilename];
|
||||
[[NSFileManager defaultManager] moveItemAtURL:location toURL:[NSURL fileURLWithPath:file] error:nil];
|
||||
if (UIVideoAtPathIsCompatibleWithSavedPhotosAlbum(file)) {
|
||||
UISaveVideoAtPathToSavedPhotosAlbum(file, self, @selector(UISaveVideoAtPathToSavedPhotosAlbum_videoPath:didFinishSavingWithError:contextInfo:), nil);
|
||||
} else {
|
||||
[self.yb_auxiliaryViewHandler() yb_showIncorrectToastWithContainer:self.yb_containerView text:[YBIBCopywriter sharedCopywriter].saveToPhotoAlbumFailed];
|
||||
}
|
||||
self.downloading = NO;
|
||||
}
|
||||
|
||||
#pragma mark - getters & setters
|
||||
|
||||
- (void)setVideoURL:(NSURL *)videoURL{
|
||||
_videoURL = [videoURL isKindOfClass:NSString.class] ? [NSURL URLWithString:(NSString *)videoURL] : videoURL;
|
||||
self.videoAVAsset = [AVURLAsset URLAssetWithURL:_videoURL options:nil];
|
||||
}
|
||||
|
||||
- (void)setDownloading:(BOOL)downloading {
|
||||
_downloading = downloading;
|
||||
if (downloading) {
|
||||
[self.delegate yb_videoData:self downloadingWithProgress:0];
|
||||
} else {
|
||||
[self.delegate yb_finishDownloadingForData:self];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setLoadingAVAssetFromPHAsset:(BOOL)loadingAVAssetFromPHAsset {
|
||||
_loadingAVAssetFromPHAsset = loadingAVAssetFromPHAsset;
|
||||
if (loadingAVAssetFromPHAsset) {
|
||||
[self.delegate yb_startLoadingAVAssetFromPHAssetForData:self];
|
||||
} else {
|
||||
[self.delegate yb_finishLoadingAVAssetFromPHAssetForData:self];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setLoadingFirstFrame:(BOOL)loadingFirstFrame {
|
||||
_loadingFirstFrame = loadingFirstFrame;
|
||||
if (loadingFirstFrame) {
|
||||
[self.delegate yb_startLoadingFirstFrameForData:self];
|
||||
} else {
|
||||
[self.delegate yb_finishLoadingFirstFrameForData:self];
|
||||
}
|
||||
}
|
||||
|
||||
@synthesize delegate = _delegate;
|
||||
- (void)setDelegate:(id<YBIBVideoDataDelegate>)delegate {
|
||||
_delegate = delegate;
|
||||
if (delegate) {
|
||||
[self loadData];
|
||||
}
|
||||
}
|
||||
- (id<YBIBVideoDataDelegate>)delegate {
|
||||
// Stop sending data to the '_delegate' if it is transiting.
|
||||
return self.yb_isHideTransitioning() ? nil : _delegate;
|
||||
}
|
||||
|
||||
@end
|
||||
21
Pods/YBImageBrowser/Video/YBIBVideoTopBar.h
generated
Normal file
@@ -0,0 +1,21 @@
|
||||
//
|
||||
// YBIBVideoTopBar.h
|
||||
// YBImageBrowserDemo
|
||||
//
|
||||
// Created by 波儿菜 on 2019/7/11.
|
||||
// Copyright © 2019 杨波. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface YBIBVideoTopBar : UIView
|
||||
|
||||
@property (nonatomic, strong, readonly) UIButton *cancelButton;
|
||||
|
||||
+ (CGFloat)defaultHeight;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
54
Pods/YBImageBrowser/Video/YBIBVideoTopBar.m
generated
Normal file
@@ -0,0 +1,54 @@
|
||||
//
|
||||
// YBIBVideoTopBar.m
|
||||
// YBImageBrowserDemo
|
||||
//
|
||||
// Created by 波儿菜 on 2019/7/11.
|
||||
// Copyright © 2019 杨波. All rights reserved.
|
||||
//
|
||||
|
||||
#import "YBIBVideoTopBar.h"
|
||||
#import "YBIBIconManager.h"
|
||||
|
||||
@interface YBIBVideoTopBar ()
|
||||
@property (nonatomic, strong) UIButton *cancelButton;
|
||||
@end
|
||||
|
||||
@implementation YBIBVideoTopBar
|
||||
|
||||
#pragma mark - life cycle
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
if (self) {
|
||||
[self addSubview:self.cancelButton];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)layoutSubviews {
|
||||
[super layoutSubviews];
|
||||
CGFloat buttonWidth = 54;
|
||||
self.cancelButton.frame = CGRectMake(0, 0, buttonWidth, self.bounds.size.height);
|
||||
}
|
||||
|
||||
#pragma mark - public
|
||||
|
||||
+ (CGFloat)defaultHeight {
|
||||
return 50;
|
||||
}
|
||||
|
||||
#pragma mark - getter
|
||||
|
||||
- (UIButton *)cancelButton {
|
||||
if (!_cancelButton) {
|
||||
_cancelButton = [UIButton buttonWithType:UIButtonTypeCustom];
|
||||
[_cancelButton setImage:YBIBIconManager.sharedManager.videoCancelImage() forState:UIControlStateNormal];
|
||||
_cancelButton.layer.shadowColor = UIColor.darkGrayColor.CGColor;
|
||||
_cancelButton.layer.shadowOffset = CGSizeMake(0, 1);
|
||||
_cancelButton.layer.shadowOpacity = 1;
|
||||
_cancelButton.layer.shadowRadius = 4;
|
||||
}
|
||||
return _cancelButton;
|
||||
}
|
||||
|
||||
@end
|
||||
76
Pods/YBImageBrowser/Video/YBIBVideoView.h
generated
Normal file
@@ -0,0 +1,76 @@
|
||||
//
|
||||
// YBIBVideoView.h
|
||||
// YBImageBrowserDemo
|
||||
//
|
||||
// Created by 波儿菜 on 2019/7/11.
|
||||
// Copyright © 2019 杨波. All rights reserved.
|
||||
//
|
||||
|
||||
#import "YBIBVideoActionBar.h"
|
||||
#import "YBIBVideoTopBar.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class YBIBVideoView;
|
||||
|
||||
@protocol YBIBVideoViewDelegate <NSObject>
|
||||
@required
|
||||
|
||||
- (BOOL)yb_isFreezingForVideoView:(YBIBVideoView *)view;
|
||||
|
||||
- (void)yb_preparePlayForVideoView:(YBIBVideoView *)view;
|
||||
|
||||
- (void)yb_startPlayForVideoView:(YBIBVideoView *)view;
|
||||
|
||||
- (void)yb_finishPlayForVideoView:(YBIBVideoView *)view;
|
||||
|
||||
- (void)yb_didPlayToEndTimeForVideoView:(YBIBVideoView *)view;
|
||||
|
||||
- (void)yb_playFailedForVideoView:(YBIBVideoView *)view;
|
||||
|
||||
- (void)yb_respondsToTapGestureForVideoView:(YBIBVideoView *)view;
|
||||
|
||||
- (void)yb_cancelledForVideoView:(YBIBVideoView *)view;
|
||||
|
||||
- (CGSize)yb_containerSizeForVideoView:(YBIBVideoView *)view;
|
||||
|
||||
- (void)yb_autoPlayCountChanged:(NSUInteger)count;
|
||||
|
||||
@end
|
||||
|
||||
@interface YBIBVideoView : UIView
|
||||
|
||||
@property (nonatomic, strong) UIImageView *thumbImageView;
|
||||
|
||||
@property (nonatomic, weak) id<YBIBVideoViewDelegate> delegate;
|
||||
|
||||
- (void)updateLayoutWithExpectOrientation:(UIDeviceOrientation)orientation containerSize:(CGSize)containerSize;
|
||||
|
||||
@property (nonatomic, strong, nullable) AVAsset *asset;
|
||||
|
||||
@property (nonatomic, assign, readonly, getter=isPlaying) BOOL playing;
|
||||
|
||||
@property (nonatomic, assign, readonly, getter=isPlayFailed) BOOL playFailed;
|
||||
|
||||
@property (nonatomic, assign, readonly, getter=isPreparingPlay) BOOL preparingPlay;
|
||||
|
||||
@property (nonatomic, strong, readonly) UITapGestureRecognizer *tapGesture;
|
||||
|
||||
- (void)reset;
|
||||
|
||||
- (void)hideToolBar:(BOOL)hide;
|
||||
|
||||
- (void)hidePlayButton;
|
||||
|
||||
- (void)preparPlay;
|
||||
|
||||
@property (nonatomic, assign) BOOL needAutoPlay;
|
||||
|
||||
@property (nonatomic, assign) NSUInteger autoPlayCount;
|
||||
|
||||
@property (nonatomic, strong, readonly) YBIBVideoTopBar *topBar;
|
||||
@property (nonatomic, strong, readonly) YBIBVideoActionBar *actionBar;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
394
Pods/YBImageBrowser/Video/YBIBVideoView.m
generated
Normal file
@@ -0,0 +1,394 @@
|
||||
//
|
||||
// YBIBVideoView.m
|
||||
// YBImageBrowserDemo
|
||||
//
|
||||
// Created by 波儿菜 on 2019/7/11.
|
||||
// Copyright © 2019 杨波. All rights reserved.
|
||||
//
|
||||
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
#import "YBIBVideoView.h"
|
||||
#import "YBIBVideoActionBar.h"
|
||||
#import "YBIBVideoTopBar.h"
|
||||
#import "YBIBUtilities.h"
|
||||
#import "YBIBIconManager.h"
|
||||
|
||||
@interface YBIBVideoView () <YBIBVideoActionBarDelegate>
|
||||
@property (nonatomic, strong) YBIBVideoTopBar *topBar;
|
||||
@property (nonatomic, strong) YBIBVideoActionBar *actionBar;
|
||||
@property (nonatomic, strong) UIButton *playButton;
|
||||
@end
|
||||
|
||||
@implementation YBIBVideoView {
|
||||
AVPlayer *_player;
|
||||
AVPlayerItem *_playerItem;
|
||||
AVPlayerLayer *_playerLayer;
|
||||
BOOL _active;
|
||||
}
|
||||
|
||||
#pragma mark - life cycle
|
||||
|
||||
- (void)dealloc {
|
||||
[self removeObserverForSystem];
|
||||
[self reset];
|
||||
}
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
if (self) {
|
||||
[self initValue];
|
||||
self.backgroundColor = UIColor.clearColor;
|
||||
|
||||
[self addSubview:self.thumbImageView];
|
||||
[self addSubview:self.topBar];
|
||||
[self addSubview:self.actionBar];
|
||||
[self addSubview:self.playButton];
|
||||
[self addObserverForSystem];
|
||||
|
||||
_tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(respondsToTapGesture:)];
|
||||
[self addGestureRecognizer:_tapGesture];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)initValue {
|
||||
_playing = NO;
|
||||
_active = YES;
|
||||
_needAutoPlay = NO;
|
||||
_autoPlayCount = 0;
|
||||
_playFailed = NO;
|
||||
_preparingPlay = NO;
|
||||
}
|
||||
|
||||
#pragma mark - public
|
||||
|
||||
- (void)updateLayoutWithExpectOrientation:(UIDeviceOrientation)orientation containerSize:(CGSize)containerSize {
|
||||
UIEdgeInsets padding = YBIBPaddingByBrowserOrientation(orientation);
|
||||
CGFloat width = containerSize.width - padding.left - padding.right, height = containerSize.height;
|
||||
self.topBar.frame = CGRectMake(padding.left, padding.top, width, [YBIBVideoTopBar defaultHeight]);
|
||||
self.actionBar.frame = CGRectMake(padding.left, height - [YBIBVideoActionBar defaultHeight] - padding.bottom - 10, width, [YBIBVideoActionBar defaultHeight]);
|
||||
self.playButton.center = CGPointMake(containerSize.width / 2.0, containerSize.height / 2.0);
|
||||
_playerLayer.frame = (CGRect){CGPointZero, containerSize};
|
||||
}
|
||||
|
||||
- (void)reset {
|
||||
[self removeObserverForPlayer];
|
||||
|
||||
// If set '_playerLayer.player = nil' or '_player = nil', can not cancel observeing of 'addPeriodicTimeObserverForInterval'.
|
||||
[_player pause];
|
||||
_playerItem = nil;
|
||||
[_playerLayer removeFromSuperlayer];
|
||||
_playerLayer = nil;
|
||||
|
||||
[self finishPlay];
|
||||
}
|
||||
|
||||
- (void)hideToolBar:(BOOL)hide {
|
||||
if (hide) {
|
||||
self.actionBar.hidden = YES;
|
||||
self.topBar.hidden = YES;
|
||||
} else if (self.isPlaying) {
|
||||
self.actionBar.hidden = NO;
|
||||
self.topBar.hidden = NO;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)hidePlayButton {
|
||||
self.playButton.hidden = YES;
|
||||
}
|
||||
|
||||
#pragma mark - private
|
||||
|
||||
- (void)videoJumpWithScale:(float)scale {
|
||||
CMTime startTime = CMTimeMakeWithSeconds(scale, _player.currentTime.timescale);
|
||||
AVPlayer *tmpPlayer = _player;
|
||||
|
||||
if (CMTIME_IS_INDEFINITE(startTime) || CMTIME_IS_INVALID(startTime)) return;
|
||||
[_player seekToTime:startTime toleranceBefore:CMTimeMake(1, 1000) toleranceAfter:CMTimeMake(1, 1000) completionHandler:^(BOOL finished) {
|
||||
if (finished && tmpPlayer == self->_player) {
|
||||
[self startPlay];
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)preparPlay {
|
||||
_preparingPlay = YES;
|
||||
_playFailed = NO;
|
||||
|
||||
self.playButton.hidden = YES;
|
||||
|
||||
[self.delegate yb_preparePlayForVideoView:self];
|
||||
|
||||
if (!_playerLayer) {
|
||||
_playerItem = [AVPlayerItem playerItemWithAsset:self.asset];
|
||||
_player = [AVPlayer playerWithPlayerItem:_playerItem];
|
||||
|
||||
_playerLayer = [AVPlayerLayer playerLayerWithPlayer:_player];
|
||||
_playerLayer.frame = (CGRect){CGPointZero, [self.delegate yb_containerSizeForVideoView:self]};
|
||||
[self.layer insertSublayer:_playerLayer above:self.thumbImageView.layer];
|
||||
|
||||
[self addObserverForPlayer];
|
||||
} else {
|
||||
[self videoJumpWithScale:0];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)startPlay {
|
||||
if (_player) {
|
||||
_playing = YES;
|
||||
|
||||
[_player play];
|
||||
[self.actionBar play];
|
||||
|
||||
self.topBar.hidden = NO;
|
||||
self.actionBar.hidden = NO;
|
||||
|
||||
[self.delegate yb_startPlayForVideoView:self];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)finishPlay {
|
||||
self.playButton.hidden = NO;
|
||||
[self.actionBar setCurrentValue:0];
|
||||
self.actionBar.hidden = YES;
|
||||
self.topBar.hidden = YES;
|
||||
|
||||
_playing = NO;
|
||||
|
||||
[self.delegate yb_finishPlayForVideoView:self];
|
||||
}
|
||||
|
||||
- (void)playerPause {
|
||||
if (_player) {
|
||||
[_player pause];
|
||||
[self.actionBar pause];
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)autoPlay {
|
||||
if (self.autoPlayCount == NSUIntegerMax) {
|
||||
[self preparPlay];
|
||||
} else if (self.autoPlayCount > 0) {
|
||||
--self.autoPlayCount;
|
||||
[self.delegate yb_autoPlayCountChanged:self.autoPlayCount];
|
||||
[self preparPlay];
|
||||
} else {
|
||||
return NO;
|
||||
}
|
||||
return YES;
|
||||
}
|
||||
|
||||
#pragma mark - <YBIBVideoActionBarDelegate>
|
||||
|
||||
- (void)yb_videoActionBar:(YBIBVideoActionBar *)actionBar clickPlayButton:(UIButton *)playButton {
|
||||
[self startPlay];
|
||||
}
|
||||
|
||||
- (void)yb_videoActionBar:(YBIBVideoActionBar *)actionBar clickPauseButton:(UIButton *)pauseButton {
|
||||
[self playerPause];
|
||||
}
|
||||
|
||||
- (void)yb_videoActionBar:(YBIBVideoActionBar *)actionBar changeValue:(float)value {
|
||||
[self videoJumpWithScale:value];
|
||||
}
|
||||
|
||||
#pragma mark - observe
|
||||
|
||||
- (void)addObserverForPlayer {
|
||||
[_playerItem addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:nil];
|
||||
__weak typeof(self) wSelf = self;
|
||||
[_player addPeriodicTimeObserverForInterval:CMTimeMake(1, 1) queue:dispatch_get_main_queue() usingBlock:^(CMTime time) {
|
||||
__strong typeof(wSelf) self = wSelf;
|
||||
if (!self) return;
|
||||
float currentTime = time.value / time.timescale;
|
||||
[self.actionBar setCurrentValue:currentTime];
|
||||
}];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didPlayToEndTime:) name:AVPlayerItemDidPlayToEndTimeNotification object:_playerItem];
|
||||
}
|
||||
|
||||
- (void)removeObserverForPlayer {
|
||||
[_playerItem removeObserver:self forKeyPath:@"status"];
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self name:AVPlayerItemDidPlayToEndTimeNotification object:_playerItem];
|
||||
}
|
||||
|
||||
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
|
||||
if (![self.delegate yb_isFreezingForVideoView:self]) {
|
||||
if (object == _playerItem) {
|
||||
if ([keyPath isEqualToString:@"status"]) {
|
||||
[self playerItemStatusChanged];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)didPlayToEndTime:(NSNotification *)noti {
|
||||
if (noti.object == _playerItem) {
|
||||
[self finishPlay];
|
||||
[self.delegate yb_didPlayToEndTimeForVideoView:self];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)playerItemStatusChanged {
|
||||
if (!_active) return;
|
||||
|
||||
_preparingPlay = NO;
|
||||
|
||||
switch (_playerItem.status) {
|
||||
case AVPlayerItemStatusReadyToPlay: {
|
||||
// Delay to update UI.
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.15 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||
[self startPlay];
|
||||
double max = CMTimeGetSeconds(self->_playerItem.duration);
|
||||
[self.actionBar setMaxValue:(isnan(max) || isinf(max)) ? 0 : max];
|
||||
});
|
||||
}
|
||||
break;
|
||||
case AVPlayerItemStatusUnknown: {
|
||||
_playFailed = YES;
|
||||
[self.delegate yb_playFailedForVideoView:self];
|
||||
[self reset];
|
||||
}
|
||||
break;
|
||||
case AVPlayerItemStatusFailed: {
|
||||
_playFailed = YES;
|
||||
[self.delegate yb_playFailedForVideoView:self];
|
||||
[self reset];
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)removeObserverForSystem {
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationWillResignActiveNotification object:nil];
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidBecomeActiveNotification object:nil];
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidChangeStatusBarFrameNotification object:nil];
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self name:AVAudioSessionRouteChangeNotification object:nil];
|
||||
}
|
||||
|
||||
- (void)addObserverForSystem {
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationWillResignActive:) name:UIApplicationWillResignActiveNotification object:nil];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidBecomeActive:) name:UIApplicationDidBecomeActiveNotification object:nil];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didChangeStatusBarFrame) name:UIApplicationDidChangeStatusBarFrameNotification object:nil];
|
||||
[[AVAudioSession sharedInstance] setActive:YES error:nil];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(audioRouteChangeListenerCallback:) name:AVAudioSessionRouteChangeNotification object:nil];
|
||||
}
|
||||
|
||||
- (void)applicationWillResignActive:(NSNotification *)notification {
|
||||
_active = NO;
|
||||
[self playerPause];
|
||||
}
|
||||
|
||||
- (void)applicationDidBecomeActive:(NSNotification *)notification {
|
||||
_active = YES;
|
||||
}
|
||||
|
||||
- (void)didChangeStatusBarFrame {
|
||||
if ([UIApplication sharedApplication].statusBarFrame.size.height > YBIBStatusbarHeight()) {
|
||||
[self playerPause];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)audioRouteChangeListenerCallback:(NSNotification*)notification {
|
||||
YBIB_DISPATCH_ASYNC_MAIN(^{
|
||||
NSDictionary *interuptionDict = notification.userInfo;
|
||||
NSInteger routeChangeReason = [[interuptionDict valueForKey:AVAudioSessionRouteChangeReasonKey] integerValue];
|
||||
switch (routeChangeReason) {
|
||||
case AVAudioSessionRouteChangeReasonOldDeviceUnavailable:
|
||||
[self playerPause];
|
||||
break;
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#pragma mark - event
|
||||
|
||||
- (void)respondsToTapGesture:(UITapGestureRecognizer *)tap {
|
||||
if (self.isPlaying) {
|
||||
self.actionBar.hidden = !self.actionBar.isHidden;
|
||||
self.topBar.hidden = !self.topBar.isHidden;
|
||||
} else {
|
||||
[self.delegate yb_respondsToTapGestureForVideoView:self];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)clickCancelButton:(UIButton *)button {
|
||||
[self.delegate yb_cancelledForVideoView:self];
|
||||
}
|
||||
|
||||
- (void)clickPlayButton:(UIButton *)button {
|
||||
[self preparPlay];
|
||||
}
|
||||
|
||||
#pragma mark - getters & setters
|
||||
|
||||
- (void)setNeedAutoPlay:(BOOL)needAutoPlay {
|
||||
if (needAutoPlay && _asset && !self.isPlaying) {
|
||||
[self autoPlay];
|
||||
} else {
|
||||
_needAutoPlay = needAutoPlay;
|
||||
}
|
||||
}
|
||||
|
||||
@synthesize asset = _asset;
|
||||
- (void)setAsset:(AVAsset *)asset {
|
||||
_asset = asset;
|
||||
if (!asset) return;
|
||||
if (self.needAutoPlay) {
|
||||
if (![self autoPlay]) {
|
||||
self.playButton.hidden = NO;
|
||||
}
|
||||
self.needAutoPlay = NO;
|
||||
} else {
|
||||
self.playButton.hidden = NO;
|
||||
}
|
||||
}
|
||||
- (AVAsset *)asset {
|
||||
if ([_asset isKindOfClass:AVURLAsset.class]) {
|
||||
_asset = [AVURLAsset assetWithURL:((AVURLAsset *)_asset).URL];
|
||||
}
|
||||
return _asset;
|
||||
}
|
||||
|
||||
- (YBIBVideoTopBar *)topBar {
|
||||
if (!_topBar) {
|
||||
_topBar = [YBIBVideoTopBar new];
|
||||
[_topBar.cancelButton addTarget:self action:@selector(clickCancelButton:) forControlEvents:UIControlEventTouchUpInside];
|
||||
_topBar.hidden = YES;
|
||||
}
|
||||
return _topBar;
|
||||
}
|
||||
|
||||
- (YBIBVideoActionBar *)actionBar {
|
||||
if (!_actionBar) {
|
||||
_actionBar = [YBIBVideoActionBar new];
|
||||
_actionBar.delegate = self;
|
||||
_actionBar.hidden = YES;
|
||||
}
|
||||
return _actionBar;
|
||||
}
|
||||
|
||||
- (UIButton *)playButton {
|
||||
if (!_playButton) {
|
||||
_playButton = [UIButton buttonWithType:UIButtonTypeCustom];
|
||||
_playButton.bounds = CGRectMake(0, 0, 100, 100);
|
||||
[_playButton setImage:YBIBIconManager.sharedManager.videoBigPlayImage() forState:UIControlStateNormal];
|
||||
[_playButton addTarget:self action:@selector(clickPlayButton:) forControlEvents:UIControlEventTouchUpInside];
|
||||
_playButton.hidden = YES;
|
||||
_playButton.layer.shadowColor = UIColor.darkGrayColor.CGColor;
|
||||
_playButton.layer.shadowOffset = CGSizeMake(0, 1);
|
||||
_playButton.layer.shadowOpacity = 1;
|
||||
_playButton.layer.shadowRadius = 4;
|
||||
}
|
||||
return _playButton;
|
||||
}
|
||||
|
||||
- (UIImageView *)thumbImageView {
|
||||
if (!_thumbImageView) {
|
||||
_thumbImageView = [UIImageView new];
|
||||
_thumbImageView.contentMode = UIViewContentModeScaleAspectFit;
|
||||
_thumbImageView.layer.masksToBounds = YES;
|
||||
}
|
||||
return _thumbImageView;
|
||||
}
|
||||
|
||||
@end
|
||||
BIN
Pods/YBImageBrowser/Video/YBImageBrowserVideo.bundle/ybib_bigPlay@2x.png
generated
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
Pods/YBImageBrowser/Video/YBImageBrowserVideo.bundle/ybib_bigPlay@3x.png
generated
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
Pods/YBImageBrowser/Video/YBImageBrowserVideo.bundle/ybib_cancel@2x.png
generated
Normal file
|
After Width: | Height: | Size: 850 B |
BIN
Pods/YBImageBrowser/Video/YBImageBrowserVideo.bundle/ybib_cancel@3x.png
generated
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
Pods/YBImageBrowser/Video/YBImageBrowserVideo.bundle/ybib_circlePoint@2x.png
generated
Normal file
|
After Width: | Height: | Size: 799 B |
BIN
Pods/YBImageBrowser/Video/YBImageBrowserVideo.bundle/ybib_circlePoint@3x.png
generated
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
Pods/YBImageBrowser/Video/YBImageBrowserVideo.bundle/ybib_pause@2x.png
generated
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
BIN
Pods/YBImageBrowser/Video/YBImageBrowserVideo.bundle/ybib_pause@3x.png
generated
Normal file
|
After Width: | Height: | Size: 961 B |
BIN
Pods/YBImageBrowser/Video/YBImageBrowserVideo.bundle/ybib_play@2x.png
generated
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
BIN
Pods/YBImageBrowser/Video/YBImageBrowserVideo.bundle/ybib_play@3x.png
generated
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
39
Pods/YBImageBrowser/YBImageBrowser/AuxiliaryView/YBIBAuxiliaryViewHandler.h
generated
Normal file
@@ -0,0 +1,39 @@
|
||||
//
|
||||
// YBIBAuxiliaryViewHandler.h
|
||||
// YBImageBrowserDemo
|
||||
//
|
||||
// Created by 波儿菜 on 2019/6/27.
|
||||
// Copyright © 2019 波儿菜. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@protocol YBIBAuxiliaryViewHandler <NSObject>
|
||||
|
||||
@required
|
||||
|
||||
/// 展示正确情况的提示
|
||||
- (void)yb_showCorrectToastWithContainer:(UIView *)container text:(NSString *)text;
|
||||
/// 展示错误情况的提示
|
||||
- (void)yb_showIncorrectToastWithContainer:(UIView *)container text:(NSString *)text;
|
||||
/// 隐藏所有提示
|
||||
- (void)yb_hideToastWithContainer:(UIView *)container;
|
||||
|
||||
/// 展示加载视图
|
||||
- (void)yb_showLoadingWithContainer:(UIView *)container;
|
||||
/// 展示带进度的加载视图
|
||||
- (void)yb_showLoadingWithContainer:(UIView *)container progress:(CGFloat)progress;
|
||||
/// 展示带文字的视图
|
||||
- (void)yb_showLoadingWithContainer:(UIView *)container text:(NSString *)text;
|
||||
/// 隐藏所有视图
|
||||
- (void)yb_hideLoadingWithContainer:(UIView *)container;
|
||||
|
||||
@end
|
||||
|
||||
@interface YBIBAuxiliaryViewHandler : NSObject <YBIBAuxiliaryViewHandler>
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
45
Pods/YBImageBrowser/YBImageBrowser/AuxiliaryView/YBIBAuxiliaryViewHandler.m
generated
Normal file
@@ -0,0 +1,45 @@
|
||||
//
|
||||
// YBIBAuxiliaryViewHandler.m
|
||||
// YBImageBrowserDemo
|
||||
//
|
||||
// Created by 波儿菜 on 2019/6/27.
|
||||
// Copyright © 2019 波儿菜. All rights reserved.
|
||||
//
|
||||
|
||||
#import "YBIBAuxiliaryViewHandler.h"
|
||||
#import "YBIBToastView.h"
|
||||
#import "YBIBLoadingView.h"
|
||||
|
||||
@implementation YBIBAuxiliaryViewHandler
|
||||
|
||||
#pragma mark - <YBIBAuxiliaryViewHandler>
|
||||
|
||||
- (void)yb_showCorrectToastWithContainer:(UIView *)container text:(NSString *)text {
|
||||
[container ybib_showHookToast:text];
|
||||
}
|
||||
|
||||
- (void)yb_showIncorrectToastWithContainer:(UIView *)container text:(NSString *)text {
|
||||
[container ybib_showForkToast:text];
|
||||
}
|
||||
|
||||
- (void)yb_hideToastWithContainer:(UIView *)container {
|
||||
[container ybib_hideToast];
|
||||
}
|
||||
|
||||
- (void)yb_showLoadingWithContainer:(UIView *)container {
|
||||
[container ybib_showLoading];
|
||||
}
|
||||
|
||||
- (void)yb_showLoadingWithContainer:(UIView *)container progress:(CGFloat)progress {
|
||||
[container ybib_showLoadingWithProgress:progress];
|
||||
}
|
||||
|
||||
- (void)yb_showLoadingWithContainer:(UIView *)container text:(NSString *)text {
|
||||
[container ybib_showLoadingWithText:text click:nil];
|
||||
}
|
||||
|
||||
- (void)yb_hideLoadingWithContainer:(UIView *)container {
|
||||
[container ybib_hideLoading];
|
||||
}
|
||||
|
||||
@end
|
||||
36
Pods/YBImageBrowser/YBImageBrowser/AuxiliaryView/YBIBLoadingView.h
generated
Normal file
@@ -0,0 +1,36 @@
|
||||
//
|
||||
// YBIBLoadingView.h
|
||||
// YBImageBrowserDemo
|
||||
//
|
||||
// Created by 波儿菜 on 2018/9/1.
|
||||
// Copyright © 2018年 波儿菜. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface UIView (YBIBLoading)
|
||||
|
||||
- (void)ybib_showLoading;
|
||||
|
||||
- (void)ybib_showLoadingWithProgress:(CGFloat)progress;
|
||||
|
||||
- (void)ybib_showLoadingWithText:(NSString *)text click:(nullable void(^)(void))click;
|
||||
|
||||
- (void)ybib_hideLoading;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@interface YBIBLoadingView : UIView
|
||||
|
||||
- (void)show;
|
||||
|
||||
- (void)showProgress:(CGFloat)progress;
|
||||
|
||||
- (void)showText:(NSString *)text click:(void(^)(void))click;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
258
Pods/YBImageBrowser/YBImageBrowser/AuxiliaryView/YBIBLoadingView.m
generated
Normal file
@@ -0,0 +1,258 @@
|
||||
//
|
||||
// YBIBLoadingView.m
|
||||
// YBImageBrowserDemo
|
||||
//
|
||||
// Created by 波儿菜 on 2018/9/1.
|
||||
// Copyright © 2018年 波儿菜. All rights reserved.
|
||||
//
|
||||
|
||||
#import "YBIBLoadingView.h"
|
||||
#import "YBIBIconManager.h"
|
||||
#import <objc/runtime.h>
|
||||
|
||||
@interface UIView ()
|
||||
@property (nonatomic, strong, readonly) YBIBLoadingView *ybib_loading;
|
||||
@end
|
||||
|
||||
@implementation UIView (YBIBLoading)
|
||||
|
||||
- (void)ybib_showLoadingWithProgress:(CGFloat)progress {
|
||||
[self ybib_addLoadingView];
|
||||
[self.ybib_loading showProgress:progress];
|
||||
}
|
||||
|
||||
- (void)ybib_showLoading {
|
||||
[self ybib_addLoadingView];
|
||||
[self.ybib_loading show];
|
||||
}
|
||||
|
||||
- (void)ybib_showLoadingWithText:(NSString *)text click:(void (^)(void))click {
|
||||
[self ybib_addLoadingView];
|
||||
[self.ybib_loading showText:text click:click];
|
||||
}
|
||||
|
||||
- (void)ybib_addLoadingView {
|
||||
YBIBLoadingView *loading = self.ybib_loading;
|
||||
if (!loading.superview) {
|
||||
[self addSubview:loading];
|
||||
loading.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
NSLayoutConstraint *layA = [NSLayoutConstraint constraintWithItem:loading attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeLeft multiplier:1 constant:0];
|
||||
NSLayoutConstraint *layB = [NSLayoutConstraint constraintWithItem:loading attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeRight multiplier:1 constant:0];
|
||||
NSLayoutConstraint *layC = [NSLayoutConstraint constraintWithItem:loading attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeBottom multiplier:1 constant:0];
|
||||
NSLayoutConstraint *layD = [NSLayoutConstraint constraintWithItem:loading attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeTop multiplier:1 constant:0];
|
||||
[self addConstraints:@[layA, layB, layC, layD]];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)ybib_hideLoading {
|
||||
YBIBLoadingView *loading = self.ybib_loading;
|
||||
if (loading && loading.superview) {
|
||||
[loading removeFromSuperview];
|
||||
}
|
||||
}
|
||||
|
||||
static void *YBIBLoadingKey = &YBIBLoadingKey;
|
||||
- (void)setYbib_loading:(YBIBLoadingView *)ybib_loading {
|
||||
objc_setAssociatedObject(self, YBIBLoadingKey, ybib_loading, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
||||
}
|
||||
- (YBIBLoadingView *)ybib_loading {
|
||||
YBIBLoadingView *loading = objc_getAssociatedObject(self, YBIBLoadingKey);
|
||||
if (!loading) {
|
||||
loading = [YBIBLoadingView new];
|
||||
self.ybib_loading = loading;
|
||||
}
|
||||
return loading;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@interface YBIBProgressDrawView : UIView
|
||||
@property (nonatomic, assign) CGFloat progress;
|
||||
@end
|
||||
@implementation YBIBProgressDrawView
|
||||
- (void)drawRect:(CGRect)rect {
|
||||
if (self.isHidden) return;
|
||||
|
||||
CGFloat progress = (isnan(_progress) || isinf(_progress) || _progress < 0) ? 0 : _progress;
|
||||
|
||||
CGFloat radius = 17;
|
||||
CGFloat strokeWidth = 3;
|
||||
CGPoint center = CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds));
|
||||
|
||||
[[UIColor lightGrayColor] setStroke];
|
||||
UIBezierPath *bottomPath = [UIBezierPath bezierPathWithArcCenter:center radius:radius startAngle:0 endAngle:M_PI * 2 clockwise:YES];
|
||||
bottomPath.lineWidth = 4.0;
|
||||
bottomPath.lineCapStyle = kCGLineCapRound;
|
||||
bottomPath.lineJoinStyle = kCGLineCapRound;
|
||||
[bottomPath stroke];
|
||||
|
||||
[[UIColor whiteColor] setStroke];
|
||||
UIBezierPath *activePath = [UIBezierPath bezierPathWithArcCenter:center radius:radius startAngle:-M_PI / 2.0 endAngle:M_PI * 2 * progress - M_PI / 2.0 clockwise:true];
|
||||
activePath.lineWidth = strokeWidth;
|
||||
activePath.lineCapStyle = kCGLineCapRound;
|
||||
activePath.lineJoinStyle = kCGLineCapRound;
|
||||
[activePath stroke];
|
||||
|
||||
NSShadow *shadow = [NSShadow new];
|
||||
shadow.shadowBlurRadius = 4;
|
||||
shadow.shadowOffset = CGSizeMake(0, 1);
|
||||
shadow.shadowColor = UIColor.darkGrayColor;
|
||||
NSString *string = [NSString stringWithFormat:@"%.0lf%@", progress * 100, @"%"];
|
||||
NSMutableAttributedString *atts = [[NSMutableAttributedString alloc] initWithString:string attributes:@{NSFontAttributeName:[UIFont boldSystemFontOfSize:10], NSForegroundColorAttributeName:[UIColor whiteColor], NSShadowAttributeName:shadow}];
|
||||
CGSize size = atts.size;
|
||||
[atts drawAtPoint:CGPointMake(center.x - size.width / 2.0, center.y - size.height / 2.0)];
|
||||
}
|
||||
@end
|
||||
|
||||
|
||||
typedef NS_ENUM(NSUInteger, YBImageBrowserProgressType) {
|
||||
YBImageBrowserProgressTypeProgress,
|
||||
YBImageBrowserProgressTypeLoad,
|
||||
YBImageBrowserProgressTypeText
|
||||
};
|
||||
|
||||
@interface YBIBLoadingView () {
|
||||
YBImageBrowserProgressType _type;
|
||||
}
|
||||
@property (nonatomic, strong) UILabel *textLabel;
|
||||
@property (nonatomic, strong) UIImageView *imageView;
|
||||
@property (nonatomic, strong) YBIBProgressDrawView *drawView;
|
||||
@property (nonatomic, copy) void(^clickTextLabelBlock)(void);
|
||||
@end
|
||||
|
||||
@implementation YBIBLoadingView
|
||||
|
||||
#pragma mark life cycle
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
if (self) {
|
||||
self.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0];
|
||||
self.userInteractionEnabled = NO;
|
||||
|
||||
[self addSubview:self.drawView];
|
||||
[self addSubview:self.textLabel];
|
||||
[self addSubview:self.imageView];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)updateConstraints {
|
||||
self.textLabel.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
NSLayoutConstraint *layA = [NSLayoutConstraint constraintWithItem:self.textLabel attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeLeft multiplier:1 constant:20];
|
||||
NSLayoutConstraint *layB = [NSLayoutConstraint constraintWithItem:self.textLabel attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeRight multiplier:1 constant:-20];
|
||||
NSLayoutConstraint *layC = [NSLayoutConstraint constraintWithItem:self.textLabel attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeCenterY multiplier:1 constant:0];
|
||||
|
||||
self.imageView.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
NSLayoutConstraint *layE = [NSLayoutConstraint constraintWithItem:self.imageView attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeCenterX multiplier:1 constant:0];
|
||||
NSLayoutConstraint *layF = [NSLayoutConstraint constraintWithItem:self.imageView attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeCenterY multiplier:1 constant:0];
|
||||
|
||||
self.drawView.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
NSLayoutConstraint *layG = [NSLayoutConstraint constraintWithItem:self.drawView attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeCenterX multiplier:1 constant:0];
|
||||
NSLayoutConstraint *layH = [NSLayoutConstraint constraintWithItem:self.drawView attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeCenterY multiplier:1 constant:0];
|
||||
NSLayoutConstraint *layI = [NSLayoutConstraint constraintWithItem:self.drawView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1 constant:50];
|
||||
NSLayoutConstraint *layJ = [NSLayoutConstraint constraintWithItem:self.drawView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1 constant:50];
|
||||
|
||||
[self addConstraints:@[layA, layB, layC, layE, layF, layG, layH, layI, layJ]];
|
||||
[super updateConstraints];
|
||||
}
|
||||
|
||||
#pragma mark public
|
||||
|
||||
- (void)showProgress:(CGFloat)progress {
|
||||
self.userInteractionEnabled = NO;
|
||||
_type = YBImageBrowserProgressTypeProgress;
|
||||
self.drawView.hidden = NO;
|
||||
self.textLabel.hidden = YES;
|
||||
self.imageView.hidden = YES;
|
||||
[self stopImageViewAnimation];
|
||||
|
||||
self.drawView.progress = progress;
|
||||
[self.drawView setNeedsDisplay];
|
||||
}
|
||||
|
||||
- (void)show {
|
||||
self.userInteractionEnabled = NO;
|
||||
_type = YBImageBrowserProgressTypeLoad;
|
||||
self.drawView.hidden = YES;
|
||||
self.textLabel.hidden = YES;
|
||||
self.imageView.hidden = NO;
|
||||
|
||||
[self startImageViewAnimation];
|
||||
[self.drawView setNeedsDisplay];
|
||||
}
|
||||
|
||||
- (void)startImageViewAnimation {
|
||||
CABasicAnimation *ra = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"];
|
||||
ra.toValue = [NSNumber numberWithFloat:M_PI * 2];
|
||||
ra.duration = 1;
|
||||
ra.cumulative = YES;
|
||||
ra.repeatCount = HUGE_VALF;
|
||||
ra.removedOnCompletion = NO;
|
||||
ra.fillMode = kCAFillModeForwards;
|
||||
[self.imageView.layer addAnimation:ra forKey:@"ra"];
|
||||
}
|
||||
|
||||
- (void)stopImageViewAnimation {
|
||||
[self.imageView.layer removeAllAnimations];
|
||||
}
|
||||
|
||||
- (void)showText:(NSString *)text click:(void (^)(void))click {
|
||||
self.userInteractionEnabled = click ? YES : NO;
|
||||
_type = YBImageBrowserProgressTypeText;
|
||||
self.drawView.hidden = YES;
|
||||
self.textLabel.hidden = NO;
|
||||
self.imageView.hidden = YES;
|
||||
[self stopImageViewAnimation];
|
||||
|
||||
self.textLabel.text = text;
|
||||
self.clickTextLabelBlock = click;
|
||||
[self.drawView setNeedsDisplay];
|
||||
}
|
||||
|
||||
#pragma mark - touch event
|
||||
|
||||
- (void)respondsToTapTextlabel {
|
||||
if (self.clickTextLabelBlock) {
|
||||
self.clickTextLabelBlock();
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - getter
|
||||
|
||||
- (YBIBProgressDrawView *)drawView {
|
||||
if (!_drawView) {
|
||||
_drawView = [YBIBProgressDrawView new];
|
||||
_drawView.backgroundColor = [UIColor clearColor];
|
||||
}
|
||||
return _drawView;
|
||||
}
|
||||
|
||||
- (UILabel *)textLabel {
|
||||
if (!_textLabel) {
|
||||
_textLabel = [UILabel new];
|
||||
_textLabel.textColor = [UIColor whiteColor];
|
||||
_textLabel.numberOfLines = 0;
|
||||
_textLabel.font = [UIFont systemFontOfSize:14];
|
||||
_textLabel.textAlignment = NSTextAlignmentCenter;
|
||||
UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(respondsToTapTextlabel)];
|
||||
[_textLabel addGestureRecognizer:tapGesture];
|
||||
_textLabel.userInteractionEnabled = YES;
|
||||
}
|
||||
return _textLabel;
|
||||
}
|
||||
|
||||
- (UIImageView *)imageView {
|
||||
if (!_imageView) {
|
||||
_imageView = [UIImageView new];
|
||||
_imageView.image = [YBIBIconManager sharedManager].loadingImage();
|
||||
_imageView.layer.shadowColor = UIColor.darkGrayColor.CGColor;
|
||||
_imageView.layer.shadowOffset = CGSizeMake(0, 1);
|
||||
_imageView.layer.shadowOpacity = 1;
|
||||
_imageView.layer.shadowRadius = 4;
|
||||
}
|
||||
return _imageView;
|
||||
}
|
||||
|
||||
@end
|
||||
36
Pods/YBImageBrowser/YBImageBrowser/AuxiliaryView/YBIBToastView.h
generated
Normal file
@@ -0,0 +1,36 @@
|
||||
//
|
||||
// YBIBToastView.h
|
||||
// YBImageBrowserDemo
|
||||
//
|
||||
// Created by 波儿菜 on 2019/6/20.
|
||||
// Copyright © 2019 波儿菜. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface UIView (YBIBToast)
|
||||
|
||||
- (void)ybib_showHookToast:(NSString *)text;
|
||||
|
||||
- (void)ybib_showForkToast:(NSString *)text;
|
||||
|
||||
- (void)ybib_hideToast;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
typedef NS_ENUM(NSInteger, YBIBToastType) {
|
||||
YBIBToastTypeNone,
|
||||
YBIBToastTypeHook,
|
||||
YBIBToastTypeFork
|
||||
};
|
||||
|
||||
@interface YBIBToastView : UIView
|
||||
|
||||
- (void)showWithText:(NSString *)text type:(YBIBToastType)type;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
179
Pods/YBImageBrowser/YBImageBrowser/AuxiliaryView/YBIBToastView.m
generated
Normal file
@@ -0,0 +1,179 @@
|
||||
//
|
||||
// YBIBToastView.m
|
||||
// YBImageBrowserDemo
|
||||
//
|
||||
// Created by 波儿菜 on 2019/6/20.
|
||||
// Copyright © 2019 波儿菜. All rights reserved.
|
||||
//
|
||||
|
||||
#import "YBIBToastView.h"
|
||||
#import <objc/runtime.h>
|
||||
|
||||
@interface UIView ()
|
||||
@property (nonatomic, strong, readonly) YBIBToastView *ybib_toast;
|
||||
@end
|
||||
|
||||
@implementation UIView (YBIBToast)
|
||||
|
||||
- (void)ybib_showHookToast:(NSString *)text {
|
||||
[self ybib_showToastWithText:text type:YBIBToastTypeHook hideAfterDelay:1.7];
|
||||
}
|
||||
|
||||
- (void)ybib_showForkToast:(NSString *)text {
|
||||
[self ybib_showToastWithText:text type:YBIBToastTypeFork hideAfterDelay:1.7];
|
||||
}
|
||||
|
||||
- (void)ybib_showToastWithText:(NSString *)text type:(YBIBToastType)type hideAfterDelay:(NSTimeInterval)delay {
|
||||
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(ybib_hideToast) object:nil];
|
||||
|
||||
YBIBToastView *toast = self.ybib_toast;
|
||||
if (!toast.superview) {
|
||||
[self addSubview:toast];
|
||||
toast.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
NSLayoutConstraint *layA = [NSLayoutConstraint constraintWithItem:toast attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeCenterX multiplier:1 constant:0];
|
||||
NSLayoutConstraint *layB = [NSLayoutConstraint constraintWithItem:toast attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeCenterY multiplier:1 constant:0];
|
||||
NSLayoutConstraint *layC = [NSLayoutConstraint constraintWithItem:toast attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationGreaterThanOrEqual toItem:self attribute:NSLayoutAttributeLeft multiplier:1 constant:40];
|
||||
NSLayoutConstraint *layD = [NSLayoutConstraint constraintWithItem:toast attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationLessThanOrEqual toItem:self attribute:NSLayoutAttributeRight multiplier:1 constant:-40];
|
||||
[self addConstraints:@[layA, layB, layC, layD]];
|
||||
}
|
||||
|
||||
[toast showWithText:text type:type];
|
||||
[self performSelector:@selector(ybib_hideToast) withObject:nil afterDelay:delay];
|
||||
}
|
||||
|
||||
- (void)ybib_hideToast {
|
||||
YBIBToastView *toast = self.ybib_toast;
|
||||
if (toast && toast.superview) {
|
||||
[UIView animateWithDuration:0.25 animations:^{
|
||||
toast.alpha = 0;
|
||||
} completion:^(BOOL finished) {
|
||||
[toast removeFromSuperview];
|
||||
toast.alpha = 1;
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
static void *YBIBToastKey = &YBIBToastKey;
|
||||
- (void)setYbib_toast:(YBIBToastView *)ybib_toast {
|
||||
objc_setAssociatedObject(self, YBIBToastKey, ybib_toast, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
||||
}
|
||||
- (YBIBToastView *)ybib_toast {
|
||||
YBIBToastView *toast = objc_getAssociatedObject(self, YBIBToastKey);
|
||||
if (!toast) {
|
||||
toast = [YBIBToastView new];
|
||||
self.ybib_toast = toast;
|
||||
}
|
||||
return toast;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@interface YBIBToastView () {
|
||||
YBIBToastType _type;
|
||||
CAShapeLayer *_shapeLayer;
|
||||
}
|
||||
@property (nonatomic, strong) UILabel *textLabel;
|
||||
@end
|
||||
|
||||
@implementation YBIBToastView
|
||||
|
||||
#pragma mark - life cycle
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
if (self) {
|
||||
self.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.8];
|
||||
self.userInteractionEnabled = NO;
|
||||
self.layer.cornerRadius = 7;
|
||||
|
||||
[self addSubview:self.textLabel];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)updateConstraints {
|
||||
self.textLabel.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
NSLayoutConstraint *layA = [NSLayoutConstraint constraintWithItem:self.textLabel attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeLeft multiplier:1 constant:20];
|
||||
NSLayoutConstraint *layB = [NSLayoutConstraint constraintWithItem:self.textLabel attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeRight multiplier:1 constant:-20];
|
||||
NSLayoutConstraint *layC = [NSLayoutConstraint constraintWithItem:self.textLabel attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeBottom multiplier:1 constant:-15];
|
||||
NSLayoutConstraint *layD = [NSLayoutConstraint constraintWithItem:self.textLabel attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeTop multiplier:1 constant:70];
|
||||
NSLayoutConstraint *layE = [NSLayoutConstraint constraintWithItem:self.textLabel attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationGreaterThanOrEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1 constant:60];
|
||||
[self addConstraints:@[layA, layB, layC, layD, layE]];
|
||||
[super updateConstraints];
|
||||
}
|
||||
|
||||
- (void)layoutSubviews {
|
||||
[super layoutSubviews];
|
||||
[self startAnimation];
|
||||
}
|
||||
|
||||
#pragma mark - animation
|
||||
|
||||
- (void)showWithText:(NSString *)text type:(YBIBToastType)type {
|
||||
self.textLabel.text = text;
|
||||
_type = type;
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
|
||||
- (void)startAnimation {
|
||||
if (_shapeLayer && _shapeLayer.superlayer) {
|
||||
[_shapeLayer removeFromSuperlayer];
|
||||
}
|
||||
_shapeLayer = [CAShapeLayer layer];
|
||||
_shapeLayer.strokeColor = [UIColor whiteColor].CGColor;
|
||||
_shapeLayer.fillColor = [UIColor clearColor].CGColor;
|
||||
_shapeLayer.lineWidth = 5.0;
|
||||
_shapeLayer.lineCap = @"round";
|
||||
_shapeLayer.lineJoin = @"round";
|
||||
_shapeLayer.strokeStart = 0.0;
|
||||
_shapeLayer.strokeEnd = 0.0;
|
||||
|
||||
UIBezierPath *bezierPath = [UIBezierPath bezierPath];
|
||||
CGFloat r = 13.0;
|
||||
CGFloat x = self.bounds.size.width / 2.0;
|
||||
CGFloat y = 38.0;
|
||||
switch (_type) {
|
||||
case YBIBToastTypeHook: {
|
||||
[bezierPath moveToPoint:CGPointMake(x - r - r / 2, y)];
|
||||
[bezierPath addLineToPoint:CGPointMake(x - r / 2, y + r)];
|
||||
[bezierPath addLineToPoint:CGPointMake(x + r * 2 - r / 2, y - r)];
|
||||
}
|
||||
break;
|
||||
case YBIBToastTypeFork: {
|
||||
[bezierPath moveToPoint:CGPointMake(x - r, y - r)];
|
||||
[bezierPath addLineToPoint:CGPointMake(x + r, y + r)];
|
||||
[bezierPath moveToPoint:CGPointMake(x - r, y + r)];
|
||||
[bezierPath addLineToPoint:CGPointMake(x + r, y - r)];
|
||||
}
|
||||
break;
|
||||
default:break;
|
||||
}
|
||||
|
||||
CABasicAnimation *baseAnimation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
|
||||
[baseAnimation setFromValue:@0.0];
|
||||
[baseAnimation setToValue:@1.0];
|
||||
[baseAnimation setDuration:0.3];
|
||||
baseAnimation.removedOnCompletion = NO;
|
||||
baseAnimation.fillMode = kCAFillModeBoth;
|
||||
|
||||
_shapeLayer.path = bezierPath.CGPath;
|
||||
[self.layer addSublayer:_shapeLayer];
|
||||
[_shapeLayer addAnimation:baseAnimation forKey:@"strokeEnd"];
|
||||
}
|
||||
|
||||
#pragma mark - getter
|
||||
|
||||
- (UILabel *)textLabel {
|
||||
if (!_textLabel) {
|
||||
_textLabel = [UILabel new];
|
||||
_textLabel.textColor = [UIColor whiteColor];
|
||||
_textLabel.font = [UIFont systemFontOfSize:14];
|
||||
_textLabel.textAlignment = NSTextAlignmentCenter;
|
||||
_textLabel.numberOfLines = 0;
|
||||
}
|
||||
return _textLabel;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
19
Pods/YBImageBrowser/YBImageBrowser/Base/NSObject+YBImageBrowser.h
generated
Normal file
@@ -0,0 +1,19 @@
|
||||
//
|
||||
// NSObject+YBImageBrowser.h
|
||||
// YBImageBrowserDemo
|
||||
//
|
||||
// Created by 波儿菜 on 2019/9/26.
|
||||
// Copyright © 2019 杨波. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface NSObject (YBImageBrowser)
|
||||
|
||||
@property (nonatomic, assign) CGFloat ybib_originAlpha;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
23
Pods/YBImageBrowser/YBImageBrowser/Base/NSObject+YBImageBrowser.m
generated
Normal file
@@ -0,0 +1,23 @@
|
||||
//
|
||||
// NSObject+YBImageBrowser.m
|
||||
// YBImageBrowserDemo
|
||||
//
|
||||
// Created by 波儿菜 on 2019/9/26.
|
||||
// Copyright © 2019 杨波. All rights reserved.
|
||||
//
|
||||
|
||||
#import "NSObject+YBImageBrowser.h"
|
||||
#import <objc/runtime.h>
|
||||
|
||||
@implementation NSObject (YBImageBrowser)
|
||||
|
||||
static void *YBIBOriginAlphaKey = &YBIBOriginAlphaKey;
|
||||
- (void)setYbib_originAlpha:(CGFloat)ybib_originAlpha {
|
||||
objc_setAssociatedObject(self, YBIBOriginAlphaKey, @(ybib_originAlpha), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
||||
}
|
||||
- (CGFloat)ybib_originAlpha {
|
||||
NSNumber *alpha = objc_getAssociatedObject(self, YBIBOriginAlphaKey);
|
||||
return alpha ? alpha.floatValue : 1;
|
||||
}
|
||||
|
||||
@end
|
||||
46
Pods/YBImageBrowser/YBImageBrowser/Base/YBIBAnimatedTransition.h
generated
Normal file
@@ -0,0 +1,46 @@
|
||||
//
|
||||
// YBIBAnimatedTransition.h
|
||||
// YBImageBrowserDemo
|
||||
//
|
||||
// Created by 波儿菜 on 2019/6/6.
|
||||
// Copyright © 2019 波儿菜. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@protocol YBIBAnimatedTransition <NSObject>
|
||||
@required
|
||||
|
||||
- (void)yb_showTransitioningWithContainer:(UIView *)container startView:(nullable __kindof UIView *)startView startImage:(nullable UIImage *)startImage endFrame:(CGRect)endFrame orientation:(UIDeviceOrientation)orientation completion:(void(^)(void))completion;
|
||||
|
||||
- (void)yb_hideTransitioningWithContainer:(UIView *)container startView:(nullable __kindof UIView *)startView endView:(UIView *)endView orientation:(UIDeviceOrientation)orientation completion:(void(^)(void))completion;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
typedef NS_ENUM(NSInteger, YBIBTransitionType) {
|
||||
/// 无动效
|
||||
YBIBTransitionTypeNone,
|
||||
/// 渐隐
|
||||
YBIBTransitionTypeFade,
|
||||
/// 连贯移动
|
||||
YBIBTransitionTypeCoherent
|
||||
};
|
||||
|
||||
@interface YBIBAnimatedTransition : NSObject <YBIBAnimatedTransition>
|
||||
|
||||
/// 入场动效类型
|
||||
@property (nonatomic, assign) YBIBTransitionType showType;
|
||||
/// 出场动效类型
|
||||
@property (nonatomic, assign) YBIBTransitionType hideType;
|
||||
|
||||
/// 入场动效持续时间
|
||||
@property (nonatomic, assign) NSTimeInterval showDuration;
|
||||
/// 出场动效持续时间
|
||||
@property (nonatomic, assign) NSTimeInterval hideDuration;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
175
Pods/YBImageBrowser/YBImageBrowser/Base/YBIBAnimatedTransition.m
generated
Normal file
@@ -0,0 +1,175 @@
|
||||
//
|
||||
// YBIBAnimatedTransition.m
|
||||
// YBImageBrowserDemo
|
||||
//
|
||||
// Created by 波儿菜 on 2019/6/6.
|
||||
// Copyright © 2019 波儿菜. All rights reserved.
|
||||
//
|
||||
|
||||
#import "YBIBAnimatedTransition.h"
|
||||
|
||||
extern CGFloat YBIBRotationAngle(UIDeviceOrientation startOrientation, UIDeviceOrientation endOrientation);
|
||||
|
||||
@implementation YBIBAnimatedTransition
|
||||
|
||||
#pragma mark - life cycle
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_showType = _hideType = YBIBTransitionTypeCoherent;
|
||||
_showDuration = _hideDuration = 0.25;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark - <YBIBAnimationHandler>
|
||||
|
||||
- (void)yb_showTransitioningWithContainer:(UIView *)container startView:(__kindof UIView *)startView startImage:(UIImage *)startImage endFrame:(CGRect)endFrame orientation:(UIDeviceOrientation)orientation completion:(void (^)(void))completion {
|
||||
YBIBTransitionType type = self.showType;
|
||||
if (type == YBIBTransitionTypeCoherent) {
|
||||
if (CGRectIsEmpty(endFrame) || !startView || orientation != (UIDeviceOrientation)UIApplication.sharedApplication.statusBarOrientation) {
|
||||
type = YBIBTransitionTypeFade;
|
||||
}
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case YBIBTransitionTypeNone: {
|
||||
completion();
|
||||
}
|
||||
break;
|
||||
case YBIBTransitionTypeFade: {
|
||||
|
||||
BOOL animateValid = !CGRectIsEmpty(endFrame) && startView;
|
||||
|
||||
UIImageView *animateImageView;
|
||||
if (animateValid) {
|
||||
animateImageView = [self imageViewAssimilateToView:startView];
|
||||
animateImageView.frame = endFrame;
|
||||
animateImageView.image = startImage;
|
||||
[container addSubview:animateImageView];
|
||||
}
|
||||
|
||||
CGFloat rawAlpha = container.alpha;
|
||||
container.alpha = 0;
|
||||
|
||||
if (!animateValid) completion();
|
||||
|
||||
[UIView animateWithDuration:self.showDuration animations:^{
|
||||
container.alpha = rawAlpha;
|
||||
} completion:^(BOOL finished) {
|
||||
if (animateValid) {
|
||||
[animateImageView removeFromSuperview];
|
||||
completion();
|
||||
}
|
||||
}];
|
||||
|
||||
}
|
||||
break;
|
||||
case YBIBTransitionTypeCoherent: {
|
||||
|
||||
UIImageView *animateImageView = [self imageViewAssimilateToView:startView];
|
||||
animateImageView.frame = [startView convertRect:startView.bounds toView:container];
|
||||
animateImageView.image = startImage;
|
||||
|
||||
[container addSubview:animateImageView];
|
||||
|
||||
UIColor *rawBackgroundColor = container.backgroundColor;
|
||||
container.backgroundColor = [rawBackgroundColor colorWithAlphaComponent:0];
|
||||
|
||||
[UIView animateWithDuration:self.showDuration animations:^{
|
||||
animateImageView.frame = endFrame;
|
||||
container.backgroundColor = rawBackgroundColor;
|
||||
} completion:^(BOOL finished) {
|
||||
completion();
|
||||
// Disappear smoothly.
|
||||
[UIView animateWithDuration:0.2 animations:^{
|
||||
animateImageView.alpha = 0;
|
||||
} completion:^(BOOL finished) {
|
||||
[animateImageView removeFromSuperview];
|
||||
}];
|
||||
}];
|
||||
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)yb_hideTransitioningWithContainer:(UIView *)container startView:(__kindof UIView *)startView endView:(UIView *)endView orientation:(UIDeviceOrientation)orientation completion:(void (^)(void))completion {
|
||||
YBIBTransitionType type = self.hideType;
|
||||
if (type == YBIBTransitionTypeCoherent && (!startView || !endView)) {
|
||||
type = YBIBTransitionTypeFade;
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case YBIBTransitionTypeNone: {
|
||||
completion();
|
||||
}
|
||||
break;
|
||||
case YBIBTransitionTypeFade: {
|
||||
|
||||
CGFloat rawAlpha = container.alpha;
|
||||
|
||||
[UIView animateWithDuration:self.hideDuration animations:^{
|
||||
container.alpha = 0;
|
||||
} completion:^(BOOL finished) {
|
||||
completion();
|
||||
container.alpha = rawAlpha;
|
||||
}];
|
||||
|
||||
}
|
||||
break;
|
||||
case YBIBTransitionTypeCoherent: {
|
||||
|
||||
CGRect startFrame = startView.frame;
|
||||
CGRect endFrame = [endView convertRect:endView.bounds toView:startView.superview];
|
||||
|
||||
UIColor *rawBackgroundColor = container.backgroundColor;
|
||||
|
||||
[UIView animateWithDuration:self.hideDuration animations:^{
|
||||
|
||||
container.backgroundColor = [rawBackgroundColor colorWithAlphaComponent:0];
|
||||
|
||||
startView.contentMode = endView.contentMode;
|
||||
|
||||
CGAffineTransform transform = startView.transform;
|
||||
UIDeviceOrientation statusBarOrientation = (UIDeviceOrientation)UIApplication.sharedApplication.statusBarOrientation;
|
||||
if (orientation != statusBarOrientation) {
|
||||
transform = CGAffineTransformRotate(transform, YBIBRotationAngle(orientation, statusBarOrientation));
|
||||
}
|
||||
|
||||
if ([startView isKindOfClass:UIImageView.self]) {
|
||||
startView.frame = endFrame;
|
||||
startView.transform = transform;
|
||||
} else {
|
||||
CGFloat scale = MAX(endFrame.size.width / startFrame.size.width, endFrame.size.height / startFrame.size.height);
|
||||
startView.center = CGPointMake(endFrame.size.width * startView.layer.anchorPoint.x + endFrame.origin.x, endFrame.size.height * startView.layer.anchorPoint.y + endFrame.origin.y);
|
||||
startView.transform = CGAffineTransformScale(transform, scale, scale);
|
||||
}
|
||||
|
||||
} completion:^(BOOL finished) {
|
||||
completion();
|
||||
container.backgroundColor = rawBackgroundColor;
|
||||
}];
|
||||
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - private
|
||||
|
||||
- (UIImageView *)imageViewAssimilateToView:(nullable __kindof UIView *)view {
|
||||
UIImageView *animateImageView = [UIImageView new];
|
||||
if ([view isKindOfClass:UIImageView.self]) {
|
||||
animateImageView.contentMode = view.contentMode;
|
||||
} else {
|
||||
animateImageView.contentMode = UIViewContentModeScaleAspectFill;
|
||||
}
|
||||
animateImageView.layer.masksToBounds = view.layer.masksToBounds;
|
||||
animateImageView.layer.cornerRadius = view.layer.cornerRadius;
|
||||
animateImageView.layer.backgroundColor = view.layer.backgroundColor;
|
||||
return animateImageView;
|
||||
}
|
||||
|
||||
@end
|
||||
25
Pods/YBImageBrowser/YBImageBrowser/Base/YBIBCollectionView.h
generated
Normal file
@@ -0,0 +1,25 @@
|
||||
//
|
||||
// YBIBCollectionView.h
|
||||
// YBImageBrowserDemo
|
||||
//
|
||||
// Created by 波儿菜 on 2019/6/6.
|
||||
// Copyright © 2019 波儿菜. All rights reserved.
|
||||
//
|
||||
|
||||
#import "YBIBCollectionViewLayout.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface YBIBCollectionView : UICollectionView
|
||||
|
||||
@property (nonatomic, strong, readonly) YBIBCollectionViewLayout *layout;
|
||||
|
||||
- (NSString *)reuseIdentifierForCellClass:(Class)cellClass;
|
||||
|
||||
- (nullable UICollectionViewCell *)centerCell;
|
||||
|
||||
- (void)scrollToPage:(NSInteger)page;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
83
Pods/YBImageBrowser/YBImageBrowser/Base/YBIBCollectionView.m
generated
Normal file
@@ -0,0 +1,83 @@
|
||||
//
|
||||
// YBIBCollectionView.m
|
||||
// YBImageBrowserDemo
|
||||
//
|
||||
// Created by 波儿菜 on 2019/6/6.
|
||||
// Copyright © 2019 波儿菜. All rights reserved.
|
||||
//
|
||||
|
||||
#import "YBIBCollectionView.h"
|
||||
|
||||
@implementation YBIBCollectionView {
|
||||
NSMutableSet *_reuseSet;
|
||||
}
|
||||
|
||||
#pragma mark - life cycle
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
_layout = [YBIBCollectionViewLayout new];
|
||||
return [self initWithFrame:frame collectionViewLayout:_layout];
|
||||
}
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame collectionViewLayout:(nonnull UICollectionViewLayout *)layout {
|
||||
self = [super initWithFrame:frame collectionViewLayout:layout];
|
||||
if (self) {
|
||||
self.backgroundColor = [UIColor clearColor];
|
||||
self.pagingEnabled = YES;
|
||||
self.showsHorizontalScrollIndicator = NO;
|
||||
self.showsVerticalScrollIndicator = NO;
|
||||
self.alwaysBounceVertical = NO;
|
||||
self.alwaysBounceHorizontal = NO;
|
||||
self.decelerationRate = UIScrollViewDecelerationRateFast;
|
||||
if (@available(iOS 11.0, *)) {
|
||||
self.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
|
||||
}
|
||||
_reuseSet = [NSMutableSet set];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark - public
|
||||
|
||||
- (NSString *)reuseIdentifierForCellClass:(Class)cellClass {
|
||||
NSString *identifier = NSStringFromClass(cellClass);
|
||||
if (![_reuseSet containsObject:identifier]) {
|
||||
NSString *path = [[NSBundle mainBundle] pathForResource:identifier ofType:@"nib"];
|
||||
if (path) {
|
||||
[self registerNib:[UINib nibWithNibName:identifier bundle:nil] forCellWithReuseIdentifier:identifier];
|
||||
} else {
|
||||
[self registerClass:cellClass forCellWithReuseIdentifier:identifier];
|
||||
}
|
||||
[_reuseSet addObject:identifier];
|
||||
}
|
||||
return identifier;
|
||||
}
|
||||
|
||||
- (UICollectionViewCell *)centerCell {
|
||||
NSArray<UICollectionViewCell *> *cells = [self visibleCells];
|
||||
if (cells.count == 0) return nil;
|
||||
|
||||
UICollectionViewCell *res = cells[0];
|
||||
CGFloat centerX = self.contentOffset.x + (self.bounds.size.width / 2.0);
|
||||
for (int i = 1; i < cells.count; ++i) {
|
||||
if (ABS(cells[i].center.x - centerX) < ABS(res.center.x - centerX)) {
|
||||
res = cells[i];
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
- (void)scrollToPage:(NSInteger)page {
|
||||
[self setContentOffset:CGPointMake(self.bounds.size.width * page, 0)];
|
||||
}
|
||||
|
||||
#pragma mark - hit test
|
||||
|
||||
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
|
||||
UIView *view = [super hitTest:point withEvent:event];
|
||||
// When the hit-test view is 'UISlider', set '_scrollEnabled' to 'NO', avoid gesture conflicts.
|
||||
self.scrollEnabled = ![view isKindOfClass:UISlider.class];
|
||||
return view;
|
||||
}
|
||||
|
||||
@end
|
||||
16
Pods/YBImageBrowser/YBImageBrowser/Base/YBIBCollectionViewLayout.h
generated
Normal file
@@ -0,0 +1,16 @@
|
||||
//
|
||||
// YBIBCollectionViewLayout.h
|
||||
// YBImageBrowserDemo
|
||||
//
|
||||
// Created by 杨少 on 2018/4/17.
|
||||
// Copyright © 2018年 波儿菜. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface YBIBCollectionViewLayout : UICollectionViewFlowLayout
|
||||
|
||||
@property (nonatomic, assign) CGFloat distanceBetweenPages;
|
||||
|
||||
@end
|
||||
|
||||
44
Pods/YBImageBrowser/YBImageBrowser/Base/YBIBCollectionViewLayout.m
generated
Normal file
@@ -0,0 +1,44 @@
|
||||
//
|
||||
// YBIBCollectionViewLayout.m
|
||||
// YBImageBrowserDemo
|
||||
//
|
||||
// Created by 杨少 on 2018/4/17.
|
||||
// Copyright © 2018年 波儿菜. All rights reserved.
|
||||
//
|
||||
|
||||
#import "YBIBCollectionViewLayout.h"
|
||||
|
||||
@implementation YBIBCollectionViewLayout
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
self.minimumLineSpacing = 0;
|
||||
self.minimumInteritemSpacing = 0;
|
||||
self.sectionInset = UIEdgeInsetsZero;
|
||||
self.scrollDirection = UICollectionViewScrollDirectionHorizontal;
|
||||
_distanceBetweenPages = 20;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)prepareLayout {
|
||||
[super prepareLayout];
|
||||
self.itemSize = self.collectionView.bounds.size;
|
||||
}
|
||||
|
||||
- (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect {
|
||||
NSArray<UICollectionViewLayoutAttributes *> *layoutAttsArray = [[NSArray alloc] initWithArray:[super layoutAttributesForElementsInRect:rect] copyItems:YES];
|
||||
CGFloat halfWidth = self.collectionView.bounds.size.width / 2.0;
|
||||
CGFloat centerX = self.collectionView.contentOffset.x + halfWidth;
|
||||
[layoutAttsArray enumerateObjectsUsingBlock:^(UICollectionViewLayoutAttributes * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
|
||||
obj.center = CGPointMake(obj.center.x + (obj.center.x - centerX) / halfWidth * self.distanceBetweenPages / 2, obj.center.y);
|
||||
}];
|
||||
return layoutAttsArray;
|
||||
}
|
||||
|
||||
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds {
|
||||
return YES;
|
||||
}
|
||||
|
||||
@end
|
||||
17
Pods/YBImageBrowser/YBImageBrowser/Base/YBIBContainerView.h
generated
Normal file
@@ -0,0 +1,17 @@
|
||||
//
|
||||
// YBIBContainerView.h
|
||||
// YBImageBrowserDemo
|
||||
//
|
||||
// Created by 波儿菜 on 2019/7/11.
|
||||
// Copyright © 2019 杨波. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface YBIBContainerView : UIView
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
22
Pods/YBImageBrowser/YBImageBrowser/Base/YBIBContainerView.m
generated
Normal file
@@ -0,0 +1,22 @@
|
||||
//
|
||||
// YBIBContainerView.m
|
||||
// YBImageBrowserDemo
|
||||
//
|
||||
// Created by 波儿菜 on 2019/7/11.
|
||||
// Copyright © 2019 杨波. All rights reserved.
|
||||
//
|
||||
|
||||
#import "YBIBContainerView.h"
|
||||
|
||||
@implementation YBIBContainerView
|
||||
|
||||
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
|
||||
UIView *originView = [super hitTest:point withEvent:event];
|
||||
if ([originView isKindOfClass:self.class]) {
|
||||
// Continue hit-testing if the view is kind of 'self.class'.
|
||||
return nil;
|
||||
}
|
||||
return originView;
|
||||
}
|
||||
|
||||
@end
|
||||
31
Pods/YBImageBrowser/YBImageBrowser/Base/YBIBDataMediator.h
generated
Normal file
@@ -0,0 +1,31 @@
|
||||
//
|
||||
// YBIBDataMediator.h
|
||||
// YBImageBrowserDemo
|
||||
//
|
||||
// Created by 波儿菜 on 2019/6/6.
|
||||
// Copyright © 2019 波儿菜. All rights reserved.
|
||||
//
|
||||
|
||||
#import "YBImageBrowser.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface YBIBDataMediator : NSObject
|
||||
|
||||
- (instancetype)initWithBrowser:(YBImageBrowser *)browser;
|
||||
|
||||
@property (nonatomic, assign) NSUInteger dataCacheCountLimit;
|
||||
|
||||
- (NSInteger)numberOfCells;
|
||||
|
||||
- (id<YBIBDataProtocol>)dataForCellAtIndex:(NSInteger)index;
|
||||
|
||||
- (void)clear;
|
||||
|
||||
@property (nonatomic, assign) NSUInteger preloadCount;
|
||||
|
||||
- (void)preloadWithPage:(NSInteger)page;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
69
Pods/YBImageBrowser/YBImageBrowser/Base/YBIBDataMediator.m
generated
Normal file
@@ -0,0 +1,69 @@
|
||||
//
|
||||
// YBIBDataMediator.m
|
||||
// YBImageBrowserDemo
|
||||
//
|
||||
// Created by 波儿菜 on 2019/6/6.
|
||||
// Copyright © 2019 波儿菜. All rights reserved.
|
||||
//
|
||||
|
||||
#import "YBIBDataMediator.h"
|
||||
#import "YBImageBrowser+Internal.h"
|
||||
|
||||
@implementation YBIBDataMediator {
|
||||
__weak YBImageBrowser *_browser;
|
||||
NSCache<NSNumber *, id<YBIBDataProtocol>> *_dataCache;
|
||||
}
|
||||
|
||||
#pragma mark - life cycle
|
||||
|
||||
- (instancetype)initWithBrowser:(YBImageBrowser *)browser {
|
||||
if (self = [super init]) {
|
||||
_browser = browser;
|
||||
_dataCache = [NSCache new];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark - public
|
||||
|
||||
- (NSInteger)numberOfCells {
|
||||
return _browser.dataSource ? [_browser.dataSource yb_numberOfCellsInImageBrowser:_browser] : _browser.dataSourceArray.count;
|
||||
}
|
||||
|
||||
- (id<YBIBDataProtocol>)dataForCellAtIndex:(NSInteger)index {
|
||||
if (index < 0 || index > self.numberOfCells - 1) return nil;
|
||||
|
||||
id<YBIBDataProtocol> data = [_dataCache objectForKey:@(index)];
|
||||
if (!data) {
|
||||
data = _browser.dataSource ? [_browser.dataSource yb_imageBrowser:_browser dataForCellAtIndex:index] : _browser.dataSourceArray[index];
|
||||
[_dataCache setObject:data forKey:@(index)];
|
||||
[_browser implementGetBaseInfoProtocol:data];
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
- (void)clear {
|
||||
[_dataCache removeAllObjects];
|
||||
}
|
||||
|
||||
- (void)preloadWithPage:(NSInteger)page {
|
||||
if (_preloadCount == 0) return;
|
||||
|
||||
NSInteger left = -(_preloadCount / 2), right = _preloadCount - ABS(left);
|
||||
for (NSInteger i = left; i <= right; ++i) {
|
||||
if (i == 0) continue;
|
||||
id<YBIBDataProtocol> targetData = [self dataForCellAtIndex:page + i];
|
||||
if ([targetData respondsToSelector:@selector(yb_preload)]) {
|
||||
[targetData yb_preload];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - getters & setters
|
||||
|
||||
- (void)setDataCacheCountLimit:(NSUInteger)dataCacheCountLimit {
|
||||
_dataCacheCountLimit = dataCacheCountLimit;
|
||||
_dataCache.countLimit = dataCacheCountLimit;
|
||||
}
|
||||
|
||||
@end
|
||||
37
Pods/YBImageBrowser/YBImageBrowser/Base/YBIBScreenRotationHandler.h
generated
Normal file
@@ -0,0 +1,37 @@
|
||||
//
|
||||
// YBIBScreenRotationHandler.h
|
||||
// YBImageBrowserDemo
|
||||
//
|
||||
// Created by 波儿菜 on 2019/6/8.
|
||||
// Copyright © 2019 波儿菜. All rights reserved.
|
||||
//
|
||||
|
||||
#import "YBImageBrowser.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface YBIBScreenRotationHandler : NSObject
|
||||
|
||||
- (instancetype)initWithBrowser:(YBImageBrowser *)browser;
|
||||
|
||||
- (void)startObserveStatusBarOrientation;
|
||||
|
||||
- (void)startObserveDeviceOrientation;
|
||||
|
||||
- (void)clear;
|
||||
|
||||
- (void)configContainerSize:(CGSize)size;
|
||||
|
||||
- (CGSize)containerSizeWithOrientation:(UIDeviceOrientation)orientation;
|
||||
|
||||
@property (nonatomic, assign, readonly, getter=isRotating) BOOL rotating;
|
||||
|
||||
@property (nonatomic, assign, readonly) UIDeviceOrientation currentOrientation;
|
||||
|
||||
@property (nonatomic, assign) UIInterfaceOrientationMask supportedOrientations;
|
||||
|
||||
@property (nonatomic, assign) NSTimeInterval rotationDuration;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
328
Pods/YBImageBrowser/YBImageBrowser/Base/YBIBScreenRotationHandler.m
generated
Normal file
@@ -0,0 +1,328 @@
|
||||
//
|
||||
// YBIBScreenRotationHandler.m
|
||||
// YBImageBrowserDemo
|
||||
//
|
||||
// Created by 波儿菜 on 2019/6/8.
|
||||
// Copyright © 2019 波儿菜. All rights reserved.
|
||||
//
|
||||
|
||||
#import "YBIBScreenRotationHandler.h"
|
||||
#import "YBIBUtilities.h"
|
||||
#import "YBIBCellProtocol.h"
|
||||
#import "YBImageBrowser+Internal.h"
|
||||
|
||||
BOOL YBIBValidDeviceOrientation(UIDeviceOrientation orientation) {
|
||||
static NSSet *validSet;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
validSet = [NSSet setWithObjects:@(UIDeviceOrientationPortrait), @(UIDeviceOrientationPortraitUpsideDown), @(UIDeviceOrientationLandscapeLeft), @(UIDeviceOrientationLandscapeRight), nil];
|
||||
});
|
||||
return [validSet containsObject:@(orientation)];
|
||||
}
|
||||
|
||||
CGFloat YBIBRotationAngle(UIDeviceOrientation startOrientation, UIDeviceOrientation endOrientation) {
|
||||
static NSDictionary<NSNumber*, NSNumber*> *angleMap;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
angleMap = @{@(UIDeviceOrientationPortrait):@(0), @(UIDeviceOrientationPortraitUpsideDown):@(M_PI), @(UIDeviceOrientationLandscapeLeft):@(M_PI_2), @(UIDeviceOrientationLandscapeRight): @(-M_PI_2)};
|
||||
});
|
||||
NSNumber *start = angleMap[@(startOrientation)], *end = angleMap[@(endOrientation)];
|
||||
CGFloat res = CGFLOAT_IS_DOUBLE ? end.doubleValue - start.doubleValue : end.floatValue - start.floatValue;
|
||||
if (ABS(res) > M_PI) {
|
||||
return res > 0 ? res - M_PI * 2 : M_PI * 2 + res;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
static NSUInteger const kMaskNull = 10000;
|
||||
|
||||
@interface YBIBScreenRotationHandler ()
|
||||
@property (nonatomic, assign) BOOL rotating;
|
||||
@property (nonatomic, assign) UIDeviceOrientation currentOrientation;
|
||||
@end
|
||||
|
||||
@implementation YBIBScreenRotationHandler {
|
||||
__weak YBImageBrowser *_browser;
|
||||
CGSize _verticalContainerSize;
|
||||
CGSize _horizontalContainerSize;
|
||||
NSInteger _recordPage;
|
||||
}
|
||||
|
||||
#pragma mark - life cycle
|
||||
|
||||
- (void)dealloc {
|
||||
[self clear];
|
||||
}
|
||||
|
||||
- (instancetype)initWithBrowser:(YBImageBrowser *)browser {
|
||||
if (self = [super init]) {
|
||||
_browser = browser;
|
||||
_rotating = NO;
|
||||
_supportedOrientations = UIInterfaceOrientationMaskAllButUpsideDown;
|
||||
_rotationDuration = 0.25;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark - public
|
||||
|
||||
- (void)startObserveStatusBarOrientation {
|
||||
self.currentOrientation = (UIDeviceOrientation)UIApplication.sharedApplication.statusBarOrientation;
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidChangedStatusBarOrientationNotification:) name:UIApplicationDidChangeStatusBarOrientationNotification object:nil];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationWillChangeStatusBarOrientationNotification:) name:UIApplicationWillChangeStatusBarOrientationNotification object:nil];
|
||||
}
|
||||
|
||||
- (void)startObserveDeviceOrientation {
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(deviceOrientationDidChangeNotification:) name:UIDeviceOrientationDidChangeNotification object:nil];
|
||||
}
|
||||
|
||||
- (void)clear {
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
||||
}
|
||||
|
||||
- (void)configContainerSize:(CGSize)size {
|
||||
if (UIDeviceOrientationIsLandscape(self.currentOrientation)) {
|
||||
// Now is horizontal.
|
||||
_verticalContainerSize = CGSizeMake(size.height, size.width);
|
||||
_horizontalContainerSize = size;
|
||||
} else {
|
||||
// Now is vertical.
|
||||
_verticalContainerSize = size;
|
||||
_horizontalContainerSize = CGSizeMake(size.height, size.width);
|
||||
}
|
||||
}
|
||||
|
||||
- (CGSize)containerSizeWithOrientation:(UIDeviceOrientation)orientation {
|
||||
return UIDeviceOrientationIsLandscape(orientation) ? _horizontalContainerSize : _verticalContainerSize;
|
||||
}
|
||||
|
||||
#pragma mark - private
|
||||
|
||||
- (BOOL)supportedOfOrientation:(UIDeviceOrientation)orientation {
|
||||
if (!YBIBValidDeviceOrientation(orientation)) return NO;
|
||||
NSMutableSet *set = [NSMutableSet set];
|
||||
if (_supportedOrientations & UIInterfaceOrientationMaskPortrait) [set addObject:@(UIDeviceOrientationPortrait)];
|
||||
if (_supportedOrientations & UIInterfaceOrientationMaskPortraitUpsideDown) [set addObject:@(UIDeviceOrientationPortraitUpsideDown)];
|
||||
if (_supportedOrientations & UIInterfaceOrientationMaskLandscapeRight) [set addObject:@(UIDeviceOrientationLandscapeLeft)];
|
||||
if (_supportedOrientations & UIInterfaceOrientationMaskLandscapeLeft) [set addObject:@(UIDeviceOrientationLandscapeRight)];
|
||||
return [set containsObject:@(orientation)];
|
||||
}
|
||||
|
||||
- (BOOL)supportedOnlyOneSystemOrientation {
|
||||
UIInterfaceOrientationMask mask = [self supportSystemOrientationMask];
|
||||
return mask == (mask & (-mask));
|
||||
}
|
||||
|
||||
- (void)orientationWillChangeWithExpectOrientation:(UIDeviceOrientation)orientation centerCell:(UICollectionViewCell<YBIBCellProtocol> *)centerCell {
|
||||
if ([centerCell respondsToSelector:@selector(yb_orientationWillChangeWithExpectOrientation:)]) {
|
||||
[centerCell yb_orientationWillChangeWithExpectOrientation:orientation];
|
||||
}
|
||||
for (id<YBIBToolViewHandler> handler in _browser.toolViewHandlers) {
|
||||
if ([handler respondsToSelector:@selector(yb_orientationWillChangeWithExpectOrientation:)]) {
|
||||
[handler yb_orientationWillChangeWithExpectOrientation:orientation];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)orientationChangeAnimationWithExpectOrientation:(UIDeviceOrientation)orientation centerCell:(UICollectionViewCell<YBIBCellProtocol> *)centerCell {
|
||||
if ([centerCell respondsToSelector:@selector(yb_orientationChangeAnimationWithExpectOrientation:)]) {
|
||||
[centerCell yb_orientationChangeAnimationWithExpectOrientation:orientation];
|
||||
[centerCell layoutIfNeeded]; // Compatible with autolayout.
|
||||
}
|
||||
for (id<YBIBToolViewHandler> handler in _browser.toolViewHandlers) {
|
||||
if ([handler respondsToSelector:@selector(yb_orientationChangeAnimationWithExpectOrientation:)]) {
|
||||
[handler yb_orientationChangeAnimationWithExpectOrientation:orientation];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)orientationDidChangedWithOrientation:(UIDeviceOrientation)orientation centerCell:(UICollectionViewCell<YBIBCellProtocol> *)centerCell {
|
||||
if ([centerCell respondsToSelector:@selector(yb_orientationDidChangedWithOrientation:)]) {
|
||||
[centerCell yb_orientationDidChangedWithOrientation:orientation];
|
||||
}
|
||||
for (id<YBIBToolViewHandler> handler in _browser.toolViewHandlers) {
|
||||
if ([handler respondsToSelector:@selector(yb_orientationDidChangedWithOrientation:)]) {
|
||||
[handler yb_orientationDidChangedWithOrientation:orientation];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - event
|
||||
|
||||
- (void)deviceOrientationDidChangeNotification:(NSNotification *)note {
|
||||
if (![self supportedOnlyOneSystemOrientation]) return;
|
||||
if (_browser.isTransitioning || self.rotating) return;
|
||||
|
||||
UIDeviceOrientation expectOrientation = [UIDevice currentDevice].orientation;
|
||||
if (expectOrientation == self.currentOrientation || ![self supportedOfOrientation:expectOrientation]) return;
|
||||
|
||||
self.rotating = YES;
|
||||
|
||||
// Align.
|
||||
[_browser.collectionView scrollToPage:_browser.currentPage];
|
||||
// Record current page number before transforming.
|
||||
NSInteger currentPage = _browser.currentPage;
|
||||
|
||||
UIDeviceOrientation statusBarOrientation = (UIDeviceOrientation)UIApplication.sharedApplication.statusBarOrientation;
|
||||
CGFloat angleStatusBarToExpect = YBIBRotationAngle(statusBarOrientation, expectOrientation);
|
||||
CGFloat angleCurrentToExpect = YBIBRotationAngle(_currentOrientation, expectOrientation);
|
||||
CGRect expectBounds = (CGRect){CGPointZero, [self containerSizeWithOrientation:expectOrientation]};
|
||||
UICollectionViewCell<YBIBCellProtocol> *centerCell = (UICollectionViewCell<YBIBCellProtocol> *)self->_browser.collectionView.centerCell;
|
||||
// Animate smoothly if bigger rotation angle.
|
||||
NSTimeInterval duration = self.rotationDuration * (ABS(angleCurrentToExpect) > M_PI_2 ? 2 : 1);
|
||||
|
||||
// 'collectionView' transformation.
|
||||
self->_browser.collectionView.bounds = expectBounds;
|
||||
self->_browser.collectionView.transform = CGAffineTransformMakeRotation(angleStatusBarToExpect);
|
||||
centerCell.contentView.transform = CGAffineTransformMakeRotation(-angleCurrentToExpect);
|
||||
|
||||
// Reset to prevent the page number change after transforming.
|
||||
[self->_browser.collectionView scrollToPage:currentPage];
|
||||
|
||||
[self orientationWillChangeWithExpectOrientation:expectOrientation centerCell:centerCell];
|
||||
|
||||
[UIView animateWithDuration:duration delay:0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
|
||||
|
||||
// Maybe the internal UI need to transform.
|
||||
[self orientationChangeAnimationWithExpectOrientation:expectOrientation centerCell:centerCell];
|
||||
|
||||
centerCell.contentView.bounds = expectBounds;
|
||||
centerCell.contentView.transform = CGAffineTransformIdentity;
|
||||
|
||||
self->_browser.containerView.bounds = expectBounds;
|
||||
self->_browser.containerView.transform = CGAffineTransformMakeRotation(angleStatusBarToExpect);
|
||||
|
||||
} completion:^(BOOL finished) {
|
||||
self.currentOrientation = expectOrientation;
|
||||
self.rotating = NO;
|
||||
|
||||
[self orientationDidChangedWithOrientation:expectOrientation centerCell:centerCell];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)applicationWillChangeStatusBarOrientationNotification:(NSNotification *)noti {
|
||||
if ([self supportedOnlyOneSystemOrientation]) return;
|
||||
|
||||
self.rotating = YES;
|
||||
// Record current page number before transforming.
|
||||
_recordPage = _browser.currentPage;
|
||||
|
||||
UICollectionViewCell<YBIBCellProtocol> *centerCell = (UICollectionViewCell<YBIBCellProtocol> *)self->_browser.collectionView.centerCell;
|
||||
UIDeviceOrientation expectOrientation = ((NSNumber *)noti.userInfo[UIApplicationStatusBarOrientationUserInfoKey]).integerValue;
|
||||
[self orientationWillChangeWithExpectOrientation:expectOrientation centerCell:centerCell];
|
||||
}
|
||||
|
||||
- (void)applicationDidChangedStatusBarOrientationNotification:(NSNotification *)noti {
|
||||
if ([self supportedOnlyOneSystemOrientation]) return;
|
||||
|
||||
UIDeviceOrientation expectOrientation = (UIDeviceOrientation)UIApplication.sharedApplication.statusBarOrientation;
|
||||
UICollectionViewCell<YBIBCellProtocol> *centerCell = (UICollectionViewCell<YBIBCellProtocol> *)self->_browser.collectionView.centerCell;
|
||||
|
||||
[self orientationChangeAnimationWithExpectOrientation:expectOrientation centerCell:centerCell];
|
||||
|
||||
CGRect expectBounds = (CGRect){CGPointZero, [self containerSizeWithOrientation:expectOrientation]};
|
||||
self->_browser.collectionView.layout.itemSize = expectBounds.size;
|
||||
|
||||
// Reset to prevent the page number change after transforming.
|
||||
[_browser.collectionView scrollToPage:_recordPage];
|
||||
|
||||
self.currentOrientation = expectOrientation;
|
||||
self.rotating = NO;
|
||||
|
||||
[self orientationDidChangedWithOrientation:expectOrientation centerCell:centerCell];
|
||||
}
|
||||
|
||||
#pragma mark - getters & setters
|
||||
|
||||
- (void)setRotating:(BOOL)rotating {
|
||||
_rotating = rotating;
|
||||
_browser.containerView.userInteractionEnabled = !rotating;
|
||||
_browser.collectionView.userInteractionEnabled = !rotating;
|
||||
_browser.collectionView.panGestureRecognizer.enabled = !rotating;
|
||||
}
|
||||
|
||||
#pragma mark - calculate supported orientation of system
|
||||
|
||||
- (UIInterfaceOrientationMask)supportSystemOrientationMask {
|
||||
UIInterfaceOrientationMask limitMask = 0;
|
||||
// IphoneX series do not support UIInterfaceOrientationMaskPortraitUpsideDown, except selector '-application:supportedInterfaceOrientationsForWindow:' of '[UIApplication sharedApplication].delegate' return 0. Maybe it is BUG of Apple.
|
||||
BOOL ignoreUpsideDownIfIphoneX = YES;
|
||||
UIInterfaceOrientationMask delegateMask = [self maskOfApplicationDelegate];
|
||||
if (delegateMask != kMaskNull) {
|
||||
if (delegateMask == 0) {
|
||||
// Apple do.
|
||||
limitMask = UIInterfaceOrientationMaskAll;
|
||||
ignoreUpsideDownIfIphoneX = NO;
|
||||
} else {
|
||||
limitMask = delegateMask;
|
||||
}
|
||||
} else {
|
||||
// Lower priority.
|
||||
limitMask = [self maskOfInfoPlist];
|
||||
}
|
||||
|
||||
UIInterfaceOrientationMask supportMask = limitMask & [self maskOfViewController];
|
||||
|
||||
if (ignoreUpsideDownIfIphoneX && YBIBIsIphoneXSeries() && (supportMask & UIInterfaceOrientationMaskPortraitUpsideDown)) {
|
||||
supportMask ^= UIInterfaceOrientationMaskPortraitUpsideDown;
|
||||
}
|
||||
return supportMask;
|
||||
}
|
||||
|
||||
- (UIInterfaceOrientationMask)maskOfInfoPlist {
|
||||
// 'Info.plist' will not change in a process.
|
||||
static UIInterfaceOrientationMask mask = 0;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
NSString *path = [[NSBundle mainBundle] pathForResource:@"Info" ofType:@"plist"];
|
||||
NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithContentsOfFile:path];
|
||||
NSArray *array = dict[@"UISupportedInterfaceOrientations"];
|
||||
NSSet *set = [NSSet setWithArray:array];
|
||||
if ([set containsObject:@"UIInterfaceOrientationPortrait"]) mask |= UIInterfaceOrientationMaskPortrait;
|
||||
if ([set containsObject:@"UIInterfaceOrientationLandscapeRight"]) mask |= UIInterfaceOrientationMaskLandscapeRight;
|
||||
if ([set containsObject:@"UIInterfaceOrientationLandscapeLeft"]) mask |= UIInterfaceOrientationMaskLandscapeLeft;
|
||||
if ([set containsObject:@"UIInterfaceOrientationPortraitUpsideDown"]) mask |= UIInterfaceOrientationMaskPortraitUpsideDown;
|
||||
});
|
||||
return mask == 0 ? kMaskNull : mask;
|
||||
}
|
||||
|
||||
- (UIInterfaceOrientationMask)maskOfApplicationDelegate {
|
||||
UIInterfaceOrientationMask mask = kMaskNull;
|
||||
id<UIApplicationDelegate> delegate = [UIApplication sharedApplication].delegate;
|
||||
if ([delegate respondsToSelector:@selector(application:supportedInterfaceOrientationsForWindow:)]) {
|
||||
mask = [delegate application:[UIApplication sharedApplication] supportedInterfaceOrientationsForWindow:_browser.window];
|
||||
}
|
||||
return mask;
|
||||
}
|
||||
|
||||
- (UIInterfaceOrientationMask)maskOfViewController {
|
||||
UIInterfaceOrientationMask mask = kMaskNull;
|
||||
|
||||
// Find the UIViewController whitch 'browser' followed.
|
||||
UIViewController *target = nil;
|
||||
id next = _browser;
|
||||
while (next) {
|
||||
if ([next isKindOfClass:UIViewController.self]) {
|
||||
target = next;
|
||||
break;
|
||||
}
|
||||
if ([next isKindOfClass:UIWindow.self]) {
|
||||
target = YBIBTopControllerByWindow(next);
|
||||
break;
|
||||
}
|
||||
next = [next nextResponder];
|
||||
}
|
||||
|
||||
// Cover directly.
|
||||
if (target.tabBarController) {
|
||||
mask = target.tabBarController.shouldAutorotate ? target.tabBarController.supportedInterfaceOrientations : 0;
|
||||
} else if (target.navigationController) {
|
||||
mask = target.navigationController.shouldAutorotate ? target.navigationController.supportedInterfaceOrientations : 0;
|
||||
} else {
|
||||
mask = target.shouldAutorotate ? target.supportedInterfaceOrientations : 0;
|
||||
}
|
||||
return mask;
|
||||
}
|
||||
|
||||
@end
|
||||
24
Pods/YBImageBrowser/YBImageBrowser/Base/YBImageBrowser+Internal.h
generated
Normal file
@@ -0,0 +1,24 @@
|
||||
//
|
||||
// YBImageBrowser+Internal.h
|
||||
// YBImageBrowserDemo
|
||||
//
|
||||
// Created by 波儿菜 on 2019/7/1.
|
||||
// Copyright © 2019 杨波. All rights reserved.
|
||||
//
|
||||
|
||||
#import "YBImageBrowser.h"
|
||||
#import "YBIBContainerView.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface YBImageBrowser ()
|
||||
|
||||
@property (nonatomic, strong) YBIBContainerView *containerView;
|
||||
|
||||
- (void)implementGetBaseInfoProtocol:(id<YBIBGetBaseInfoProtocol>)obj;
|
||||
|
||||
@property (nonatomic, weak, nullable) NSObject *hiddenProjectiveView;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
57
Pods/YBImageBrowser/YBImageBrowser/Helper/YBIBCopywriter.h
generated
Normal file
@@ -0,0 +1,57 @@
|
||||
//
|
||||
// YBIBCopywriter.h
|
||||
// YBImageBrowserDemo
|
||||
//
|
||||
// Created by 波儿菜 on 2018/9/13.
|
||||
// Copyright © 2018年 波儿菜. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
typedef NS_ENUM(NSInteger, YBIBCopywriterType) {
|
||||
/// 简体中文
|
||||
YBIBCopywriterTypeSimplifiedChinese,
|
||||
/// 英文
|
||||
YBIBCopywriterTypeEnglish
|
||||
};
|
||||
|
||||
/**
|
||||
文案管理类
|
||||
*/
|
||||
@interface YBIBCopywriter : NSObject
|
||||
|
||||
/**
|
||||
唯一有效单例
|
||||
*/
|
||||
+ (instancetype)sharedCopywriter;
|
||||
|
||||
/// 语言类型
|
||||
@property (nonatomic, assign) YBIBCopywriterType type;
|
||||
|
||||
#pragma - 以下文案可更改
|
||||
|
||||
@property (nonatomic, copy) NSString *videoIsInvalid;
|
||||
|
||||
@property (nonatomic, copy) NSString *videoError;
|
||||
|
||||
@property (nonatomic, copy) NSString *unableToSave;
|
||||
|
||||
@property (nonatomic, copy) NSString *imageIsInvalid;
|
||||
|
||||
@property (nonatomic, copy) NSString *downloadFailed;
|
||||
|
||||
@property (nonatomic, copy) NSString *getPhotoAlbumAuthorizationFailed;
|
||||
|
||||
@property (nonatomic, copy) NSString *saveToPhotoAlbumSuccess;
|
||||
|
||||
@property (nonatomic, copy) NSString *saveToPhotoAlbumFailed;
|
||||
|
||||
@property (nonatomic, copy) NSString *saveToPhotoAlbum;
|
||||
|
||||
@property (nonatomic, copy) NSString *cancel;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
65
Pods/YBImageBrowser/YBImageBrowser/Helper/YBIBCopywriter.m
generated
Normal file
@@ -0,0 +1,65 @@
|
||||
//
|
||||
// YBIBCopywriter.m
|
||||
// YBImageBrowserDemo
|
||||
//
|
||||
// Created by 波儿菜 on 2018/9/13.
|
||||
// Copyright © 2018年 波儿菜. All rights reserved.
|
||||
//
|
||||
|
||||
#import "YBIBCopywriter.h"
|
||||
|
||||
@implementation YBIBCopywriter
|
||||
|
||||
#pragma mark - life cycle
|
||||
|
||||
+ (instancetype)sharedCopywriter {
|
||||
static YBIBCopywriter *copywriter = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
copywriter = [YBIBCopywriter new];
|
||||
});
|
||||
return copywriter;
|
||||
}
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_type = YBIBCopywriterTypeSimplifiedChinese;
|
||||
NSArray *appleLanguages = [[NSUserDefaults standardUserDefaults] objectForKey:@"AppleLanguages"];
|
||||
if (appleLanguages && appleLanguages.count > 0) {
|
||||
NSString *languages = appleLanguages[0];
|
||||
if (![languages hasPrefix:@"zh-Hans"]) {
|
||||
_type = YBIBCopywriterTypeEnglish;
|
||||
}
|
||||
}
|
||||
|
||||
[self initCopy];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark - private
|
||||
|
||||
- (void)initCopy {
|
||||
BOOL en = self.type == YBIBCopywriterTypeEnglish;
|
||||
|
||||
self.videoIsInvalid = en ? @"Video is invalid" : @"视频无效";
|
||||
self.videoError = en ? @"Video error" : @"视频错误";
|
||||
self.unableToSave = en ? @"Unable to save" : @"无法保存";
|
||||
self.imageIsInvalid = en ? @"Image is invalid" : @"图片无效";
|
||||
self.downloadFailed = en ? @"Download failed" : @"加载图片失败";
|
||||
self.getPhotoAlbumAuthorizationFailed = en ? @"Failed to get album authorization" : @"获取相册权限失败";
|
||||
self.saveToPhotoAlbumSuccess = en ? @"Save successful" : @"已保存到系统相册";
|
||||
self.saveToPhotoAlbumFailed = en ? @"Save failed" : @"保存失败";
|
||||
self.saveToPhotoAlbum = en ? @"Save" : @"保存到相册";
|
||||
self.cancel = en ? @"Cancel" : @"取消";
|
||||
}
|
||||
|
||||
#pragma mark - public
|
||||
|
||||
- (void)setType:(YBIBCopywriterType)type {
|
||||
_type = type;
|
||||
[self initCopy];
|
||||
}
|
||||
|
||||
@end
|
||||
63
Pods/YBImageBrowser/YBImageBrowser/Helper/YBIBIconManager.h
generated
Normal file
@@ -0,0 +1,63 @@
|
||||
//
|
||||
// YBIBIconManager.h
|
||||
// YBImageBrowserDemo
|
||||
//
|
||||
// Created by 波儿菜 on 2018/8/29.
|
||||
// Copyright © 2018年 波儿菜. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface UIImage (YBImageBrowser)
|
||||
|
||||
/**
|
||||
获取图片便利构造方法
|
||||
|
||||
@param name 图片名字
|
||||
@param bundle 资源对象
|
||||
@return 图片实例
|
||||
*/
|
||||
+ (instancetype)ybib_imageNamed:(NSString *)name bundle:(NSBundle *)bundle;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
/// 获取图片闭包
|
||||
typedef UIImage * _Nullable (^YBIBIconBlock)(void);
|
||||
|
||||
/**
|
||||
图标管理类
|
||||
*/
|
||||
@interface YBIBIconManager : NSObject
|
||||
|
||||
/**
|
||||
唯一有效单例
|
||||
*/
|
||||
+ (instancetype)sharedManager;
|
||||
|
||||
#pragma - 以下图片可更改
|
||||
|
||||
/// 基本-加载
|
||||
@property (nonatomic, copy) YBIBIconBlock loadingImage;
|
||||
|
||||
/// 工具视图-保存
|
||||
@property (nonatomic, copy) YBIBIconBlock toolSaveImage;
|
||||
/// 工具视图-更多
|
||||
@property (nonatomic, copy) YBIBIconBlock toolMoreImage;
|
||||
|
||||
/// 视频-播放
|
||||
@property (nonatomic, copy) YBIBIconBlock videoPlayImage;
|
||||
/// 视频-暂停
|
||||
@property (nonatomic, copy) YBIBIconBlock videoPauseImage;
|
||||
/// 视频-取消
|
||||
@property (nonatomic, copy) YBIBIconBlock videoCancelImage;
|
||||
/// 视频-播放大图
|
||||
@property (nonatomic, copy) YBIBIconBlock videoBigPlayImage;
|
||||
/// 视频-拖动圆点
|
||||
@property (nonatomic, copy) YBIBIconBlock videoDragCircleImage;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
138
Pods/YBImageBrowser/YBImageBrowser/Helper/YBIBIconManager.m
generated
Normal file
@@ -0,0 +1,138 @@
|
||||
//
|
||||
// YBIBIconManager.m
|
||||
// YBImageBrowserDemo
|
||||
//
|
||||
// Created by 波儿菜 on 2018/8/29.
|
||||
// Copyright © 2018年 波儿菜. All rights reserved.
|
||||
//
|
||||
|
||||
#import "YBIBIconManager.h"
|
||||
|
||||
// The best order for path scale search.
|
||||
static NSArray *_NSBundlePreferredScales() {
|
||||
static NSArray *scales;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
CGFloat screenScale = [UIScreen mainScreen].scale;
|
||||
if (screenScale <= 1) {
|
||||
scales = @[@1,@2,@3];
|
||||
} else if (screenScale <= 2) {
|
||||
scales = @[@2,@3,@1];
|
||||
} else {
|
||||
scales = @[@3,@2,@1];
|
||||
}
|
||||
});
|
||||
return scales;
|
||||
}
|
||||
|
||||
// Add scale modifier to the file name (without path extension), from @"name" to @"name@2x".
|
||||
static NSString *_NSStringByAppendingNameScale(NSString *string, CGFloat scale) {
|
||||
if (!string) return nil;
|
||||
if (fabs(scale - 1) <= __FLT_EPSILON__ || string.length == 0 || [string hasSuffix:@"/"]) return string.copy;
|
||||
return [string stringByAppendingFormat:@"@%@x", @(scale)];
|
||||
}
|
||||
|
||||
@implementation UIImage (YBImageBrowser)
|
||||
|
||||
+ (instancetype)ybib_imageNamed:(NSString *)name bundle:(NSBundle *)bundle {
|
||||
if (name.length == 0) return nil;
|
||||
if ([name hasSuffix:@"/"]) return nil;
|
||||
|
||||
NSString *res = name.stringByDeletingPathExtension;
|
||||
NSString *ext = name.pathExtension;
|
||||
NSString *path = nil;
|
||||
CGFloat scale = 1;
|
||||
|
||||
// If no extension, guess by system supported (same as UIImage).
|
||||
NSArray *exts = ext.length > 0 ? @[ext] : @[@"", @"png", @"jpeg", @"jpg", @"gif", @"webp", @"apng"];
|
||||
NSArray *scales = _NSBundlePreferredScales();
|
||||
for (int s = 0; s < scales.count; s++) {
|
||||
scale = ((NSNumber *)scales[s]).floatValue;
|
||||
NSString *scaledName = _NSStringByAppendingNameScale(res, scale);
|
||||
for (NSString *e in exts) {
|
||||
path = [bundle pathForResource:scaledName ofType:e];
|
||||
if (path) break;
|
||||
}
|
||||
if (path) break;
|
||||
}
|
||||
if (path.length == 0) {
|
||||
// Assets.xcassets supported.
|
||||
return [self imageNamed:name];
|
||||
}
|
||||
|
||||
NSData *data = [NSData dataWithContentsOfFile:path];
|
||||
if (data.length == 0) return nil;
|
||||
|
||||
return [[self alloc] initWithData:data scale:scale];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
NSBundle *YBIBDefaultBundle(void) {
|
||||
static NSBundle *bundle = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
NSBundle *_bundle = [NSBundle bundleForClass:NSClassFromString(@"YBImageBrowser")];
|
||||
NSString *path = [_bundle pathForResource:@"YBImageBrowser" ofType:@"bundle"];
|
||||
bundle = [NSBundle bundleWithPath:path];
|
||||
});
|
||||
return bundle;
|
||||
}
|
||||
|
||||
NSBundle *YBIBVideoBundle(void) {
|
||||
static NSBundle *bundle = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
NSBundle *_bundle = [NSBundle bundleForClass:NSClassFromString(@"YBImageBrowser")];
|
||||
NSString *path = [_bundle pathForResource:@"YBImageBrowserVideo" ofType:@"bundle"];
|
||||
bundle = [NSBundle bundleWithPath:path];
|
||||
});
|
||||
return bundle;
|
||||
}
|
||||
|
||||
@implementation YBIBIconManager
|
||||
|
||||
+ (instancetype)sharedManager {
|
||||
static YBIBIconManager *manager = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
manager = [YBIBIconManager new];
|
||||
});
|
||||
return manager;
|
||||
}
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_loadingImage = ^UIImage * _Nullable{
|
||||
return [UIImage ybib_imageNamed:@"ybib_loading" bundle:YBIBDefaultBundle()];
|
||||
};
|
||||
|
||||
_toolSaveImage = ^UIImage * _Nullable{
|
||||
return [UIImage ybib_imageNamed:@"ybib_save" bundle:YBIBDefaultBundle()];
|
||||
};
|
||||
_toolMoreImage = ^UIImage * _Nullable{
|
||||
return [UIImage ybib_imageNamed:@"ybib_more" bundle:YBIBDefaultBundle()];
|
||||
};
|
||||
|
||||
_videoPlayImage = ^UIImage * _Nullable{
|
||||
return [UIImage ybib_imageNamed:@"ybib_play" bundle:YBIBVideoBundle()];
|
||||
};
|
||||
_videoPauseImage = ^UIImage * _Nullable{
|
||||
return [UIImage ybib_imageNamed:@"ybib_pause" bundle:YBIBVideoBundle()];
|
||||
};
|
||||
_videoCancelImage = ^UIImage * _Nullable{
|
||||
return [UIImage ybib_imageNamed:@"ybib_cancel" bundle:YBIBVideoBundle()];
|
||||
};
|
||||
_videoBigPlayImage = ^UIImage * _Nullable{
|
||||
return [UIImage ybib_imageNamed:@"ybib_bigPlay" bundle:YBIBVideoBundle()];
|
||||
};
|
||||
_videoDragCircleImage = ^UIImage * _Nullable{
|
||||
return [UIImage ybib_imageNamed:@"ybib_circlePoint" bundle:YBIBVideoBundle()];
|
||||
};
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
@end
|
||||
32
Pods/YBImageBrowser/YBImageBrowser/Helper/YBIBPhotoAlbumManager.h
generated
Normal file
@@ -0,0 +1,32 @@
|
||||
//
|
||||
// YBIBPhotoAlbumManager.h
|
||||
// YBImageBrowserDemo
|
||||
//
|
||||
// Created by 波儿菜 on 2018/8/28.
|
||||
// Copyright © 2018年 波儿菜. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Photos/Photos.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface YBIBPhotoAlbumManager : NSObject
|
||||
|
||||
/**
|
||||
Get photo album authorization.
|
||||
*/
|
||||
+ (void)getPhotoAlbumAuthorizationSuccess:(void(^)(void))success failed:(void(^)(void))failed;
|
||||
|
||||
/**
|
||||
Get 'AVAsset' by 'PHAsset' asynchronously, callback is in child thread.
|
||||
*/
|
||||
+ (void)getAVAssetWithPHAsset:(PHAsset *)phAsset completion:(void(^)(AVAsset * _Nullable asset))completion;
|
||||
|
||||
/**
|
||||
Get 'ImageData' by 'PHAsset' synchronously, callback is in child thread.
|
||||
*/
|
||||
+ (void)getImageDataWithPHAsset:(PHAsset *)phAsset completion:(void(^)(NSData * _Nullable data))completion;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
65
Pods/YBImageBrowser/YBImageBrowser/Helper/YBIBPhotoAlbumManager.m
generated
Normal file
@@ -0,0 +1,65 @@
|
||||
//
|
||||
// YBIBPhotoAlbumManager.m
|
||||
// YBImageBrowserDemo
|
||||
//
|
||||
// Created by 波儿菜 on 2018/8/28.
|
||||
// Copyright © 2018年 波儿菜. All rights reserved.
|
||||
//
|
||||
|
||||
#import "YBIBPhotoAlbumManager.h"
|
||||
#import "YBIBUtilities.h"
|
||||
|
||||
@implementation YBIBPhotoAlbumManager
|
||||
|
||||
+ (void)getAVAssetWithPHAsset:(PHAsset *)phAsset completion:(nonnull void (^)(AVAsset * _Nullable))completion {
|
||||
PHVideoRequestOptions *options = [PHVideoRequestOptions new];
|
||||
options.networkAccessAllowed = YES;
|
||||
[[PHImageManager defaultManager] requestAVAssetForVideo:phAsset options:options resultHandler:^(AVAsset * _Nullable asset, AVAudioMix * _Nullable audioMix, NSDictionary * _Nullable info) {
|
||||
completion(asset);
|
||||
}];
|
||||
}
|
||||
|
||||
+ (void)getImageDataWithPHAsset:(PHAsset *)phAsset completion:(nonnull void (^)(NSData * _Nullable))completion {
|
||||
PHImageRequestOptions *options = [PHImageRequestOptions new];
|
||||
options.networkAccessAllowed = YES;
|
||||
options.resizeMode = PHImageRequestOptionsResizeModeNone;
|
||||
// Only when this property is YES, the callback will in child thread.
|
||||
options.synchronous = YES;
|
||||
[[PHImageManager defaultManager] requestImageDataForAsset:phAsset options:options resultHandler:^(NSData * _Nullable imageData, NSString * _Nullable dataUTI, UIImageOrientation orientation, NSDictionary * _Nullable info) {
|
||||
BOOL complete = ![[info objectForKey:PHImageCancelledKey] boolValue] && ![info objectForKey:PHImageErrorKey] && ![[info objectForKey:PHImageResultIsDegradedKey] boolValue];
|
||||
if (complete && imageData) {
|
||||
completion(imageData);
|
||||
} else {
|
||||
completion(nil);
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
+ (void)getPhotoAlbumAuthorizationSuccess:(void(^)(void))success failed:(void(^)(void))failed {
|
||||
PHAuthorizationStatus status = [PHPhotoLibrary authorizationStatus];
|
||||
switch (status) {
|
||||
case PHAuthorizationStatusDenied:
|
||||
if (failed) failed();
|
||||
break;
|
||||
case PHAuthorizationStatusRestricted:
|
||||
if (failed) failed();
|
||||
break;
|
||||
case PHAuthorizationStatusNotDetermined: {
|
||||
[PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status){
|
||||
YBIB_DISPATCH_ASYNC_MAIN(^{
|
||||
if (status == PHAuthorizationStatusAuthorized) {
|
||||
if (success) success();
|
||||
} else {
|
||||
if (failed) failed();
|
||||
}
|
||||
})
|
||||
}];
|
||||
}
|
||||
break;
|
||||
case PHAuthorizationStatusAuthorized:
|
||||
if (success) success();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
29
Pods/YBImageBrowser/YBImageBrowser/Helper/YBIBSentinel.h
generated
Normal file
@@ -0,0 +1,29 @@
|
||||
//
|
||||
// YBIBSentinel.h
|
||||
// YBImageBrowserDemo
|
||||
//
|
||||
// Created by 波儿菜 on 2019/6/18.
|
||||
// Copyright © 2019 波儿菜. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
Thread safe.
|
||||
*/
|
||||
@interface YBIBSentinel : NSObject
|
||||
|
||||
/// Returns the current value of the counter.
|
||||
@property (readonly) int32_t value;
|
||||
|
||||
/**
|
||||
Increase the value atomically.
|
||||
@return The new value.
|
||||
*/
|
||||
- (int32_t)increase;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
24
Pods/YBImageBrowser/YBImageBrowser/Helper/YBIBSentinel.m
generated
Normal file
@@ -0,0 +1,24 @@
|
||||
//
|
||||
// YBIBSentinel.m
|
||||
// YBImageBrowserDemo
|
||||
//
|
||||
// Created by 波儿菜 on 2019/6/18.
|
||||
// Copyright © 2019 波儿菜. All rights reserved.
|
||||
//
|
||||
|
||||
#import "YBIBSentinel.h"
|
||||
#import <libkern/OSAtomic.h>
|
||||
|
||||
@implementation YBIBSentinel {
|
||||
int32_t _value;
|
||||
}
|
||||
|
||||
- (int32_t)value {
|
||||
return _value;
|
||||
}
|
||||
|
||||
- (int32_t)increase {
|
||||
return OSAtomicIncrement32(&_value);
|
||||
}
|
||||
|
||||
@end
|
||||
52
Pods/YBImageBrowser/YBImageBrowser/Helper/YBIBUtilities.h
generated
Normal file
@@ -0,0 +1,52 @@
|
||||
//
|
||||
// YBIBUtilities.h
|
||||
// YBImageBrowserDemo
|
||||
//
|
||||
// Created by 杨少 on 2018/4/11.
|
||||
// Copyright © 2018年 波儿菜. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
|
||||
#define YBIB_DISPATCH_ASYNC(queue, block)\
|
||||
if (strcmp(dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL), dispatch_queue_get_label(queue)) == 0) {\
|
||||
block();\
|
||||
} else {\
|
||||
dispatch_async(queue, block);\
|
||||
}
|
||||
|
||||
#define YBIB_DISPATCH_ASYNC_MAIN(block) YBIB_DISPATCH_ASYNC(dispatch_get_main_queue(), block)
|
||||
|
||||
|
||||
#define YBIB_CODE_EXEC_TIME(KEY, ...) \
|
||||
CFAbsoluteTime startTime = CFAbsoluteTimeGetCurrent(); \
|
||||
__VA_ARGS__ \
|
||||
CFAbsoluteTime linkTime = (CFAbsoluteTimeGetCurrent() - startTime); \
|
||||
NSLog(@"%@ Time-Consuming: %fms", KEY, linkTime * 1000.0);
|
||||
|
||||
|
||||
UIWindow * _Nonnull YBIBNormalWindow(void);
|
||||
|
||||
UIViewController * _Nullable YBIBTopController(void);
|
||||
UIViewController * _Nullable YBIBTopControllerByWindow(UIWindow *);
|
||||
|
||||
BOOL YBIBLowMemory(void);
|
||||
|
||||
BOOL YBIBIsIphoneXSeries(void);
|
||||
CGFloat YBIBStatusbarHeight(void);
|
||||
CGFloat YBIBSafeAreaBottomHeight(void);
|
||||
|
||||
UIImage *YBIBSnapshotView(UIView *);
|
||||
|
||||
/// This is orientation of 'YBImageBrowser' not 'UIDevice'.
|
||||
UIEdgeInsets YBIBPaddingByBrowserOrientation(UIDeviceOrientation);
|
||||
|
||||
|
||||
@interface YBIBUtilities : NSObject
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
134
Pods/YBImageBrowser/YBImageBrowser/Helper/YBIBUtilities.m
generated
Normal file
@@ -0,0 +1,134 @@
|
||||
//
|
||||
// YBIBUtilities.m
|
||||
// YBImageBrowserDemo
|
||||
//
|
||||
// Created by 杨少 on 2018/4/11.
|
||||
// Copyright © 2018年 波儿菜. All rights reserved.
|
||||
//
|
||||
|
||||
#import "YBIBUtilities.h"
|
||||
#import <sys/utsname.h>
|
||||
|
||||
|
||||
UIWindow * _Nullable YBIBNormalWindow(void) {
|
||||
UIWindow *window = [[UIApplication sharedApplication] keyWindow];
|
||||
if (window.windowLevel != UIWindowLevelNormal) {
|
||||
NSArray *windows = [[UIApplication sharedApplication] windows];
|
||||
for(UIWindow *temp in windows) {
|
||||
if (temp.windowLevel == UIWindowLevelNormal) {
|
||||
return temp;
|
||||
}
|
||||
}
|
||||
}
|
||||
return window;
|
||||
}
|
||||
|
||||
UIViewController * _Nullable YBIBTopController(void) {
|
||||
return YBIBTopControllerByWindow(YBIBNormalWindow());
|
||||
}
|
||||
|
||||
UIViewController * _Nullable YBIBTopControllerByWindow(UIWindow *window) {
|
||||
if (!window) return nil;
|
||||
|
||||
UIViewController *top = nil;
|
||||
id nextResponder;
|
||||
if (window.subviews.count > 0) {
|
||||
UIView *frontView = [window.subviews objectAtIndex:0];
|
||||
nextResponder = frontView.nextResponder;
|
||||
}
|
||||
if (nextResponder && [nextResponder isKindOfClass:UIViewController.class]) {
|
||||
top = nextResponder;
|
||||
} else {
|
||||
top = window.rootViewController;
|
||||
}
|
||||
|
||||
while ([top isKindOfClass:UITabBarController.class] || [top isKindOfClass:UINavigationController.class] || top.presentedViewController) {
|
||||
if ([top isKindOfClass:UITabBarController.class]) {
|
||||
top = ((UITabBarController *)top).selectedViewController;
|
||||
} else if ([top isKindOfClass:UINavigationController.class]) {
|
||||
top = ((UINavigationController *)top).topViewController;
|
||||
} else if (top.presentedViewController) {
|
||||
top = top.presentedViewController;
|
||||
}
|
||||
}
|
||||
return top;
|
||||
}
|
||||
|
||||
BOOL YBIBLowMemory(void) {
|
||||
static BOOL lowMemory = NO;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
unsigned long long physicalMemory = [[NSProcessInfo processInfo] physicalMemory];
|
||||
lowMemory = physicalMemory > 0 && physicalMemory < 1024 * 1024 * 1500;
|
||||
});
|
||||
return lowMemory;
|
||||
}
|
||||
|
||||
BOOL YBIBIsIphoneXSeries(void) {
|
||||
return YBIBStatusbarHeight() > 20;
|
||||
}
|
||||
|
||||
CGFloat YBIBStatusbarHeight(void) {
|
||||
CGFloat height = 0;
|
||||
if (@available(iOS 11.0, *)) {
|
||||
height = UIApplication.sharedApplication.delegate.window.safeAreaInsets.top;
|
||||
}
|
||||
if (height <= 0) {
|
||||
height = UIApplication.sharedApplication.statusBarFrame.size.height;
|
||||
}
|
||||
if (height <= 0) {
|
||||
height = 20;
|
||||
}
|
||||
return height;
|
||||
}
|
||||
|
||||
CGFloat YBIBSafeAreaBottomHeight(void) {
|
||||
CGFloat bottom = 0;
|
||||
if (@available(iOS 11.0, *)) {
|
||||
bottom = UIApplication.sharedApplication.delegate.window.safeAreaInsets.bottom;
|
||||
}
|
||||
return bottom;
|
||||
}
|
||||
|
||||
UIImage *YBIBSnapshotView(UIView *view) {
|
||||
UIGraphicsBeginImageContextWithOptions(view.bounds.size, YES, [UIScreen mainScreen].scale);
|
||||
[view drawViewHierarchyInRect:view.bounds afterScreenUpdates:NO];
|
||||
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
|
||||
UIGraphicsEndImageContext();
|
||||
return image;
|
||||
}
|
||||
|
||||
UIEdgeInsets YBIBPaddingByBrowserOrientation(UIDeviceOrientation orientation) {
|
||||
UIEdgeInsets padding = UIEdgeInsetsZero;
|
||||
if (!YBIBIsIphoneXSeries()) return padding;
|
||||
|
||||
UIDeviceOrientation barOrientation = (UIDeviceOrientation)UIApplication.sharedApplication.statusBarOrientation;
|
||||
|
||||
if (UIDeviceOrientationIsLandscape(orientation)) {
|
||||
BOOL same = orientation == barOrientation;
|
||||
BOOL reverse = !same && UIDeviceOrientationIsLandscape(barOrientation);
|
||||
if (same) {
|
||||
padding.bottom = YBIBSafeAreaBottomHeight();
|
||||
padding.top = 0;
|
||||
} else if (reverse) {
|
||||
padding.top = YBIBSafeAreaBottomHeight();
|
||||
padding.bottom = 0;
|
||||
}
|
||||
padding.left = padding.right = MAX(YBIBSafeAreaBottomHeight(), YBIBStatusbarHeight());
|
||||
} else {
|
||||
if (orientation == UIDeviceOrientationPortrait) {
|
||||
padding.top = YBIBStatusbarHeight();
|
||||
padding.bottom = barOrientation == UIDeviceOrientationPortrait ? YBIBSafeAreaBottomHeight() : 0;
|
||||
} else {
|
||||
padding.bottom = YBIBStatusbarHeight();
|
||||
padding.top = barOrientation == UIDeviceOrientationPortrait ? YBIBSafeAreaBottomHeight() : 0;
|
||||
}
|
||||
padding.left = padding.right = UIDeviceOrientationIsLandscape(barOrientation) ? YBIBSafeAreaBottomHeight() : 0 ;
|
||||
}
|
||||
return padding;
|
||||
}
|
||||
|
||||
|
||||
@implementation YBIBUtilities
|
||||
|
||||
@end
|
||||
33
Pods/YBImageBrowser/YBImageBrowser/Image/YBIBImageCache+Internal.h
generated
Normal file
@@ -0,0 +1,33 @@
|
||||
//
|
||||
// YBIBImageCache+Internal.h
|
||||
// YBImageBrowserDemo
|
||||
//
|
||||
// Created by 波儿菜 on 2019/6/13.
|
||||
// Copyright © 2019 波儿菜. All rights reserved.
|
||||
//
|
||||
|
||||
#import "YBIBImageCache.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
typedef NS_ENUM(NSInteger, YBIBImageCacheType) {
|
||||
YBIBImageCacheTypeOrigin,
|
||||
YBIBImageCacheTypeCompressed
|
||||
};
|
||||
|
||||
/**
|
||||
Not thread safe.
|
||||
*/
|
||||
@interface YBIBImageCache ()
|
||||
|
||||
- (void)setImage:(UIImage *)image type:(YBIBImageCacheType)type forKey:(NSString *)key resident:(BOOL)resident;
|
||||
|
||||
- (nullable UIImage *)imageForKey:(NSString *)key type:(YBIBImageCacheType)type;
|
||||
|
||||
- (void)removeForKey:(NSString *)key;
|
||||
|
||||
- (void)removeResidentForKey:(NSString *)key;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
30
Pods/YBImageBrowser/YBImageBrowser/Image/YBIBImageCache.h
generated
Normal file
@@ -0,0 +1,30 @@
|
||||
//
|
||||
// YBIBImageCache.h
|
||||
// YBImageBrowserDemo
|
||||
//
|
||||
// Created by 波儿菜 on 2019/6/13.
|
||||
// Copyright © 2019 波儿菜. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class YBIBImageCache;
|
||||
|
||||
@interface NSObject (YBIBImageCache)
|
||||
|
||||
/// 图片浏览器持有的图片缓存管理类
|
||||
@property (nonatomic, strong, readonly) YBIBImageCache *ybib_imageCache;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@interface YBIBImageCache : NSObject
|
||||
|
||||
/// 缓存数量限制(一个单位表示一个 YBIBImageData 产生的所有图片数据)
|
||||
@property (nonatomic, assign) NSUInteger imageCacheCountLimit;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
112
Pods/YBImageBrowser/YBImageBrowser/Image/YBIBImageCache.m
generated
Normal file
@@ -0,0 +1,112 @@
|
||||
//
|
||||
// YBIBImageCache.m
|
||||
// YBImageBrowserDemo
|
||||
//
|
||||
// Created by 波儿菜 on 2019/6/13.
|
||||
// Copyright © 2019 波儿菜. All rights reserved.
|
||||
//
|
||||
|
||||
#import "YBIBImageCache.h"
|
||||
#import "YBIBImageCache+Internal.h"
|
||||
#import "YBIBUtilities.h"
|
||||
#import <objc/runtime.h>
|
||||
|
||||
|
||||
@implementation NSObject (YBIBImageCache)
|
||||
static void *YBIBImageCacheKey = &YBIBImageCacheKey;
|
||||
- (void)setYbib_imageCache:(YBIBImageCache *)ybib_imageCache {
|
||||
objc_setAssociatedObject(self, YBIBImageCacheKey, ybib_imageCache, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
||||
}
|
||||
- (YBIBImageCache *)ybib_imageCache {
|
||||
YBIBImageCache *cache = objc_getAssociatedObject(self, YBIBImageCacheKey);
|
||||
if (!cache) {
|
||||
cache = [YBIBImageCache new];
|
||||
self.ybib_imageCache = cache;
|
||||
}
|
||||
return cache;
|
||||
}
|
||||
@end
|
||||
|
||||
|
||||
@interface YBIBImageCachePack : NSObject
|
||||
@property (nonatomic, strong) UIImage *originImage;
|
||||
@property (nonatomic, strong) UIImage *compressedImage;
|
||||
@end
|
||||
@implementation YBIBImageCachePack
|
||||
@end
|
||||
|
||||
|
||||
@implementation YBIBImageCache {
|
||||
NSCache<NSString *, YBIBImageCachePack *> *_imageCache;
|
||||
NSMutableDictionary<NSString *, YBIBImageCachePack *> *_residentCache;
|
||||
}
|
||||
|
||||
#pragma mark - life cycle
|
||||
|
||||
- (void)dealloc {
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
|
||||
}
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_imageCache = [NSCache new];
|
||||
_imageCache.countLimit = _imageCacheCountLimit = YBIBLowMemory() ? 6 : 12;
|
||||
_residentCache = [NSMutableDictionary dictionary];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveMemoryWarning:) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark - event
|
||||
|
||||
- (void)didReceiveMemoryWarning:(NSNotification *)notification {
|
||||
[_imageCache removeAllObjects];
|
||||
[_residentCache removeAllObjects];
|
||||
}
|
||||
|
||||
#pragma mark - public
|
||||
|
||||
- (void)setImage:(UIImage *)image type:(YBIBImageCacheType)type forKey:(NSString *)key resident:(BOOL)resident {
|
||||
YBIBImageCachePack *pack = [_imageCache objectForKey:key];
|
||||
if (!pack) {
|
||||
pack = [YBIBImageCachePack new];
|
||||
[_imageCache setObject:pack forKey:key];
|
||||
}
|
||||
switch (type) {
|
||||
case YBIBImageCacheTypeOrigin:
|
||||
pack.originImage = image;
|
||||
break;
|
||||
case YBIBImageCacheTypeCompressed:
|
||||
pack.compressedImage = image;
|
||||
break;
|
||||
}
|
||||
[_residentCache setObject:pack forKey:key];
|
||||
}
|
||||
|
||||
- (UIImage *)imageForKey:(NSString *)key type:(YBIBImageCacheType)type {
|
||||
YBIBImageCachePack *pack = [_imageCache objectForKey:key] ?: [_residentCache objectForKey:key];
|
||||
switch (type) {
|
||||
case YBIBImageCacheTypeOrigin: return pack.originImage;
|
||||
case YBIBImageCacheTypeCompressed: return pack.compressedImage;
|
||||
default: return nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)removeForKey:(NSString *)key {
|
||||
[_imageCache removeObjectForKey:key];
|
||||
[_residentCache removeObjectForKey:key];
|
||||
}
|
||||
|
||||
- (void)removeResidentForKey:(NSString *)key {
|
||||
[_residentCache removeObjectForKey:key];
|
||||
}
|
||||
|
||||
#pragma mark - setter
|
||||
|
||||
- (void)setImageCacheCountLimit:(NSUInteger)imageCacheCountLimit {
|
||||
_imageCacheCountLimit = imageCacheCountLimit;
|
||||
_imageCache.countLimit = imageCacheCountLimit;
|
||||
}
|
||||
|
||||
@end
|
||||
22
Pods/YBImageBrowser/YBImageBrowser/Image/YBIBImageCell+Internal.h
generated
Normal file
@@ -0,0 +1,22 @@
|
||||
//
|
||||
// YBIBImageCell+Internal.h
|
||||
// YBImageBrowserDemo
|
||||
//
|
||||
// Created by 波儿菜 on 2019/12/23.
|
||||
// Copyright © 2019 杨波. All rights reserved.
|
||||
//
|
||||
|
||||
#import "YBIBImageCell.h"
|
||||
#import "YBIBImageScrollView.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface YBIBImageCell ()
|
||||
|
||||
@property (nonatomic, strong) YBIBImageScrollView *imageScrollView;
|
||||
|
||||
@property (nonatomic, strong) UIImageView *tailoringImageView;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
17
Pods/YBImageBrowser/YBImageBrowser/Image/YBIBImageCell.h
generated
Normal file
@@ -0,0 +1,17 @@
|
||||
//
|
||||
// YBIBImageCell.h
|
||||
// YBImageBrowserDemo
|
||||
//
|
||||
// Created by 波儿菜 on 2019/6/5.
|
||||
// Copyright © 2019 波儿菜. All rights reserved.
|
||||
//
|
||||
|
||||
#import "YBIBCellProtocol.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface YBIBImageCell : UICollectionViewCell <YBIBCellProtocol>
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
522
Pods/YBImageBrowser/YBImageBrowser/Image/YBIBImageCell.m
generated
Normal file
@@ -0,0 +1,522 @@
|
||||
//
|
||||
// YBIBImageCell.m
|
||||
// YBImageBrowserDemo
|
||||
//
|
||||
// Created by 波儿菜 on 2019/6/5.
|
||||
// Copyright © 2019 波儿菜. All rights reserved.
|
||||
//
|
||||
|
||||
#import "YBIBImageCell.h"
|
||||
#import "YBIBImageData.h"
|
||||
#import "YBIBIconManager.h"
|
||||
#import "YBIBImageCell+Internal.h"
|
||||
#import "YBIBImageData+Internal.h"
|
||||
#import "YBIBCopywriter.h"
|
||||
#import "YBIBUtilities.h"
|
||||
|
||||
@interface YBIBImageCell () <YBIBImageDataDelegate, UIScrollViewDelegate, UIGestureRecognizerDelegate>
|
||||
@end
|
||||
|
||||
@implementation YBIBImageCell {
|
||||
CGPoint _interactStartPoint;
|
||||
BOOL _interacting;
|
||||
}
|
||||
|
||||
#pragma mark - life cycle
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
if (self) {
|
||||
[self initValue];
|
||||
[self.contentView addSubview:self.imageScrollView];
|
||||
[self addGesture];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)layoutSubviews {
|
||||
[super layoutSubviews];
|
||||
self.imageScrollView.frame = self.bounds;
|
||||
}
|
||||
|
||||
- (void)initValue {
|
||||
_interactStartPoint = CGPointZero;
|
||||
_interacting = NO;
|
||||
}
|
||||
|
||||
- (void)prepareForReuse {
|
||||
((YBIBImageData *)self.yb_cellData).delegate = nil;
|
||||
[self.imageScrollView reset];
|
||||
[self hideTailoringImageView];
|
||||
[self hideAuxiliaryView];
|
||||
[super prepareForReuse];
|
||||
}
|
||||
|
||||
#pragma mark - <YBIBCellProtocol>
|
||||
|
||||
@synthesize yb_currentOrientation = _yb_currentOrientation;
|
||||
@synthesize yb_containerSize = _yb_containerSize;
|
||||
@synthesize yb_backView = _yb_backView;
|
||||
@synthesize yb_collectionView = _yb_collectionView;
|
||||
@synthesize yb_isTransitioning = _yb_isTransitioning;
|
||||
@synthesize yb_isRotating = _yb_isRotating;
|
||||
@synthesize yb_auxiliaryViewHandler = _yb_auxiliaryViewHandler;
|
||||
@synthesize yb_hideStatusBar = _yb_hideStatusBar;
|
||||
@synthesize yb_hideBrowser = _yb_hideBrowser;
|
||||
@synthesize yb_hideToolViews = _yb_hideToolViews;
|
||||
@synthesize yb_cellData = _yb_cellData;
|
||||
@synthesize yb_cellIsInCenter = _yb_cellIsInCenter;
|
||||
@synthesize yb_selfPage = _yb_selfPage;
|
||||
@synthesize yb_currentPage = _yb_currentPage;
|
||||
|
||||
- (void)setYb_cellData:(id<YBIBDataProtocol>)yb_cellData {
|
||||
_yb_cellData = yb_cellData;
|
||||
((YBIBImageData *)yb_cellData).delegate = self;
|
||||
}
|
||||
|
||||
- (UIView *)yb_foregroundView {
|
||||
return self.imageScrollView.imageView;
|
||||
}
|
||||
|
||||
- (void)yb_orientationWillChangeWithExpectOrientation:(UIDeviceOrientation)orientation {
|
||||
[self hideTailoringImageView];
|
||||
}
|
||||
|
||||
- (void)yb_orientationChangeAnimationWithExpectOrientation:(UIDeviceOrientation)orientation {
|
||||
[self updateImageLayoutWithOrientation:orientation previousImageSize:self.imageScrollView.imageView.image.size];
|
||||
}
|
||||
|
||||
#pragma mark - private
|
||||
|
||||
- (CGSize)contentSizeWithContainerSize:(CGSize)containerSize imageViewFrame:(CGRect)imageViewFrame {
|
||||
return CGSizeMake(MAX(containerSize.width, imageViewFrame.size.width), MAX(containerSize.height, imageViewFrame.size.height));
|
||||
}
|
||||
|
||||
- (void)updateImageLayoutWithOrientation:(UIDeviceOrientation)orientation previousImageSize:(CGSize)previousImageSize {
|
||||
if (_interacting) [self restoreInteractionWithDuration:0];
|
||||
|
||||
YBIBImageData *data = self.yb_cellData;
|
||||
|
||||
CGSize imageSize;
|
||||
|
||||
UIImage *image = self.imageScrollView.imageView.image;
|
||||
YBIBScrollImageType imageType = self.imageScrollView.imageType;
|
||||
if (imageType == YBIBScrollImageTypeCompressed) {
|
||||
imageSize = data.originImage ? data.originImage.size : image.size;
|
||||
} else {
|
||||
imageSize = image.size;
|
||||
}
|
||||
|
||||
CGSize containerSize = self.yb_containerSize(orientation);
|
||||
CGRect imageViewFrame = [data.layout yb_imageViewFrameWithContainerSize:containerSize imageSize:imageSize orientation:orientation];
|
||||
CGSize contentSize = [self contentSizeWithContainerSize:containerSize imageViewFrame:imageViewFrame];
|
||||
CGFloat maxZoomScale = imageType == YBIBScrollImageTypeThumb ? 1 : [data.layout yb_maximumZoomScaleWithContainerSize:containerSize imageSize:imageSize orientation:orientation];
|
||||
|
||||
// 'zoomScale' must set before 'contentSize' and 'imageView.frame'.
|
||||
self.imageScrollView.zoomScale = 1;
|
||||
self.imageScrollView.contentSize = contentSize;
|
||||
self.imageScrollView.minimumZoomScale = 1;
|
||||
self.imageScrollView.maximumZoomScale = maxZoomScale;
|
||||
|
||||
CGFloat scale;
|
||||
if (previousImageSize.width > 0 && previousImageSize.height > 0) {
|
||||
scale = imageSize.width / imageSize.height - previousImageSize.width / previousImageSize.height;
|
||||
} else {
|
||||
scale = 0;
|
||||
}
|
||||
// '0.001' is admissible error.
|
||||
if (ABS(scale) <= 0.001) {
|
||||
self.imageScrollView.imageView.frame = imageViewFrame;
|
||||
} else {
|
||||
[UIView animateWithDuration:0.25 animations:^{
|
||||
self.imageScrollView.imageView.frame = imageViewFrame;
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)cuttingImage {
|
||||
// This method has been delayed called, so 'browser' may be in transit now.
|
||||
if (self.yb_isTransitioning()) return;
|
||||
if (_interacting) return;
|
||||
|
||||
YBIBImageData *data = self.yb_cellData;
|
||||
if (!data.originImage) return;
|
||||
|
||||
if (self.imageScrollView.zoomScale < data.cuttingZoomScale) return;
|
||||
|
||||
if ([data shouldCompress]) {
|
||||
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(cuttingImage_) object:nil];
|
||||
[self performSelector:@selector(cuttingImage_) withObject:nil afterDelay:0.15];
|
||||
}
|
||||
}
|
||||
- (void)cuttingImage_ {
|
||||
YBIBImageData *data = self.yb_cellData;
|
||||
if (!data.originImage) return;
|
||||
|
||||
CGFloat scale = data.originImage.size.width / self.imageScrollView.contentSize.width;
|
||||
CGFloat x = self.imageScrollView.contentOffset.x * scale,
|
||||
y = self.imageScrollView.contentOffset.y * scale,
|
||||
width = self.imageScrollView.bounds.size.width * scale,
|
||||
height = self.imageScrollView.bounds.size.height * scale;
|
||||
|
||||
__weak typeof(self) wSelf = self;
|
||||
[data cuttingImageToRect:CGRectMake(x, y, width, height) complete:^(UIImage *image) {
|
||||
if (!image) return;
|
||||
YBIB_DISPATCH_ASYNC_MAIN(^{
|
||||
__strong typeof(wSelf) self = wSelf;
|
||||
if (!self) return;
|
||||
if (data == self.yb_cellData && !self.imageScrollView.isDragging && !self->_interacting && !self.yb_isTransitioning()) {
|
||||
[self showTailoringImageView:image];
|
||||
}
|
||||
})
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)showTailoringImageView:(UIImage *)image {
|
||||
CGSize containerSize = self.yb_containerSize(self.yb_currentOrientation());
|
||||
if (!self.tailoringImageView.superview) {
|
||||
[self.contentView addSubview:self.tailoringImageView];
|
||||
}
|
||||
self.tailoringImageView.frame = CGRectMake(0, 0, containerSize.width, containerSize.height);
|
||||
self.tailoringImageView.hidden = NO;
|
||||
self.tailoringImageView.image = image;
|
||||
}
|
||||
|
||||
- (void)hideTailoringImageView {
|
||||
// Don't use 'getter' method, because it's according to the need to load.
|
||||
if (_tailoringImageView) {
|
||||
self.tailoringImageView.hidden = YES;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)hideAuxiliaryView {
|
||||
[self.yb_auxiliaryViewHandler() yb_hideLoadingWithContainer:self];
|
||||
[self.yb_auxiliaryViewHandler() yb_hideToastWithContainer:self];
|
||||
}
|
||||
|
||||
- (void)hideBrowser {
|
||||
((YBIBImageData *)self.yb_cellData).delegate = nil;
|
||||
[self hideTailoringImageView];
|
||||
[self hideAuxiliaryView];
|
||||
self.yb_hideBrowser();
|
||||
_interacting = NO;
|
||||
}
|
||||
|
||||
#pragma mark - <YBIBImageDataDelegate>
|
||||
|
||||
- (void)yb_imageData:(YBIBImageData *)data startLoadingWithStatus:(YBIBImageLoadingStatus)status {
|
||||
switch (status) {
|
||||
case YBIBImageLoadingStatusDecoding: {
|
||||
if (!self.imageScrollView.imageView.image) {
|
||||
[self.yb_auxiliaryViewHandler() yb_showLoadingWithContainer:self];
|
||||
}
|
||||
}
|
||||
break;
|
||||
case YBIBImageLoadingStatusProcessing: {
|
||||
if (!self.imageScrollView.imageView.image) {
|
||||
[self.yb_auxiliaryViewHandler() yb_showLoadingWithContainer:self];
|
||||
}
|
||||
}
|
||||
break;
|
||||
case YBIBImageLoadingStatusCompressing: {
|
||||
if (!self.imageScrollView.imageView.image) {
|
||||
[self.yb_auxiliaryViewHandler() yb_showLoadingWithContainer:self];
|
||||
}
|
||||
}
|
||||
break;
|
||||
case YBIBImageLoadingStatusReadingPHAsset: {
|
||||
if (!self.imageScrollView.imageView.image) {
|
||||
[self.yb_auxiliaryViewHandler() yb_showLoadingWithContainer:self];
|
||||
}
|
||||
}
|
||||
break;
|
||||
case YBIBImageLoadingStatusNone: {
|
||||
[self hideAuxiliaryView];
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)yb_imageData:(YBIBImageData *)data readyForImage:(__kindof UIImage *)image {
|
||||
[self.yb_auxiliaryViewHandler() yb_hideLoadingWithContainer:self];
|
||||
if (self.imageScrollView.imageView.image == image) return;
|
||||
|
||||
CGSize size = self.imageScrollView.imageView.image.size;
|
||||
[self.imageScrollView setImage:image type:YBIBScrollImageTypeOriginal];
|
||||
[self updateImageLayoutWithOrientation:self.yb_currentOrientation() previousImageSize:size];
|
||||
}
|
||||
|
||||
- (void)yb_imageData:(YBIBImageData *)data readyForCompressedImage:(__kindof UIImage *)image {
|
||||
[self.yb_auxiliaryViewHandler() yb_hideLoadingWithContainer:self];
|
||||
if (self.imageScrollView.imageView.image == image) return;
|
||||
|
||||
CGSize size = self.imageScrollView.imageView.image.size;
|
||||
[self.imageScrollView setImage:image type:YBIBScrollImageTypeCompressed];
|
||||
[self updateImageLayoutWithOrientation:self.yb_currentOrientation() previousImageSize:size];
|
||||
}
|
||||
|
||||
- (void)yb_imageData:(YBIBImageData *)data readyForThumbImage:(__kindof UIImage *)image {
|
||||
if (self.imageScrollView.imageView.image) return;
|
||||
|
||||
[self.imageScrollView setImage:image type:YBIBScrollImageTypeThumb];
|
||||
[self updateImageLayoutWithOrientation:self.yb_currentOrientation() previousImageSize:image.size];
|
||||
}
|
||||
|
||||
- (void)yb_imageIsInvalidForData:(YBIBImageData *)data {
|
||||
[self.yb_auxiliaryViewHandler() yb_hideLoadingWithContainer:self];
|
||||
NSString *imageIsInvalid = [YBIBCopywriter sharedCopywriter].imageIsInvalid;
|
||||
if (self.imageScrollView.imageView.image) {
|
||||
[self.yb_auxiliaryViewHandler() yb_showIncorrectToastWithContainer:self text:imageIsInvalid];
|
||||
} else {
|
||||
[self.yb_auxiliaryViewHandler() yb_showLoadingWithContainer:self text:imageIsInvalid];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)yb_imageData:(YBIBImageData *)data downloadProgress:(CGFloat)progress {
|
||||
[self.yb_auxiliaryViewHandler() yb_showLoadingWithContainer:self progress:progress];
|
||||
}
|
||||
|
||||
- (void)yb_imageDownloadFailedForData:(YBIBImageData *)data {
|
||||
if (self.imageScrollView.imageView.image) {
|
||||
[self.yb_auxiliaryViewHandler() yb_hideLoadingWithContainer:self];
|
||||
[self.yb_auxiliaryViewHandler() yb_showIncorrectToastWithContainer:self text:[YBIBCopywriter sharedCopywriter].downloadFailed];
|
||||
} else {
|
||||
[self.yb_auxiliaryViewHandler() yb_showLoadingWithContainer:self text:[YBIBCopywriter sharedCopywriter].downloadFailed];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - gesture
|
||||
|
||||
- (void)addGesture {
|
||||
UITapGestureRecognizer *tapSingle = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(respondsToTapSingle:)];
|
||||
tapSingle.numberOfTapsRequired = 1;
|
||||
UITapGestureRecognizer *tapDouble = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(respondsToTapDouble:)];
|
||||
tapDouble.numberOfTapsRequired = 2;
|
||||
UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(respondsToPan:)];
|
||||
pan.maximumNumberOfTouches = 1;
|
||||
pan.delegate = self;
|
||||
|
||||
[tapSingle requireGestureRecognizerToFail:tapDouble];
|
||||
[tapSingle requireGestureRecognizerToFail:pan];
|
||||
[tapDouble requireGestureRecognizerToFail:pan];
|
||||
|
||||
[self addGestureRecognizer:tapSingle];
|
||||
[self addGestureRecognizer:tapDouble];
|
||||
[self addGestureRecognizer:pan];
|
||||
}
|
||||
|
||||
- (void)respondsToTapSingle:(UITapGestureRecognizer *)tap {
|
||||
if (self.yb_isRotating()) return;
|
||||
|
||||
YBIBImageData *data = self.yb_cellData;
|
||||
if (data.singleTouchBlock) {
|
||||
data.singleTouchBlock(data);
|
||||
} else {
|
||||
[self hideTailoringImageView];
|
||||
[self hideAuxiliaryView];
|
||||
self.yb_hideBrowser();
|
||||
}
|
||||
}
|
||||
|
||||
- (void)respondsToTapDouble:(UITapGestureRecognizer *)tap {
|
||||
if (self.yb_isRotating()) return;
|
||||
|
||||
[self hideTailoringImageView];
|
||||
|
||||
UIScrollView *scrollView = self.imageScrollView;
|
||||
UIView *zoomView = [self viewForZoomingInScrollView:scrollView];
|
||||
CGPoint point = [tap locationInView:zoomView];
|
||||
if (!CGRectContainsPoint(zoomView.bounds, point)) return;
|
||||
if (scrollView.zoomScale == scrollView.maximumZoomScale) {
|
||||
[scrollView setZoomScale:1 animated:YES];
|
||||
} else {
|
||||
[scrollView zoomToRect:CGRectMake(point.x, point.y, 1, 1) animated:YES];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)respondsToPan:(UIPanGestureRecognizer *)pan {
|
||||
if (self.yb_isRotating()) return;
|
||||
|
||||
YBIBInteractionProfile *profile = ((YBIBImageData *)self.yb_cellData).interactionProfile;
|
||||
if (profile.disable) return;
|
||||
if ((CGRectIsEmpty(self.imageScrollView.imageView.frame) || !self.imageScrollView.imageView.image)) return;
|
||||
|
||||
CGPoint point = [pan locationInView:self];
|
||||
CGSize containerSize = self.yb_containerSize(self.yb_currentOrientation());
|
||||
|
||||
if (pan.state == UIGestureRecognizerStateBegan) {
|
||||
_interactStartPoint = point;
|
||||
} else if (pan.state == UIGestureRecognizerStateCancelled || pan.state == UIGestureRecognizerStateEnded || pan.state == UIGestureRecognizerStateRecognized || pan.state == UIGestureRecognizerStateFailed) {
|
||||
|
||||
// End.
|
||||
if (_interacting) {
|
||||
CGPoint velocity = [pan velocityInView:self.imageScrollView];
|
||||
|
||||
BOOL velocityArrive = ABS(velocity.y) > profile.dismissVelocityY;
|
||||
BOOL distanceArrive = ABS(point.y - _interactStartPoint.y) > containerSize.height * profile.dismissScale;
|
||||
|
||||
BOOL shouldDismiss = distanceArrive || velocityArrive;
|
||||
if (shouldDismiss) {
|
||||
[self hideBrowser];
|
||||
} else {
|
||||
[self restoreInteractionWithDuration:profile.restoreDuration];
|
||||
}
|
||||
}
|
||||
|
||||
} else if (pan.state == UIGestureRecognizerStateChanged) {
|
||||
if (_interacting) {
|
||||
|
||||
// Change.
|
||||
self.imageScrollView.center = point;
|
||||
CGFloat scale = 1 - ABS(point.y - _interactStartPoint.y) / (containerSize.height * 1.2);
|
||||
if (scale > 1) scale = 1;
|
||||
if (scale < 0.35) scale = 0.35;
|
||||
self.imageScrollView.transform = CGAffineTransformMakeScale(scale, scale);
|
||||
|
||||
CGFloat alpha = 1 - ABS(point.y - _interactStartPoint.y) / (containerSize.height * 0.7);
|
||||
if (alpha > 1) alpha = 1;
|
||||
if (alpha < 0) alpha = 0;
|
||||
self.yb_backView.backgroundColor = [self.yb_backView.backgroundColor colorWithAlphaComponent:alpha];
|
||||
|
||||
} else {
|
||||
|
||||
// Start.
|
||||
if (CGPointEqualToPoint(_interactStartPoint, CGPointZero) || self.yb_currentPage() != self.yb_selfPage() || !self.yb_cellIsInCenter() || self.imageScrollView.isZooming) return;
|
||||
|
||||
CGPoint velocity = [pan velocityInView:self.imageScrollView];
|
||||
CGFloat triggerDistance = profile.triggerDistance;
|
||||
CGFloat offsetY = self.imageScrollView.contentOffset.y, height = self.imageScrollView.bounds.size.height;
|
||||
|
||||
BOOL distanceArrive = ABS(point.x - _interactStartPoint.x) < triggerDistance && ABS(velocity.x) < 500;
|
||||
BOOL upArrive = point.y - _interactStartPoint.y > triggerDistance && offsetY <= 1;
|
||||
BOOL downArrive = point.y - _interactStartPoint.y < -triggerDistance && offsetY + height >= MAX(self.imageScrollView.contentSize.height, height) - 1;
|
||||
|
||||
BOOL shouldStart = (upArrive || downArrive) && distanceArrive;
|
||||
if (!shouldStart) return;
|
||||
|
||||
_interactStartPoint = point;
|
||||
|
||||
CGRect startFrame = self.imageScrollView.frame;
|
||||
CGFloat anchorX = point.x / startFrame.size.width, anchorY = point.y / startFrame.size.height;
|
||||
self.imageScrollView.layer.anchorPoint = CGPointMake(anchorX, anchorY);
|
||||
self.imageScrollView.userInteractionEnabled = NO;
|
||||
self.imageScrollView.scrollEnabled = NO;
|
||||
self.imageScrollView.center = point;
|
||||
|
||||
self.yb_hideToolViews(YES);
|
||||
self.yb_hideStatusBar(NO);
|
||||
self.yb_collectionView().scrollEnabled = NO;
|
||||
[self hideTailoringImageView];
|
||||
|
||||
_interacting = YES;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)restoreInteractionWithDuration:(NSTimeInterval)duration {
|
||||
CGSize containerSize = self.yb_containerSize(self.yb_currentOrientation());
|
||||
|
||||
void (^animations)(void) = ^{
|
||||
self.yb_backView.backgroundColor = [self.yb_backView.backgroundColor colorWithAlphaComponent:1];
|
||||
|
||||
CGPoint anchorPoint = self.imageScrollView.layer.anchorPoint;
|
||||
self.imageScrollView.center = CGPointMake(containerSize.width * anchorPoint.x, containerSize.height * anchorPoint.y);
|
||||
self.imageScrollView.transform = CGAffineTransformIdentity;
|
||||
};
|
||||
void (^completion)(BOOL finished) = ^(BOOL finished){
|
||||
self.imageScrollView.layer.anchorPoint = CGPointMake(0.5, 0.5);
|
||||
self.imageScrollView.center = CGPointMake(containerSize.width * 0.5, containerSize.height * 0.5);
|
||||
self.imageScrollView.userInteractionEnabled = YES;
|
||||
self.imageScrollView.scrollEnabled = YES;
|
||||
|
||||
self.yb_hideToolViews(NO);
|
||||
self.yb_hideStatusBar(YES);
|
||||
self.yb_collectionView().scrollEnabled = YES;
|
||||
[self cuttingImage];
|
||||
|
||||
self->_interactStartPoint = CGPointZero;
|
||||
self->_interacting = NO;
|
||||
};
|
||||
|
||||
if (duration <= 0) {
|
||||
animations();
|
||||
completion(NO);
|
||||
} else {
|
||||
[UIView animateWithDuration:duration animations:animations completion:completion];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - <UIScrollViewDelegate>
|
||||
|
||||
- (void)scrollViewDidZoom:(UIScrollView *)scrollView {
|
||||
YBIBImageData *data = self.yb_cellData;
|
||||
if (data.imageDidZoomBlock) {
|
||||
data.imageDidZoomBlock(data, scrollView);
|
||||
}
|
||||
|
||||
CGRect imageViewFrame = self.imageScrollView.imageView.frame;
|
||||
CGFloat width = imageViewFrame.size.width,
|
||||
height = imageViewFrame.size.height,
|
||||
sHeight = scrollView.bounds.size.height,
|
||||
sWidth = scrollView.bounds.size.width;
|
||||
if (height > sHeight) {
|
||||
imageViewFrame.origin.y = 0;
|
||||
} else {
|
||||
imageViewFrame.origin.y = (sHeight - height) / 2.0;
|
||||
}
|
||||
if (width > sWidth) {
|
||||
imageViewFrame.origin.x = 0;
|
||||
} else {
|
||||
imageViewFrame.origin.x = (sWidth - width) / 2.0;
|
||||
}
|
||||
self.imageScrollView.imageView.frame = imageViewFrame;
|
||||
}
|
||||
|
||||
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView {
|
||||
return self.imageScrollView.imageView;
|
||||
}
|
||||
|
||||
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
|
||||
YBIBImageData *data = self.yb_cellData;
|
||||
if (data.imageDidScrollBlock) {
|
||||
data.imageDidScrollBlock(data, scrollView);
|
||||
}
|
||||
|
||||
[self cuttingImage];
|
||||
}
|
||||
|
||||
- (void)scrollViewWillBeginZooming:(UIScrollView *)scrollView withView:(UIView *)view {
|
||||
[self hideTailoringImageView];
|
||||
}
|
||||
|
||||
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
|
||||
[self hideTailoringImageView];
|
||||
}
|
||||
|
||||
#pragma mark - <UIGestureRecognizerDelegate>
|
||||
|
||||
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
|
||||
return YES;
|
||||
}
|
||||
|
||||
#pragma mark - getters
|
||||
|
||||
- (YBIBImageScrollView *)imageScrollView {
|
||||
if (!_imageScrollView) {
|
||||
_imageScrollView = [YBIBImageScrollView new];
|
||||
_imageScrollView.delegate = self;
|
||||
}
|
||||
return _imageScrollView;
|
||||
}
|
||||
|
||||
- (UIImageView *)tailoringImageView {
|
||||
if (!_tailoringImageView) {
|
||||
_tailoringImageView = [UIImageView new];
|
||||
_tailoringImageView.contentMode = UIViewContentModeScaleAspectFit;
|
||||
}
|
||||
return _tailoringImageView;
|
||||
}
|
||||
|
||||
@end
|
||||
63
Pods/YBImageBrowser/YBImageBrowser/Image/YBIBImageData+Internal.h
generated
Normal file
@@ -0,0 +1,63 @@
|
||||
//
|
||||
// YBIBImageData+Internal.h
|
||||
// YBImageBrowserDemo
|
||||
//
|
||||
// Created by 波儿菜 on 2019/6/12.
|
||||
// Copyright © 2019 波儿菜. All rights reserved.
|
||||
//
|
||||
|
||||
#import "YBIBImageData.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
typedef NS_ENUM(NSInteger, YBIBImageLoadingStatus) {
|
||||
YBIBImageLoadingStatusNone,
|
||||
YBIBImageLoadingStatusCompressing,
|
||||
YBIBImageLoadingStatusDecoding,
|
||||
YBIBImageLoadingStatusQuerying,
|
||||
YBIBImageLoadingStatusProcessing,
|
||||
YBIBImageLoadingStatusDownloading,
|
||||
YBIBImageLoadingStatusReadingPHAsset,
|
||||
};
|
||||
|
||||
@class YBIBImageData;
|
||||
|
||||
@protocol YBIBImageDataDelegate <NSObject>
|
||||
@required
|
||||
|
||||
- (void)yb_imageData:(YBIBImageData *)data startLoadingWithStatus:(YBIBImageLoadingStatus)status;
|
||||
|
||||
- (void)yb_imageIsInvalidForData:(YBIBImageData *)data;
|
||||
|
||||
- (void)yb_imageData:(YBIBImageData *)data readyForImage:(__kindof UIImage *)image;
|
||||
|
||||
- (void)yb_imageData:(YBIBImageData *)data readyForThumbImage:(__kindof UIImage *)image;
|
||||
|
||||
- (void)yb_imageData:(YBIBImageData *)data readyForCompressedImage:(__kindof UIImage *)image;
|
||||
|
||||
- (void)yb_imageData:(YBIBImageData *)data downloadProgress:(CGFloat)progress;
|
||||
|
||||
- (void)yb_imageDownloadFailedForData:(YBIBImageData *)data;
|
||||
|
||||
@end
|
||||
|
||||
@interface YBIBImageData ()
|
||||
|
||||
@property (nonatomic, weak) id<YBIBImageDataDelegate> delegate;
|
||||
|
||||
/// The status of asynchronous tasks.
|
||||
@property (nonatomic, assign) YBIBImageLoadingStatus loadingStatus;
|
||||
|
||||
/// The origin image.
|
||||
@property (nonatomic, strong) UIImage *originImage;
|
||||
|
||||
/// The image compressed by 'originImage' if need.
|
||||
@property (nonatomic, strong) UIImage *compressedImage;
|
||||
|
||||
- (BOOL)shouldCompress;
|
||||
|
||||
- (void)cuttingImageToRect:(CGRect)rect complete:(void(^)(UIImage * _Nullable image))complete;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
157
Pods/YBImageBrowser/YBImageBrowser/Image/YBIBImageData.h
generated
Normal file
@@ -0,0 +1,157 @@
|
||||
//
|
||||
// YBIBImageData.h
|
||||
// YBImageBrowserDemo
|
||||
//
|
||||
// Created by 波儿菜 on 2019/6/5.
|
||||
// Copyright © 2019 波儿菜. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Photos/Photos.h>
|
||||
#import "YBIBDataProtocol.h"
|
||||
#import "YBIBImageLayout.h"
|
||||
#import "YBIBImageCache.h"
|
||||
#import "YBIBInteractionProfile.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class YBIBImageData;
|
||||
|
||||
/// 获取 NSData 的闭包
|
||||
typedef NSData * _Nullable (^YBIBImageDataBlock)(void);
|
||||
|
||||
/// 获取 UIImage 的闭包
|
||||
typedef UIImage * _Nullable (^YBIBImageBlock)(void);
|
||||
|
||||
/// 修改 NSURLRequest 并返回的闭包
|
||||
typedef NSURLRequest * _Nullable (^YBIBRequestModifierBlock)(YBIBImageData *imageData, NSURLRequest *request);
|
||||
|
||||
/// 根据图片逻辑像素和 scale 判断是否需要预解码的闭包
|
||||
typedef BOOL (^YBIBPreDecodeDecisionBlock)(YBIBImageData *imageData, CGSize imageSize, CGFloat scale);
|
||||
|
||||
/// 修改 UIImage 并返回的闭包
|
||||
typedef void (^YBIBImageModifierBlock)(YBIBImageData *imageData, UIImage *image, void(^completion)(UIImage *processedImage));
|
||||
|
||||
/// 单击事件的处理闭包
|
||||
typedef void (^YBIBImageSingleTouchBlock)(YBIBImageData *imageData);
|
||||
|
||||
/// 内部图片滚动视图状态回调闭包
|
||||
typedef void (^YBIBImageScrollViewStatusBlock)(YBIBImageData *imageData, UIScrollView *scrollView);
|
||||
|
||||
|
||||
/**
|
||||
图片数据类,承担配置数据和处理数据的责任
|
||||
*/
|
||||
@interface YBIBImageData : NSObject <YBIBDataProtocol>
|
||||
|
||||
/// 本地图片名字
|
||||
@property (nonatomic, copy, nullable) NSString *imageName;
|
||||
|
||||
/// 本地图片路径
|
||||
@property (nonatomic, copy, nullable) NSString *imagePath;
|
||||
|
||||
/// 本地图片字节码,返回 NSData
|
||||
@property (nonatomic, copy, nullable) YBIBImageDataBlock imageData;
|
||||
|
||||
/// 本地图片,返回 UIImage 及其衍生类 (若不是遵循'YYAnimatedImage'协议的类型,将失去对动图和拓展格式的支持)
|
||||
@property (nonatomic, copy, nullable) YBIBImageBlock image;
|
||||
|
||||
/// 网络图片资源
|
||||
@property (nonatomic, copy, nullable) NSURL *imageURL;
|
||||
|
||||
/// 修改 NSURLRequest 并返回
|
||||
@property (nonatomic, copy, nullable) YBIBRequestModifierBlock requestModifier;
|
||||
|
||||
/// 相册图片资源
|
||||
@property (nonatomic, strong, nullable) PHAsset *imagePHAsset;
|
||||
|
||||
/// 投影视图,当前数据模型对应外界业务的 UIView (通常为 UIImageView),做转场动效用
|
||||
@property (nonatomic, weak, nullable) __kindof UIView *projectiveView;
|
||||
|
||||
/// 预览图/缩约图,注意若这个图片过大会导致内存压力(若 projectiveView 存在且是 UIImageView 类型将会自动获取缩约图)
|
||||
@property (nonatomic, strong, nullable) UIImage *thumbImage;
|
||||
|
||||
/// 预览图/缩约图 URL,缓存中未找到则忽略(若 projectiveView 存在且是 UIImageView 类型将会自动获取缩约图)
|
||||
@property (nonatomic, copy, nullable) NSURL *thumbURL;
|
||||
|
||||
/// 是否允许保存到相册
|
||||
@property (nonatomic, assign) BOOL allowSaveToPhotoAlbum;
|
||||
|
||||
/// 根据图片信息判断是否需要预解码
|
||||
@property (nonatomic, copy, nullable) YBIBPreDecodeDecisionBlock preDecodeDecision;
|
||||
|
||||
/// 是否异步预解码,默认为 YES
|
||||
@property (nonatomic, assign) BOOL shouldPreDecodeAsync;
|
||||
|
||||
/// 压缩物理像素界限大小,当图片超过这个值将会被压缩显示,默认为 4096*4096
|
||||
@property (nonatomic, assign) CGFloat compressingSize;
|
||||
|
||||
/// 触发裁剪的缩放比例,必须大于等于 1,默认情况内部会动态计算 (仅当图片需要压缩显示时有效)
|
||||
@property (nonatomic, assign) CGFloat cuttingZoomScale;
|
||||
|
||||
/**
|
||||
判断图片是否需要压缩显示
|
||||
*/
|
||||
- (BOOL)shouldCompressWithImage:(UIImage *)image;
|
||||
|
||||
/**
|
||||
修改原始图片并返回处理后的图片 (特别注意当 image 是大图的时候,避免 OOM)
|
||||
Example:
|
||||
[... setImageModifier:^(YBIBImageData *imageData, UIImage * _Nonnull image, void (^ _Nonnull completion)(UIImage * _Nonnull)) {
|
||||
//step 1 : Add watermark, trademark, etc. (Sync or async).
|
||||
... processing code ...
|
||||
//step 2 : Return the processed UIImage.
|
||||
completion(image);
|
||||
}];
|
||||
*/
|
||||
@property (nonatomic, copy, nullable) YBIBImageModifierBlock originImageModifier;
|
||||
|
||||
/// 修改压缩图片并返回处理后的图片 (仅在当前图片需要被压缩时有效)
|
||||
@property (nonatomic, copy, nullable) YBIBImageModifierBlock compressedImageModifier;
|
||||
|
||||
/// 修改裁剪图片并返回处理后的图片 (仅在当前图片需要被裁剪时有效)
|
||||
@property (nonatomic, copy, nullable) YBIBImageModifierBlock cuttedImageModifier;
|
||||
|
||||
/// 预留属性可随意使用
|
||||
@property (nonatomic, strong, nullable) id extraData;
|
||||
|
||||
/// 手势交互动效配置文件
|
||||
@property (nonatomic, strong) YBIBInteractionProfile *interactionProfile;
|
||||
|
||||
/// 单击的处理,默认是退出图片浏览器
|
||||
@property (nonatomic, copy, nullable) YBIBImageSingleTouchBlock singleTouchBlock;
|
||||
|
||||
/// 图片滚动的回调
|
||||
@property (nonatomic, copy, nullable) YBIBImageScrollViewStatusBlock imageDidScrollBlock;
|
||||
|
||||
/// 图片缩放的回调
|
||||
@property (nonatomic, copy, nullable) YBIBImageScrollViewStatusBlock imageDidZoomBlock;
|
||||
|
||||
/// 图片布局类 (赋值可自定义)
|
||||
@property (nonatomic, strong) id<YBIBImageLayout> layout;
|
||||
/// 默认图片布局类 (可配置其属性)
|
||||
@property (nonatomic, weak, readonly) YBIBImageLayout *defaultLayout;
|
||||
|
||||
/**
|
||||
终止处理数据程序
|
||||
*/
|
||||
- (void)stopLoading;
|
||||
|
||||
/**
|
||||
清除缓存的数据
|
||||
*/
|
||||
- (void)clearCache;
|
||||
|
||||
/**
|
||||
加载数据,一般不需要调用这个方法,当该数据对象做了数据更新时调用
|
||||
*/
|
||||
- (void)loadData;
|
||||
|
||||
/// 处理后的原始图片
|
||||
@property (nonatomic, strong, readonly) UIImage *originImage;
|
||||
|
||||
/// 处理后的压缩图片
|
||||
@property (nonatomic, strong, readonly) UIImage *compressedImage;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
695
Pods/YBImageBrowser/YBImageBrowser/Image/YBIBImageData.m
generated
Normal file
@@ -0,0 +1,695 @@
|
||||
//
|
||||
// YBIBImageData.m
|
||||
// YBImageBrowserDemo
|
||||
//
|
||||
// Created by 波儿菜 on 2019/6/5.
|
||||
// Copyright © 2019 波儿菜. All rights reserved.
|
||||
//
|
||||
|
||||
#import "YBImage.h"
|
||||
#import "YBIBImageData.h"
|
||||
#import "YBIBImageCell.h"
|
||||
#import "YBIBPhotoAlbumManager.h"
|
||||
#import "YBIBImageData+Internal.h"
|
||||
#import "YBIBUtilities.h"
|
||||
#import "YBIBImageCache+Internal.h"
|
||||
#import "YBIBSentinel.h"
|
||||
#import "YBIBCopywriter.h"
|
||||
#import <AssetsLibrary/AssetsLibrary.h>
|
||||
|
||||
extern CGImageRef YYCGImageCreateDecodedCopy(CGImageRef imageRef, BOOL decodeForDisplay);
|
||||
|
||||
static dispatch_queue_t YBIBImageProcessingQueue(void) {
|
||||
static dispatch_queue_t queue;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
queue = dispatch_queue_create("com.yangbo.imagebrowser.imageprocessing", DISPATCH_QUEUE_CONCURRENT);
|
||||
});
|
||||
return queue;
|
||||
}
|
||||
|
||||
@implementation YBIBImageData {
|
||||
__weak id _downloadToken;
|
||||
YBIBSentinel *_cuttingSentinel;
|
||||
/// Stop processing tasks when in freeze.
|
||||
BOOL _freezing;
|
||||
}
|
||||
|
||||
#pragma mark - life cycle
|
||||
|
||||
- (void)dealloc {
|
||||
if (_downloadToken && [self.yb_webImageMediator() respondsToSelector:@selector(yb_cancelTaskWithDownloadToken:)]) {
|
||||
[self.yb_webImageMediator() yb_cancelTaskWithDownloadToken:_downloadToken];
|
||||
}
|
||||
[self.imageCache removeForKey:self.cacheKey];
|
||||
}
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
[self initValue];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)initValue {
|
||||
_defaultLayout = _layout = [YBIBImageLayout new];
|
||||
_loadingStatus = YBIBImageLoadingStatusNone;
|
||||
_compressingSize = 4096 * 4096;
|
||||
_shouldPreDecodeAsync = YES;
|
||||
_freezing = NO;
|
||||
_cuttingSentinel = [YBIBSentinel new];
|
||||
_interactionProfile = [YBIBInteractionProfile new];
|
||||
_allowSaveToPhotoAlbum = YES;
|
||||
}
|
||||
|
||||
#pragma mark - load data
|
||||
|
||||
- (void)loadData {
|
||||
_freezing = NO;
|
||||
|
||||
// Avoid handling asynchronous tasks repeatedly.
|
||||
if (self.loadingStatus != YBIBImageLoadingStatusNone) {
|
||||
[self loadThumbImage];
|
||||
self.loadingStatus = self.loadingStatus;
|
||||
return;
|
||||
}
|
||||
|
||||
if (self.originImage) {
|
||||
[self loadOriginImage];
|
||||
} else if (self.imageName || self.imagePath || self.imageData) {
|
||||
[self loadYBImage];
|
||||
} else if (self.image) {
|
||||
[self loadImageBlock];
|
||||
} else if (self.imageURL) {
|
||||
[self loadThumbImage];
|
||||
[self loadURL];
|
||||
} else if (self.imagePHAsset) {
|
||||
[self loadThumbImage];
|
||||
[self loadPHAsset];
|
||||
} else {
|
||||
[self.delegate yb_imageIsInvalidForData:self];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)loadOriginImage {
|
||||
if (_freezing) return;
|
||||
if (!self.originImage) return;
|
||||
|
||||
if ([self shouldCompress]) {
|
||||
if (self.compressedImage) {
|
||||
[self.delegate yb_imageData:self readyForCompressedImage:self.compressedImage];
|
||||
} else {
|
||||
[self loadThumbImage];
|
||||
[self loadOriginImage_compress];
|
||||
}
|
||||
} else {
|
||||
[self.delegate yb_imageData:self readyForImage:self.originImage];
|
||||
}
|
||||
}
|
||||
- (void)loadOriginImage_compress {
|
||||
if (_freezing) return;
|
||||
if (!self.originImage) return;
|
||||
|
||||
self.loadingStatus = YBIBImageLoadingStatusCompressing;
|
||||
__weak typeof(self) wSelf = self;
|
||||
CGSize size = [self bestSizeOfCompressing];
|
||||
|
||||
YBIB_DISPATCH_ASYNC(YBIBImageProcessingQueue(), ^{
|
||||
if (self->_freezing) {
|
||||
self.loadingStatus = YBIBImageLoadingStatusNone;
|
||||
return;
|
||||
}
|
||||
// Ensure the best display effect.
|
||||
UIGraphicsBeginImageContextWithOptions(size, NO, UIScreen.mainScreen.scale);
|
||||
[self.originImage drawInRect:CGRectMake(0, 0, size.width, size.height)];
|
||||
if (self->_freezing) {
|
||||
UIGraphicsEndImageContext();
|
||||
self.loadingStatus = YBIBImageLoadingStatusNone;
|
||||
return;
|
||||
}
|
||||
UIImage *resultImage = UIGraphicsGetImageFromCurrentImageContext();
|
||||
UIGraphicsEndImageContext();
|
||||
|
||||
YBIB_DISPATCH_ASYNC_MAIN(^{
|
||||
__strong typeof(wSelf) self = wSelf;
|
||||
if (!self) return;
|
||||
|
||||
self.loadingStatus = YBIBImageLoadingStatusNone;
|
||||
|
||||
[self modifyImageWithModifier:self.compressedImageModifier image:resultImage completion:^(UIImage *processedImage) {
|
||||
__strong typeof(wSelf) self = wSelf;
|
||||
if (!self) return;
|
||||
self.compressedImage = processedImage ?: self.originImage;
|
||||
[self.delegate yb_imageData:self readyForCompressedImage:self.compressedImage];
|
||||
}];
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
- (void)loadYBImage {
|
||||
if (_freezing) return;
|
||||
NSString *name = self.imageName.copy;
|
||||
NSString *path = self.imagePath.copy;
|
||||
NSData *data = self.imageData ? self.imageData().copy : nil;
|
||||
if (name.length == 0 && path.length == 0 && data.length == 0) return;
|
||||
|
||||
YBImageDecodeDecision decision = [self defaultDecodeDecision];
|
||||
|
||||
__block YBImage *image;
|
||||
__weak typeof(self) wSelf = self;
|
||||
void(^dealBlock)(void) = ^{
|
||||
if (name.length > 0) {
|
||||
image = [YBImage imageNamed:name decodeDecision:decision];
|
||||
} else if (path.length > 0) {
|
||||
image = [YBImage imageWithContentsOfFile:path decodeDecision:decision];
|
||||
} else if (data.length > 0) {
|
||||
image = [YBImage imageWithData:data scale:UIScreen.mainScreen.scale decodeDecision:decision];
|
||||
}
|
||||
YBIB_DISPATCH_ASYNC_MAIN(^{
|
||||
__strong typeof(wSelf) self = wSelf;
|
||||
if (!self) return;
|
||||
self.loadingStatus = YBIBImageLoadingStatusNone;
|
||||
if (image) {
|
||||
[self setOriginImageAndLoadWithImage:image];
|
||||
} else {
|
||||
[self.delegate yb_imageIsInvalidForData:self];
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
if (self.shouldPreDecodeAsync) {
|
||||
[self loadThumbImage];
|
||||
self.loadingStatus = YBIBImageLoadingStatusDecoding;
|
||||
YBIB_DISPATCH_ASYNC(YBIBImageProcessingQueue(), dealBlock)
|
||||
} else {
|
||||
self.loadingStatus = YBIBImageLoadingStatusDecoding;
|
||||
dealBlock();
|
||||
}
|
||||
}
|
||||
|
||||
- (void)loadImageBlock {
|
||||
if (_freezing) return;
|
||||
__block UIImage *image = self.image ? self.image() : nil;
|
||||
if (!image) return;
|
||||
|
||||
BOOL shouldPreDecode = self.preDecodeDecision ? self.preDecodeDecision(self, image.size, image.scale) : ![self shouldCompressWithImage:image];
|
||||
|
||||
__weak typeof(self) wSelf = self;
|
||||
void(^dealBlock)(void) = ^{
|
||||
// Do not need to decode If 'image' conformed 'YYAnimatedImage'. (Not entirely accurate.)
|
||||
if (![image conformsToProtocol:@protocol(YYAnimatedImage)]) {
|
||||
CGImageRef cgImage = YYCGImageCreateDecodedCopy(image.CGImage, shouldPreDecode);
|
||||
image = [UIImage imageWithCGImage:cgImage scale:image.scale orientation:image.imageOrientation];
|
||||
if (cgImage) CGImageRelease(cgImage);
|
||||
}
|
||||
YBIB_DISPATCH_ASYNC_MAIN(^{
|
||||
__strong typeof(wSelf) self = wSelf;
|
||||
if (!self) return;
|
||||
self.loadingStatus = YBIBImageLoadingStatusNone;
|
||||
[self setOriginImageAndLoadWithImage:image];
|
||||
})
|
||||
};
|
||||
|
||||
if (self.shouldPreDecodeAsync) {
|
||||
[self loadThumbImage];
|
||||
self.loadingStatus = YBIBImageLoadingStatusDecoding;
|
||||
YBIB_DISPATCH_ASYNC(YBIBImageProcessingQueue(), dealBlock)
|
||||
} else {
|
||||
self.loadingStatus = YBIBImageLoadingStatusDecoding;
|
||||
dealBlock();
|
||||
}
|
||||
}
|
||||
|
||||
- (void)loadURL {
|
||||
if (!self.imageURL || self.imageURL.absoluteString.length == 0) return;
|
||||
[self loadURL_queryCache];
|
||||
}
|
||||
- (void)loadURL_queryCache {
|
||||
if (_freezing) return;
|
||||
if (!self.imageURL || self.imageURL.absoluteString.length == 0) return;
|
||||
|
||||
YBImageDecodeDecision decision = [self defaultDecodeDecision];
|
||||
|
||||
self.loadingStatus = YBIBImageLoadingStatusQuerying;
|
||||
[self.yb_webImageMediator() yb_queryCacheOperationForKey:self.imageURL completed:^(UIImage * _Nullable image, NSData * _Nullable imageData) {
|
||||
if (!imageData || imageData.length == 0) {
|
||||
YBIB_DISPATCH_ASYNC_MAIN(^{
|
||||
self.loadingStatus = YBIBImageLoadingStatusNone;
|
||||
[self loadURL_download];
|
||||
})
|
||||
return;
|
||||
}
|
||||
|
||||
YBIB_DISPATCH_ASYNC(YBIBImageProcessingQueue(), ^{
|
||||
if (self->_freezing) {
|
||||
self.loadingStatus = YBIBImageLoadingStatusNone;
|
||||
return;
|
||||
}
|
||||
YBImage *image = [YBImage imageWithData:imageData scale:UIScreen.mainScreen.scale decodeDecision:decision];
|
||||
__weak typeof(self) wSelf = self;
|
||||
YBIB_DISPATCH_ASYNC_MAIN(^{
|
||||
__strong typeof(wSelf) self = wSelf;
|
||||
if (!self) return;
|
||||
self.loadingStatus = YBIBImageLoadingStatusNone;
|
||||
if (image) { // Maybe the image data is invalid.
|
||||
[self setOriginImageAndLoadWithImage:image];
|
||||
} else {
|
||||
[self loadURL_download];
|
||||
}
|
||||
})
|
||||
})
|
||||
}];
|
||||
}
|
||||
- (void)loadURL_download {
|
||||
if (_freezing) return;
|
||||
if (!self.imageURL || self.imageURL.absoluteString.length == 0) return;
|
||||
|
||||
YBImageDecodeDecision decision = [self defaultDecodeDecision];
|
||||
|
||||
self.loadingStatus = YBIBImageLoadingStatusDownloading;
|
||||
__weak typeof(self) wSelf = self;
|
||||
_downloadToken = [self.yb_webImageMediator() yb_downloadImageWithURL:self.imageURL requestModifier:^NSURLRequest * _Nullable(NSURLRequest * _Nonnull request) {
|
||||
return self.requestModifier ? self.requestModifier(self, request) : request;
|
||||
} progress:^(NSInteger receivedSize, NSInteger expectedSize) {
|
||||
CGFloat progress = receivedSize * 1.0 / expectedSize ?: 0;
|
||||
YBIB_DISPATCH_ASYNC_MAIN(^{
|
||||
__strong typeof(wSelf) self = wSelf;
|
||||
if (!self) return;
|
||||
[self.delegate yb_imageData:self downloadProgress:progress];
|
||||
})
|
||||
} success:^(NSData * _Nullable imageData, BOOL finished) {
|
||||
if (!finished) return;
|
||||
|
||||
YBIB_DISPATCH_ASYNC(YBIBImageProcessingQueue(), ^{
|
||||
if (self->_freezing) {
|
||||
self.loadingStatus = YBIBImageLoadingStatusNone;
|
||||
return;
|
||||
}
|
||||
YBImage *image = [YBImage imageWithData:imageData scale:UIScreen.mainScreen.scale decodeDecision:decision];
|
||||
YBIB_DISPATCH_ASYNC_MAIN(^{
|
||||
__strong typeof(wSelf) self = wSelf;
|
||||
if (!self) return;
|
||||
[self.yb_webImageMediator() yb_storeToDiskWithImageData:imageData forKey:self.imageURL];
|
||||
self.loadingStatus = YBIBImageLoadingStatusNone;
|
||||
if (image) {
|
||||
[self setOriginImageAndLoadWithImage:image];
|
||||
} else {
|
||||
[self.delegate yb_imageIsInvalidForData:self];
|
||||
}
|
||||
})
|
||||
})
|
||||
} failed:^(NSError * _Nullable error, BOOL finished) {
|
||||
if (!finished) return;
|
||||
__strong typeof(wSelf) self = wSelf;
|
||||
if (!self) return;
|
||||
self.loadingStatus = YBIBImageLoadingStatusNone;
|
||||
[self.delegate yb_imageDownloadFailedForData:self];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)loadPHAsset {
|
||||
if (_freezing) return;
|
||||
if (!self.imagePHAsset) return;
|
||||
|
||||
YBImageDecodeDecision decision = [self defaultDecodeDecision];
|
||||
|
||||
self.loadingStatus = YBIBImageLoadingStatusReadingPHAsset;
|
||||
YBIB_DISPATCH_ASYNC(YBIBImageProcessingQueue(), ^{
|
||||
[YBIBPhotoAlbumManager getImageDataWithPHAsset:self.imagePHAsset completion:^(NSData * _Nullable data) {
|
||||
if (self->_freezing) {
|
||||
self.loadingStatus = YBIBImageLoadingStatusNone;
|
||||
return;
|
||||
}
|
||||
YBImage *image = [YBImage imageWithData:data scale:UIScreen.mainScreen.scale decodeDecision:decision];
|
||||
__weak typeof(self) wSelf = self;
|
||||
YBIB_DISPATCH_ASYNC_MAIN(^{
|
||||
__strong typeof(wSelf) self = wSelf;
|
||||
if (!self) return;
|
||||
|
||||
self.loadingStatus = YBIBImageLoadingStatusNone;
|
||||
if (image) {
|
||||
[self setOriginImageAndLoadWithImage:image];
|
||||
} else {
|
||||
[self.delegate yb_imageIsInvalidForData:self];
|
||||
}
|
||||
})
|
||||
}];
|
||||
})
|
||||
}
|
||||
|
||||
- (void)loadThumbImage {
|
||||
if (_freezing) return;
|
||||
if (self.thumbImage) {
|
||||
[self.delegate yb_imageData:self readyForThumbImage:self.thumbImage];
|
||||
} else if (self.thumbURL) {
|
||||
__weak typeof(self) wSelf = self;
|
||||
[self.yb_webImageMediator() yb_queryCacheOperationForKey:self.thumbURL completed:^(UIImage * _Nullable image, NSData * _Nullable imageData) {
|
||||
__strong typeof(wSelf) self = wSelf;
|
||||
if (!self) return;
|
||||
|
||||
UIImage *thumbImage;
|
||||
if (image) {
|
||||
thumbImage = image;
|
||||
} else if (imageData) {
|
||||
thumbImage = [UIImage imageWithData:imageData];
|
||||
}
|
||||
// If the target image is ready, ignore the thumb image.
|
||||
BOOL shouldIgnore = [self shouldCompress] ? (self.compressedImage != nil) : (self.originImage != nil);
|
||||
if (!shouldIgnore && thumbImage) {
|
||||
[self.delegate yb_imageData:self readyForThumbImage:thumbImage];
|
||||
}
|
||||
}];
|
||||
} else if (self.projectiveView && [self.projectiveView isKindOfClass:UIImageView.self] && ((UIImageView *)self.projectiveView).image) {
|
||||
UIImage *thumbImage = ((UIImageView *)self.projectiveView).image;
|
||||
[self.delegate yb_imageData:self readyForThumbImage:thumbImage];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - internal
|
||||
|
||||
- (void)cuttingImageToRect:(CGRect)rect complete:(void (^)(UIImage * _Nullable))complete {
|
||||
if (_freezing) return;
|
||||
if (!self.originImage) return;
|
||||
|
||||
int32_t value = [_cuttingSentinel increase];
|
||||
BOOL (^isCancelled)(void) = ^BOOL(void) {
|
||||
if (self->_freezing) return YES;
|
||||
return value != self->_cuttingSentinel.value;
|
||||
};
|
||||
|
||||
YBIB_DISPATCH_ASYNC(YBIBImageProcessingQueue(), ^{
|
||||
if (isCancelled()) {
|
||||
complete(nil);
|
||||
return;
|
||||
}
|
||||
CGFloat scale = self.originImage.scale;
|
||||
CGFloat width = self.originImage.size.width;
|
||||
CGFloat height = self.originImage.size.height;
|
||||
|
||||
BOOL reverseWidthHeight = NO;
|
||||
CGAffineTransform transform = CGAffineTransformIdentity;
|
||||
switch (self.originImage.imageOrientation) {
|
||||
case UIImageOrientationDown:
|
||||
case UIImageOrientationDownMirrored:
|
||||
CGAffineTransformTranslate(CGAffineTransformMakeRotation(-M_PI), -width, -height);
|
||||
break;
|
||||
case UIImageOrientationLeft:
|
||||
case UIImageOrientationLeftMirrored:
|
||||
transform = CGAffineTransformTranslate(CGAffineTransformMakeRotation(M_PI_2), 0, -height);
|
||||
reverseWidthHeight = YES;
|
||||
break;
|
||||
case UIImageOrientationRight:
|
||||
case UIImageOrientationRightMirrored:
|
||||
transform = CGAffineTransformTranslate(CGAffineTransformMakeRotation(-M_PI_2), -width, 0);
|
||||
reverseWidthHeight = YES;
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
transform = CGAffineTransformScale(transform, scale, scale);
|
||||
CGRect correctRect = CGRectApplyAffineTransform(rect, transform);
|
||||
CGImageRef cgImage = CGImageCreateWithImageInRect(self.originImage.CGImage, correctRect);
|
||||
|
||||
if (isCancelled()) {
|
||||
complete(nil);
|
||||
if (cgImage) CGImageRelease(cgImage);
|
||||
return;
|
||||
}
|
||||
CGFloat cgWidth = reverseWidthHeight ? CGImageGetHeight(cgImage) : CGImageGetWidth(cgImage);
|
||||
CGFloat cgHeight = reverseWidthHeight ? CGImageGetWidth(cgImage) : CGImageGetHeight(cgImage);
|
||||
CGSize size = [self bestSizeOfCuttingWithOriginSize:CGSizeMake(cgWidth / scale, cgHeight / scale)];
|
||||
UIImage *tmpImage = [UIImage imageWithCGImage:cgImage scale:self.originImage.scale orientation:self.originImage.imageOrientation];
|
||||
if (isCancelled()) {
|
||||
complete(nil);
|
||||
if (cgImage) CGImageRelease(cgImage);
|
||||
return;
|
||||
}
|
||||
|
||||
// Ensure the best display effect.
|
||||
UIGraphicsBeginImageContextWithOptions(size, NO, UIScreen.mainScreen.scale);
|
||||
[tmpImage drawInRect:CGRectMake(0, 0, size.width, size.height)];
|
||||
if (isCancelled()) {
|
||||
complete(nil);
|
||||
UIGraphicsEndImageContext();
|
||||
if (cgImage) CGImageRelease(cgImage);
|
||||
return;
|
||||
}
|
||||
UIImage *resultImage = UIGraphicsGetImageFromCurrentImageContext();
|
||||
UIGraphicsEndImageContext();
|
||||
if (cgImage) CGImageRelease(cgImage);
|
||||
|
||||
__weak typeof(self) wSelf = self;
|
||||
YBIB_DISPATCH_ASYNC_MAIN(^{
|
||||
__strong typeof(wSelf) self = wSelf;
|
||||
if (!self) return;
|
||||
|
||||
[self modifyImageWithModifier:self.cuttedImageModifier image:resultImage completion:^(UIImage *image) {
|
||||
__strong typeof(wSelf) self = wSelf;
|
||||
if (!self) return;
|
||||
complete(image);
|
||||
}];
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
- (BOOL)shouldCompress {
|
||||
return [self shouldCompressWithImage:self.originImage];
|
||||
}
|
||||
|
||||
#pragma mark - public
|
||||
|
||||
- (BOOL)shouldCompressWithImage:(UIImage *)image {
|
||||
if (!image) return NO;
|
||||
return [self shouldCompressWithImageSize:image.size scale:image.scale];
|
||||
}
|
||||
|
||||
- (void)stopLoading {
|
||||
_freezing = YES;
|
||||
self.loadingStatus = YBIBImageLoadingStatusNone;
|
||||
}
|
||||
|
||||
- (void)clearCache {
|
||||
[self.imageCache removeForKey:self.cacheKey];
|
||||
}
|
||||
|
||||
#pragma mark - private
|
||||
|
||||
/// 'size': logic pixel.
|
||||
- (BOOL)shouldCompressWithImageSize:(CGSize)size scale:(CGFloat)scale {
|
||||
return size.width * scale * size.height * scale > self.compressingSize;
|
||||
}
|
||||
|
||||
/// Logic pixel.
|
||||
- (CGSize)bestSizeOfCompressing {
|
||||
if (!self.originImage) return CGSizeZero;
|
||||
UIDeviceOrientation orientation = self.yb_currentOrientation();
|
||||
CGRect imageViewFrame = [self.layout yb_imageViewFrameWithContainerSize:self.yb_containerSize(orientation) imageSize:self.originImage.size orientation:orientation];
|
||||
return imageViewFrame.size;
|
||||
}
|
||||
|
||||
/// Logic pixel.
|
||||
- (CGSize)bestSizeOfCuttingWithOriginSize:(CGSize)originSize {
|
||||
CGSize containerSize = self.yb_containerSize(self.yb_currentOrientation());
|
||||
CGFloat maxWidth = containerSize.width, maxHeight = containerSize.height;
|
||||
CGFloat oWidth = originSize.width, oHeight = originSize.height;
|
||||
if (maxWidth <= 0 || maxHeight <= 0 || oWidth <= 0 || oHeight <= 0) return CGSizeZero;
|
||||
|
||||
if (oWidth <= maxWidth && oHeight <= maxHeight) {
|
||||
return originSize;
|
||||
}
|
||||
CGFloat rWidth = 0, rHeight = 0;
|
||||
if (oWidth / maxWidth < oHeight / maxHeight) {
|
||||
rHeight = maxHeight;
|
||||
rWidth = oWidth / oHeight * rHeight;
|
||||
} else {
|
||||
rWidth = maxWidth;
|
||||
rHeight = oHeight / oWidth * rWidth;
|
||||
}
|
||||
return CGSizeMake(rWidth, rHeight);
|
||||
}
|
||||
|
||||
- (YBImageDecodeDecision)defaultDecodeDecision {
|
||||
__weak typeof(self) wSelf = self;
|
||||
return ^BOOL(CGSize imageSize, CGFloat scale) {
|
||||
__strong typeof(wSelf) self = wSelf;
|
||||
if (!self) return NO;
|
||||
CGSize logicSize = CGSizeMake(imageSize.width / scale, imageSize.height / scale);
|
||||
if (self.preDecodeDecision) return self.preDecodeDecision(self, logicSize, scale);
|
||||
if ([self shouldCompressWithImageSize:logicSize scale:scale]) return NO;
|
||||
return YES;
|
||||
};
|
||||
}
|
||||
|
||||
- (void)modifyImageWithModifier:(YBIBImageModifierBlock)modifier image:(UIImage *)image completion:(void(^)(UIImage *processedImage))completion {
|
||||
if (modifier) {
|
||||
self.loadingStatus = YBIBImageLoadingStatusProcessing;
|
||||
__weak typeof(self) wSelf = self;
|
||||
modifier(self, image, ^(UIImage *processedImage){
|
||||
// This step is necessary, maybe 'self' is already 'dealloc' if processing code takes too much time.
|
||||
__strong typeof(wSelf) self = wSelf;
|
||||
if (!self) return;
|
||||
self.loadingStatus = YBIBImageLoadingStatusNone;
|
||||
completion(processedImage);
|
||||
});
|
||||
} else {
|
||||
completion(image);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setOriginImageAndLoadWithImage:(UIImage *)image {
|
||||
__weak typeof(self) wSelf = self;
|
||||
[self modifyImageWithModifier:self.originImageModifier image:image completion:^(UIImage *processedImage) {
|
||||
__strong typeof(wSelf) self = wSelf;
|
||||
if (!self) return;
|
||||
self.originImage = processedImage;
|
||||
[self loadOriginImage];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)saveToPhotoAlbumCompleteWithError:(nullable NSError *)error {
|
||||
if (error) {
|
||||
[self.yb_auxiliaryViewHandler() yb_showIncorrectToastWithContainer:self.yb_containerView text:[YBIBCopywriter sharedCopywriter].saveToPhotoAlbumFailed];
|
||||
} else {
|
||||
[self.yb_auxiliaryViewHandler() yb_showCorrectToastWithContainer:self.yb_containerView text:[YBIBCopywriter sharedCopywriter].saveToPhotoAlbumSuccess];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)UIImageWriteToSavedPhotosAlbum_completedWithImage:(UIImage *)image error:(NSError *)error context:(void *)context {
|
||||
[self saveToPhotoAlbumCompleteWithError:error];
|
||||
}
|
||||
|
||||
- (YBIBImageCache *)imageCache {
|
||||
return self.yb_backView.ybib_imageCache;
|
||||
}
|
||||
|
||||
#pragma mark - <YBIBDataProtocol>
|
||||
|
||||
@synthesize yb_isHideTransitioning = _yb_isHideTransitioning;
|
||||
@synthesize yb_currentOrientation = _yb_currentOrientation;
|
||||
@synthesize yb_containerSize = _yb_containerSize;
|
||||
@synthesize yb_containerView = _yb_containerView;
|
||||
@synthesize yb_auxiliaryViewHandler = _yb_auxiliaryViewHandler;
|
||||
@synthesize yb_webImageMediator = _yb_webImageMediator;
|
||||
@synthesize yb_backView = _yb_backView;
|
||||
|
||||
- (nonnull Class)yb_classOfCell {
|
||||
return YBIBImageCell.self;
|
||||
}
|
||||
|
||||
- (UIView *)yb_projectiveView {
|
||||
return self.projectiveView;
|
||||
}
|
||||
|
||||
- (CGRect)yb_imageViewFrameWithContainerSize:(CGSize)containerSize imageSize:(CGSize)imageSize orientation:(UIDeviceOrientation)orientation {
|
||||
return [self.layout yb_imageViewFrameWithContainerSize:containerSize imageSize:imageSize orientation:orientation];
|
||||
}
|
||||
|
||||
- (void)yb_preload {
|
||||
if (!self.delegate) {
|
||||
[self loadData];
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)yb_allowSaveToPhotoAlbum {
|
||||
return self.allowSaveToPhotoAlbum;
|
||||
}
|
||||
|
||||
- (void)yb_saveToPhotoAlbum {
|
||||
void(^saveData)(NSData *) = ^(NSData * _Nonnull data){
|
||||
[[ALAssetsLibrary new] writeImageDataToSavedPhotosAlbum:data metadata:nil completionBlock:^(NSURL *assetURL, NSError *error) {
|
||||
[self saveToPhotoAlbumCompleteWithError:error];
|
||||
}];
|
||||
};
|
||||
void(^saveImage)(UIImage *) = ^(UIImage * _Nonnull image){
|
||||
UIImageWriteToSavedPhotosAlbum(image, self, @selector(UIImageWriteToSavedPhotosAlbum_completedWithImage:error:context:), NULL);
|
||||
};
|
||||
void(^unableToSave)(void) = ^(){
|
||||
[self.yb_auxiliaryViewHandler() yb_showIncorrectToastWithContainer:self.yb_containerView text:[YBIBCopywriter sharedCopywriter].unableToSave];
|
||||
};
|
||||
|
||||
[YBIBPhotoAlbumManager getPhotoAlbumAuthorizationSuccess:^{
|
||||
if ([self.originImage conformsToProtocol:@protocol(YYAnimatedImage)] && [self.originImage respondsToSelector:@selector(animatedImageData)] && [self.originImage performSelector:@selector(animatedImageData)]) {
|
||||
NSData *data = [self.originImage performSelector:@selector(animatedImageData)];
|
||||
data ? saveData(data) : unableToSave();
|
||||
} else if (self.originImage) {
|
||||
saveImage(self.originImage);
|
||||
} else if (self.imageURL) {
|
||||
[self.yb_webImageMediator() yb_queryCacheOperationForKey:self.imageURL completed:^(UIImage * _Nullable image, NSData * _Nullable data) {
|
||||
if (data) {
|
||||
saveData(data);
|
||||
} else if (image) {
|
||||
saveImage(image);
|
||||
} else {
|
||||
unableToSave();
|
||||
}
|
||||
}];
|
||||
} else {
|
||||
unableToSave();
|
||||
}
|
||||
} failed:^{
|
||||
[self.yb_auxiliaryViewHandler() yb_showIncorrectToastWithContainer:self.yb_containerView text:[YBIBCopywriter sharedCopywriter].getPhotoAlbumAuthorizationFailed];
|
||||
}];
|
||||
}
|
||||
|
||||
#pragma mark - getters & setters
|
||||
|
||||
@synthesize delegate = _delegate;
|
||||
- (void)setDelegate:(id<YBIBImageDataDelegate>)delegate {
|
||||
_delegate = delegate;
|
||||
if (delegate) {
|
||||
[self loadData];
|
||||
} else {
|
||||
_freezing = YES;
|
||||
// Remove the resident cache if '_delegate' is nil.
|
||||
[self.imageCache removeResidentForKey:self.cacheKey];
|
||||
}
|
||||
}
|
||||
- (id<YBIBImageDataDelegate>)delegate {
|
||||
// Stop sending data to the '_delegate' if it is transiting.
|
||||
return self.yb_isHideTransitioning() ? nil : _delegate;
|
||||
}
|
||||
|
||||
- (void)setImageURL:(NSURL *)imageURL {
|
||||
_imageURL = [imageURL isKindOfClass:NSString.class] ? [NSURL URLWithString:(NSString *)imageURL] : imageURL;
|
||||
}
|
||||
|
||||
- (NSString *)cacheKey {
|
||||
return [NSString stringWithFormat:@"%p", self];
|
||||
}
|
||||
|
||||
- (void)setOriginImage:(__kindof UIImage *)originImage {
|
||||
// 'image' should be resident if '_delegate' exists.
|
||||
[self.imageCache setImage:originImage type:YBIBImageCacheTypeOrigin forKey:self.cacheKey resident:self->_delegate != nil];
|
||||
}
|
||||
- (UIImage *)originImage {
|
||||
return [self.imageCache imageForKey:self.cacheKey type:YBIBImageCacheTypeOrigin];
|
||||
}
|
||||
|
||||
- (void)setCompressedImage:(UIImage *)compressedImage {
|
||||
// 'image' should be resident if '_delegate' exists.
|
||||
[self.imageCache setImage:compressedImage type:YBIBImageCacheTypeCompressed forKey:self.cacheKey resident:_delegate != nil];
|
||||
}
|
||||
- (UIImage *)compressedImage {
|
||||
return [self.imageCache imageForKey:self.cacheKey type:YBIBImageCacheTypeCompressed];
|
||||
}
|
||||
|
||||
- (void)setLoadingStatus:(YBIBImageLoadingStatus)loadingStatus {
|
||||
// Ensure thread safety.
|
||||
YBIB_DISPATCH_ASYNC_MAIN(^{
|
||||
self->_loadingStatus = loadingStatus;
|
||||
[self.delegate yb_imageData:self startLoadingWithStatus:loadingStatus];
|
||||
});
|
||||
}
|
||||
|
||||
- (CGFloat)cuttingZoomScale {
|
||||
if (_cuttingZoomScale >= 1) return _cuttingZoomScale;
|
||||
_cuttingZoomScale = 1.1;
|
||||
if (!self.originImage) return _cuttingZoomScale;
|
||||
CGFloat imagePixel = self.originImage.size.width * self.originImage.size.height * self.originImage.scale * self.originImage.scale;
|
||||
// The larger the image size, the larger the '_cuttingZoomScale', in order to reduce the burden of CPU and memory.
|
||||
CGFloat scale = YBIBLowMemory() ? 0.28 : 0.19;
|
||||
_cuttingZoomScale += (imagePixel / self.compressingSize * scale);
|
||||
return _cuttingZoomScale;
|
||||
}
|
||||
|
||||
@end
|
||||
61
Pods/YBImageBrowser/YBImageBrowser/Image/YBIBImageLayout.h
generated
Normal file
@@ -0,0 +1,61 @@
|
||||
//
|
||||
// YBIBImageLayout.h
|
||||
// YBImageBrowserDemo
|
||||
//
|
||||
// Created by 波儿菜 on 2019/6/12.
|
||||
// Copyright © 2019 波儿菜. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@protocol YBIBImageLayout <NSObject>
|
||||
@required
|
||||
|
||||
/**
|
||||
计算图片展示的位置
|
||||
|
||||
@param containerSize 容器大小
|
||||
@param imageSize 图片大小 (逻辑像素)
|
||||
@param orientation 图片浏览器的方向
|
||||
@return 图片展示的位置 (frame)
|
||||
*/
|
||||
- (CGRect)yb_imageViewFrameWithContainerSize:(CGSize)containerSize imageSize:(CGSize)imageSize orientation:(UIDeviceOrientation)orientation;
|
||||
|
||||
/**
|
||||
计算最大缩放比例
|
||||
|
||||
@param containerSize 容器大小
|
||||
@param imageSize 图片大小 (逻辑像素)
|
||||
@param orientation 图片浏览器的方向
|
||||
@return 最大缩放比例
|
||||
*/
|
||||
- (CGFloat)yb_maximumZoomScaleWithContainerSize:(CGSize)containerSize imageSize:(CGSize)imageSize orientation:(UIDeviceOrientation)orientation;
|
||||
|
||||
@end
|
||||
|
||||
typedef NS_ENUM(NSUInteger, YBIBImageFillType) {
|
||||
/// 宽度优先填充满
|
||||
YBIBImageFillTypeFullWidth,
|
||||
/// 完整显示
|
||||
YBIBImageFillTypeCompletely
|
||||
};
|
||||
|
||||
@interface YBIBImageLayout : NSObject <YBIBImageLayout>
|
||||
|
||||
/// 纵向的填充方式,默认 YBIBImageFillTypeCompletely
|
||||
@property (nonatomic, assign) YBIBImageFillType verticalFillType;
|
||||
|
||||
/// 横向的填充方式,默认 YBIBImageFillTypeFullWidth
|
||||
@property (nonatomic, assign) YBIBImageFillType horizontalFillType;
|
||||
|
||||
/// 最大缩放比例 (必须大于 1 才有效,若不指定内部会自动计算)
|
||||
@property (nonatomic, assign) CGFloat maxZoomScale;
|
||||
|
||||
/// 自动计算严格缩放比例后,再乘以这个值作为最终缩放比例,默认 1.5
|
||||
@property (nonatomic, assign) CGFloat zoomScaleSurplus;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
90
Pods/YBImageBrowser/YBImageBrowser/Image/YBIBImageLayout.m
generated
Normal file
@@ -0,0 +1,90 @@
|
||||
//
|
||||
// YBIBImageLayout.m
|
||||
// YBImageBrowserDemo
|
||||
//
|
||||
// Created by 波儿菜 on 2019/6/12.
|
||||
// Copyright © 2019 波儿菜. All rights reserved.
|
||||
//
|
||||
|
||||
#import "YBIBImageLayout.h"
|
||||
|
||||
@implementation YBIBImageLayout
|
||||
|
||||
#pragma mark - life cycle
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_verticalFillType = YBIBImageFillTypeCompletely;
|
||||
_horizontalFillType = YBIBImageFillTypeFullWidth;
|
||||
_zoomScaleSurplus = 1.5;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark - private
|
||||
|
||||
- (YBIBImageFillType)fillTypeByOrientation:(UIDeviceOrientation)orientation {
|
||||
return UIDeviceOrientationIsLandscape(orientation) ? self.horizontalFillType : self.verticalFillType;
|
||||
}
|
||||
|
||||
#pragma mark - <YBIBImageLayout>
|
||||
|
||||
- (CGRect)yb_imageViewFrameWithContainerSize:(CGSize)containerSize imageSize:(CGSize)imageSize orientation:(UIDeviceOrientation)orientation {
|
||||
if (containerSize.width <= 0 || containerSize.height <= 0 || imageSize.width <= 0 || imageSize.height <= 0) return CGRectZero;
|
||||
|
||||
CGFloat x = 0, y = 0, width = 0, height = 0;
|
||||
switch ([self fillTypeByOrientation:orientation]) {
|
||||
case YBIBImageFillTypeFullWidth: {
|
||||
x = 0;
|
||||
width = containerSize.width;
|
||||
height = containerSize.width * (imageSize.height / imageSize.width);
|
||||
if (imageSize.width / imageSize.height >= containerSize.width / containerSize.height)
|
||||
y = (containerSize.height - height) / 2.0;
|
||||
else
|
||||
y = 0;
|
||||
}
|
||||
break;
|
||||
case YBIBImageFillTypeCompletely: {
|
||||
if (imageSize.width / imageSize.height >= containerSize.width / containerSize.height) {
|
||||
width = containerSize.width;
|
||||
height = containerSize.width * (imageSize.height / imageSize.width);
|
||||
x = 0;
|
||||
y = (containerSize.height - height) / 2.0;
|
||||
} else {
|
||||
height = containerSize.height;
|
||||
width = containerSize.height * (imageSize.width / imageSize.height);
|
||||
x = (containerSize.width - width) / 2.0;
|
||||
y = 0;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default: return CGRectZero;
|
||||
}
|
||||
return CGRectMake(x, y, width, height);
|
||||
}
|
||||
|
||||
- (CGFloat)yb_maximumZoomScaleWithContainerSize:(CGSize)containerSize imageSize:(CGSize)imageSize orientation:(UIDeviceOrientation)orientation {
|
||||
if (self.maxZoomScale >= 1) return self.maxZoomScale;
|
||||
|
||||
if (containerSize.width <= 0 || containerSize.height <= 0) return 0;
|
||||
|
||||
CGFloat scale = [UIScreen mainScreen].scale;
|
||||
if (scale <= 0) return 0;
|
||||
|
||||
CGFloat widthScale = imageSize.width / scale / containerSize.width,
|
||||
heightScale = imageSize.height / scale / containerSize.height,
|
||||
maxScale = 1;
|
||||
switch ([self fillTypeByOrientation:orientation]) {
|
||||
case YBIBImageFillTypeFullWidth:
|
||||
maxScale = widthScale;
|
||||
break;
|
||||
case YBIBImageFillTypeCompletely:
|
||||
maxScale = MAX(widthScale, heightScale);
|
||||
break;
|
||||
default: return 0;
|
||||
}
|
||||
return MAX(maxScale, 1) * self.zoomScaleSurplus;
|
||||
}
|
||||
|
||||
@end
|
||||
32
Pods/YBImageBrowser/YBImageBrowser/Image/YBIBImageScrollView.h
generated
Normal file
@@ -0,0 +1,32 @@
|
||||
//
|
||||
// YBIBImageScrollView.h
|
||||
// YBImageBrowserDemo
|
||||
//
|
||||
// Created by 波儿菜 on 2019/6/10.
|
||||
// Copyright © 2019 波儿菜. All rights reserved.
|
||||
//
|
||||
|
||||
#import "YBImage.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
typedef NS_ENUM(NSInteger, YBIBScrollImageType) {
|
||||
YBIBScrollImageTypeNone,
|
||||
YBIBScrollImageTypeOriginal,
|
||||
YBIBScrollImageTypeCompressed,
|
||||
YBIBScrollImageTypeThumb
|
||||
};
|
||||
|
||||
@interface YBIBImageScrollView : UIScrollView
|
||||
|
||||
- (void)setImage:(__kindof UIImage *)image type:(YBIBScrollImageType)type;
|
||||
|
||||
@property (nonatomic, strong, readonly) YYAnimatedImageView *imageView;
|
||||
|
||||
@property (nonatomic, assign) YBIBScrollImageType imageType;
|
||||
|
||||
- (void)reset;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
64
Pods/YBImageBrowser/YBImageBrowser/Image/YBIBImageScrollView.m
generated
Normal file
@@ -0,0 +1,64 @@
|
||||
//
|
||||
// YBIBImageScrollView.m
|
||||
// YBImageBrowserDemo
|
||||
//
|
||||
// Created by 波儿菜 on 2019/6/10.
|
||||
// Copyright © 2019 波儿菜. All rights reserved.
|
||||
//
|
||||
|
||||
#import "YBIBImageScrollView.h"
|
||||
|
||||
@interface YBIBImageScrollView ()
|
||||
@property (nonatomic, strong) YYAnimatedImageView *imageView;
|
||||
@end
|
||||
|
||||
@implementation YBIBImageScrollView
|
||||
|
||||
#pragma mark - life cycle
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
if (self) {
|
||||
self.showsHorizontalScrollIndicator = NO;
|
||||
self.showsVerticalScrollIndicator = NO;
|
||||
self.decelerationRate = UIScrollViewDecelerationRateFast;
|
||||
self.maximumZoomScale = 1;
|
||||
self.minimumZoomScale = 1;
|
||||
self.alwaysBounceHorizontal = NO;
|
||||
self.alwaysBounceVertical = NO;
|
||||
self.layer.masksToBounds = NO;
|
||||
if (@available(iOS 11.0, *)) {
|
||||
self.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
|
||||
}
|
||||
|
||||
[self addSubview:self.imageView];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark - public
|
||||
|
||||
- (void)setImage:(__kindof UIImage *)image type:(YBIBScrollImageType)type {
|
||||
self.imageView.image = image;
|
||||
self.imageType = type;
|
||||
}
|
||||
|
||||
- (void)reset {
|
||||
self.zoomScale = 1;
|
||||
self.imageView.image = nil;
|
||||
self.imageView.frame = CGRectZero;
|
||||
self.imageType = YBIBScrollImageTypeNone;
|
||||
}
|
||||
|
||||
#pragma mark - getters
|
||||
|
||||
- (YYAnimatedImageView *)imageView {
|
||||
if (!_imageView) {
|
||||
_imageView = [YYAnimatedImageView new];
|
||||
_imageView.contentMode = UIViewContentModeScaleAspectFill;
|
||||
_imageView.layer.masksToBounds = YES;
|
||||
}
|
||||
return _imageView;
|
||||
}
|
||||
|
||||
@end
|
||||
32
Pods/YBImageBrowser/YBImageBrowser/Image/YBIBInteractionProfile.h
generated
Normal file
@@ -0,0 +1,32 @@
|
||||
//
|
||||
// YBIBInteractionProfile.h
|
||||
// YBImageBrowserDemo
|
||||
//
|
||||
// Created by 波儿菜 on 2019/6/30.
|
||||
// Copyright © 2019 杨波. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface YBIBInteractionProfile : NSObject
|
||||
|
||||
/// 是否取消手势交互动效
|
||||
@property (nonatomic, assign) BOOL disable;
|
||||
|
||||
/// 拖动的距离与最大高度的比例,达到这个比例就会出场
|
||||
@property (nonatomic, assign) CGFloat dismissScale;
|
||||
|
||||
/// 拖动的速度,达到这个值就会出场
|
||||
@property (nonatomic, assign) CGFloat dismissVelocityY;
|
||||
|
||||
/// 拖动动效复位时的时长
|
||||
@property (nonatomic, assign) CGFloat restoreDuration;
|
||||
|
||||
/// 拖动触发手势交互动效的起始距离
|
||||
@property (nonatomic, assign) CGFloat triggerDistance;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
25
Pods/YBImageBrowser/YBImageBrowser/Image/YBIBInteractionProfile.m
generated
Normal file
@@ -0,0 +1,25 @@
|
||||
//
|
||||
// YBIBInteractionProfile.m
|
||||
// YBImageBrowserDemo
|
||||
//
|
||||
// Created by 波儿菜 on 2019/6/30.
|
||||
// Copyright © 2019 杨波. All rights reserved.
|
||||
//
|
||||
|
||||
#import "YBIBInteractionProfile.h"
|
||||
|
||||
@implementation YBIBInteractionProfile
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_disable = NO;
|
||||
_dismissScale = 0.22;
|
||||
_dismissVelocityY = 800;
|
||||
_restoreDuration = 0.15;
|
||||
_triggerDistance = 3;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
@end
|
||||
89
Pods/YBImageBrowser/YBImageBrowser/Image/YBImage.h
generated
Normal file
@@ -0,0 +1,89 @@
|
||||
//
|
||||
// YBImage.h
|
||||
// YBImageBrowserDemo
|
||||
//
|
||||
// Created by 波儿菜 on 2018/8/31.
|
||||
// Copyright © 2018年 波儿菜. All rights reserved.
|
||||
//
|
||||
|
||||
#if __has_include(<YYImage/YYImage.h>)
|
||||
#import <YYImage/YYFrameImage.h>
|
||||
#import <YYImage/YYSpriteSheetImage.h>
|
||||
#import <YYImage/YYImageCoder.h>
|
||||
#import <YYImage/YYAnimatedImageView.h>
|
||||
#elif __has_include(<YYWebImage/YYImage.h>)
|
||||
#import <YYWebImage/YYFrameImage.h>
|
||||
#import <YYWebImage/YYSpriteSheetImage.h>
|
||||
#import <YYWebImage/YYImageCoder.h>
|
||||
#import <YYWebImage/YYAnimatedImageView.h>
|
||||
#else
|
||||
#import "YYFrameImage.h"
|
||||
#import "YYSpriteSheetImage.h"
|
||||
#import "YYImageCoder.h"
|
||||
#import "YYAnimatedImageView.h"
|
||||
#endif
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/// 🙄波儿菜:Decide whether should to decode by image size. ('imageSize': physical pixel)
|
||||
typedef BOOL(^YBImageDecodeDecision)(CGSize imageSize, CGFloat scale);
|
||||
|
||||
/**
|
||||
It is a fully compatible `UIImage` subclass. It extends the UIImage
|
||||
to support animated WebP, APNG and GIF format image data decoding. It also
|
||||
support NSCoding protocol to archive and unarchive multi-frame image data.
|
||||
|
||||
If the image is created from multi-frame image data, and you want to play the
|
||||
animation, try replace UIImageView with `YYAnimatedImageView`.
|
||||
|
||||
🙄波儿菜:Copied from 'YYImage' and made some extensions.
|
||||
*/
|
||||
|
||||
@interface YBImage : UIImage <YYAnimatedImage>
|
||||
|
||||
+ (nullable __kindof UIImage *)imageNamed:(NSString *)name; // no cache!
|
||||
+ (nullable YBImage *)imageWithContentsOfFile:(NSString *)path;
|
||||
+ (nullable YBImage *)imageWithData:(NSData *)data;
|
||||
+ (nullable YBImage *)imageWithData:(NSData *)data scale:(CGFloat)scale;
|
||||
|
||||
/// 🙄波儿菜:Expand methodes.
|
||||
/// Start ->
|
||||
+ (nullable __kindof UIImage *)imageNamed:(NSString *)name decodeDecision:(nullable YBImageDecodeDecision)decodeDecision;
|
||||
+ (nullable YBImage *)imageWithContentsOfFile:(NSString *)path decodeDecision:(nullable YBImageDecodeDecision)decodeDecision;
|
||||
+ (nullable YBImage *)imageWithData:(NSData *)data decodeDecision:(nullable YBImageDecodeDecision)decodeDecision;
|
||||
+ (nullable YBImage *)imageWithData:(NSData *)data scale:(CGFloat)scale decodeDecision:(nullable YBImageDecodeDecision)decodeDecision;
|
||||
/// <- End
|
||||
|
||||
/**
|
||||
If the image is created from data or file, then the value indicates the data type.
|
||||
*/
|
||||
@property (nonatomic, readonly) YYImageType animatedImageType;
|
||||
|
||||
/**
|
||||
If the image is created from animated image data (multi-frame GIF/APNG/WebP),
|
||||
this property stores the original image data.
|
||||
*/
|
||||
@property (nullable, nonatomic, readonly) NSData *animatedImageData;
|
||||
|
||||
/**
|
||||
The total memory usage (in bytes) if all frame images was loaded into memory.
|
||||
The value is 0 if the image is not created from a multi-frame image data.
|
||||
*/
|
||||
@property (nonatomic, readonly) NSUInteger animatedImageMemorySize;
|
||||
|
||||
/**
|
||||
Preload all frame image to memory.
|
||||
|
||||
@discussion Set this property to `YES` will block the calling thread to decode
|
||||
all animation frame image to memory, set to `NO` will release the preloaded frames.
|
||||
If the image is shared by lots of image views (such as emoticon), preload all
|
||||
frames will reduce the CPU cost.
|
||||
|
||||
See `animatedImageMemorySize` for memory cost.
|
||||
*/
|
||||
@property (nonatomic) BOOL preloadAllAnimatedImageFrames;
|
||||
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
284
Pods/YBImageBrowser/YBImageBrowser/Image/YBImage.m
generated
Normal file
@@ -0,0 +1,284 @@
|
||||
//
|
||||
// YBImage.m
|
||||
// YBImageBrowserDemo
|
||||
//
|
||||
// Created by 波儿菜 on 2018/8/31.
|
||||
// Copyright © 2018年 波儿菜. All rights reserved.
|
||||
//
|
||||
|
||||
#import "YBImage.h"
|
||||
#import "YBIBImageData.h"
|
||||
#import "YBIBUtilities.h"
|
||||
|
||||
/**
|
||||
An array of NSNumber objects, shows the best order for path scale search.
|
||||
e.g. iPhone3GS:@[@1,@2,@3] iPhone5:@[@2,@3,@1] iPhone6 Plus:@[@3,@2,@1]
|
||||
*/
|
||||
static NSArray *_NSBundlePreferredScales() {
|
||||
static NSArray *scales;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
CGFloat screenScale = [UIScreen mainScreen].scale;
|
||||
if (screenScale <= 1) {
|
||||
scales = @[@1,@2,@3];
|
||||
} else if (screenScale <= 2) {
|
||||
scales = @[@2,@3,@1];
|
||||
} else {
|
||||
scales = @[@3,@2,@1];
|
||||
}
|
||||
});
|
||||
return scales;
|
||||
}
|
||||
|
||||
/**
|
||||
Add scale modifier to the file name (without path extension),
|
||||
From @"name" to @"name@2x".
|
||||
|
||||
e.g.
|
||||
<table>
|
||||
<tr><th>Before </th><th>After(scale:2)</th></tr>
|
||||
<tr><td>"icon" </td><td>"icon@2x" </td></tr>
|
||||
<tr><td>"icon " </td><td>"icon @2x" </td></tr>
|
||||
<tr><td>"icon.top" </td><td>"icon.top@2x" </td></tr>
|
||||
<tr><td>"/p/name" </td><td>"/p/name@2x" </td></tr>
|
||||
<tr><td>"/path/" </td><td>"/path/" </td></tr>
|
||||
</table>
|
||||
|
||||
@param scale Resource scale.
|
||||
@return String by add scale modifier, or just return if it's not end with file name.
|
||||
*/
|
||||
static NSString *_NSStringByAppendingNameScale(NSString *string, CGFloat scale) {
|
||||
if (!string) return nil;
|
||||
if (fabs(scale - 1) <= __FLT_EPSILON__ || string.length == 0 || [string hasSuffix:@"/"]) return string.copy;
|
||||
return [string stringByAppendingFormat:@"@%@x", @(scale)];
|
||||
}
|
||||
|
||||
/**
|
||||
Return the path scale.
|
||||
|
||||
e.g.
|
||||
<table>
|
||||
<tr><th>Path </th><th>Scale </th></tr>
|
||||
<tr><td>"icon.png" </td><td>1 </td></tr>
|
||||
<tr><td>"icon@2x.png" </td><td>2 </td></tr>
|
||||
<tr><td>"icon@2.5x.png" </td><td>2.5 </td></tr>
|
||||
<tr><td>"icon@2x" </td><td>1 </td></tr>
|
||||
<tr><td>"icon@2x..png" </td><td>1 </td></tr>
|
||||
<tr><td>"icon@2x.png/" </td><td>1 </td></tr>
|
||||
</table>
|
||||
*/
|
||||
static CGFloat _NSStringPathScale(NSString *string) {
|
||||
if (string.length == 0 || [string hasSuffix:@"/"]) return 1;
|
||||
NSString *name = string.stringByDeletingPathExtension;
|
||||
__block CGFloat scale = 1;
|
||||
|
||||
NSRegularExpression *pattern = [NSRegularExpression regularExpressionWithPattern:@"@[0-9]+\\.?[0-9]*x$" options:NSRegularExpressionAnchorsMatchLines error:nil];
|
||||
[pattern enumerateMatchesInString:name options:kNilOptions range:NSMakeRange(0, name.length) usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {
|
||||
if (result.range.location >= 3) {
|
||||
scale = [string substringWithRange:NSMakeRange(result.range.location + 1, result.range.length - 2)].doubleValue;
|
||||
}
|
||||
}];
|
||||
|
||||
return scale;
|
||||
}
|
||||
|
||||
|
||||
@implementation YBImage {
|
||||
YYImageDecoder *_decoder;
|
||||
NSArray *_preloadedFrames;
|
||||
dispatch_semaphore_t _preloadedLock;
|
||||
NSUInteger _bytesPerFrame;
|
||||
}
|
||||
|
||||
+ (__kindof UIImage *)imageNamed:(NSString *)name {
|
||||
return [self imageNamed:name decodeDecision:nil];
|
||||
}
|
||||
+ (__kindof UIImage *)imageNamed:(NSString *)name decodeDecision:(nullable YBImageDecodeDecision)decodeDecision {
|
||||
if (name.length == 0) return nil;
|
||||
if ([name hasSuffix:@"/"]) return nil;
|
||||
|
||||
NSString *res = name.stringByDeletingPathExtension;
|
||||
NSString *ext = name.pathExtension;
|
||||
NSString *path = nil;
|
||||
CGFloat scale = 1;
|
||||
|
||||
// If no extension, guess by system supported (same as UIImage).
|
||||
NSArray *exts = ext.length > 0 ? @[ext] : @[@"", @"png", @"jpeg", @"jpg", @"gif", @"webp", @"apng"];
|
||||
NSArray *scales = _NSBundlePreferredScales();
|
||||
for (int s = 0; s < scales.count; s++) {
|
||||
scale = ((NSNumber *)scales[s]).floatValue;
|
||||
NSString *scaledName = _NSStringByAppendingNameScale(res, scale);
|
||||
for (NSString *e in exts) {
|
||||
path = [[NSBundle mainBundle] pathForResource:scaledName ofType:e];
|
||||
if (path) break;
|
||||
}
|
||||
if (path) break;
|
||||
}
|
||||
if (path.length == 0) {
|
||||
// 🙄波儿菜:Assets.xcassets supported.
|
||||
return [super imageNamed:name];
|
||||
}
|
||||
|
||||
NSData *data = [NSData dataWithContentsOfFile:path];
|
||||
if (data.length == 0) return nil;
|
||||
|
||||
return [[self alloc] initWithData:data scale:scale decodeDecision:decodeDecision];
|
||||
}
|
||||
|
||||
+ (YBImage *)imageWithContentsOfFile:(NSString *)path {
|
||||
return [self imageWithContentsOfFile:path decodeDecision:nil];
|
||||
}
|
||||
+ (YBImage *)imageWithContentsOfFile:(NSString *)path decodeDecision:(nullable YBImageDecodeDecision)decodeDecision {
|
||||
return [[self alloc] initWithContentsOfFile:path decodeDecision:decodeDecision];
|
||||
}
|
||||
|
||||
+ (YBImage *)imageWithData:(NSData *)data {
|
||||
return [self imageWithData:data decodeDecision:nil];
|
||||
}
|
||||
+ (YBImage *)imageWithData:(NSData *)data decodeDecision:(nullable YBImageDecodeDecision)decodeDecision {
|
||||
return [[self alloc] initWithData:data decodeDecision:decodeDecision];
|
||||
}
|
||||
|
||||
+ (YBImage *)imageWithData:(NSData *)data scale:(CGFloat)scale {
|
||||
return [self imageWithData:data scale:scale decodeDecision:nil];
|
||||
}
|
||||
+ (YBImage *)imageWithData:(NSData *)data scale:(CGFloat)scale decodeDecision:(nullable YBImageDecodeDecision)decodeDecision {
|
||||
return [[self alloc] initWithData:data scale:scale decodeDecision:decodeDecision];
|
||||
}
|
||||
|
||||
- (instancetype)initWithContentsOfFile:(NSString *)path {
|
||||
return [self initWithContentsOfFile:path decodeDecision:nil];
|
||||
}
|
||||
- (instancetype)initWithContentsOfFile:(NSString *)path decodeDecision:(nullable YBImageDecodeDecision)decodeDecision {
|
||||
NSData *data = [NSData dataWithContentsOfFile:path];
|
||||
return [self initWithData:data scale:_NSStringPathScale(path) decodeDecision:decodeDecision];
|
||||
}
|
||||
|
||||
- (instancetype)initWithData:(NSData *)data {
|
||||
return [self initWithData:data decodeDecision:nil];
|
||||
}
|
||||
- (instancetype)initWithData:(NSData *)data decodeDecision:(nullable YBImageDecodeDecision)decodeDecision {
|
||||
return [self initWithData:data scale:1 decodeDecision:decodeDecision];
|
||||
}
|
||||
|
||||
- (instancetype)initWithData:(NSData *)data scale:(CGFloat)scale {
|
||||
return [self initWithData:data scale:scale decodeDecision:nil];
|
||||
}
|
||||
- (instancetype)initWithData:(NSData *)data scale:(CGFloat)scale decodeDecision:(nullable YBImageDecodeDecision)decodeDecision {
|
||||
if (data.length == 0) return nil;
|
||||
if (scale <= 0) scale = [UIScreen mainScreen].scale;
|
||||
_preloadedLock = dispatch_semaphore_create(1);
|
||||
@autoreleasepool {
|
||||
YYImageDecoder *decoder = [YYImageDecoder decoderWithData:data scale:scale];
|
||||
|
||||
// 🙄波儿菜:Determine whether should to decode.
|
||||
BOOL decodeForDisplay = YES;
|
||||
if (decodeDecision) {
|
||||
decodeForDisplay = decodeDecision(CGSizeMake(decoder.width, decoder.height), decoder.scale ?: 1);
|
||||
}
|
||||
|
||||
YYImageFrame *frame = [decoder frameAtIndex:0 decodeForDisplay:decodeForDisplay];
|
||||
UIImage *image = frame.image;
|
||||
if (!image) return nil;
|
||||
self = [self initWithCGImage:image.CGImage scale:decoder.scale orientation:image.imageOrientation];
|
||||
if (!self) return nil;
|
||||
_animatedImageType = decoder.type;
|
||||
if (decoder.frameCount > 1) {
|
||||
_decoder = decoder;
|
||||
_bytesPerFrame = CGImageGetBytesPerRow(image.CGImage) * CGImageGetHeight(image.CGImage);
|
||||
_animatedImageMemorySize = _bytesPerFrame * decoder.frameCount;
|
||||
}
|
||||
self.yy_isDecodedForDisplay = YES;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSData *)animatedImageData {
|
||||
return _decoder.data;
|
||||
}
|
||||
|
||||
- (void)setPreloadAllAnimatedImageFrames:(BOOL)preloadAllAnimatedImageFrames {
|
||||
if (_preloadAllAnimatedImageFrames != preloadAllAnimatedImageFrames) {
|
||||
if (preloadAllAnimatedImageFrames && _decoder.frameCount > 0) {
|
||||
NSMutableArray *frames = [NSMutableArray new];
|
||||
for (NSUInteger i = 0, max = _decoder.frameCount; i < max; i++) {
|
||||
UIImage *img = [self animatedImageFrameAtIndex:i];
|
||||
if (img) {
|
||||
[frames addObject:img];
|
||||
} else {
|
||||
[frames addObject:[NSNull null]];
|
||||
}
|
||||
}
|
||||
dispatch_semaphore_wait(_preloadedLock, DISPATCH_TIME_FOREVER);
|
||||
_preloadedFrames = frames;
|
||||
dispatch_semaphore_signal(_preloadedLock);
|
||||
} else {
|
||||
dispatch_semaphore_wait(_preloadedLock, DISPATCH_TIME_FOREVER);
|
||||
_preloadedFrames = nil;
|
||||
dispatch_semaphore_signal(_preloadedLock);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - protocol NSCoding
|
||||
|
||||
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
|
||||
NSNumber *scale = [aDecoder decodeObjectForKey:@"YYImageScale"];
|
||||
NSData *data = [aDecoder decodeObjectForKey:@"YYImageData"];
|
||||
if (data.length) {
|
||||
self = [self initWithData:data scale:scale.doubleValue];
|
||||
} else {
|
||||
self = [super initWithCoder:aDecoder];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(NSCoder *)aCoder {
|
||||
if (_decoder.data.length) {
|
||||
[aCoder encodeObject:@(self.scale) forKey:@"YYImageScale"];
|
||||
[aCoder encodeObject:_decoder.data forKey:@"YYImageData"];
|
||||
} else {
|
||||
[super encodeWithCoder:aCoder]; // Apple use UIImagePNGRepresentation() to encode UIImage.
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - protocol YYAnimatedImage
|
||||
|
||||
- (NSUInteger)animatedImageFrameCount {
|
||||
return _decoder.frameCount;
|
||||
}
|
||||
|
||||
- (NSUInteger)animatedImageLoopCount {
|
||||
return _decoder.loopCount;
|
||||
}
|
||||
|
||||
- (NSUInteger)animatedImageBytesPerFrame {
|
||||
return _bytesPerFrame;
|
||||
}
|
||||
|
||||
- (UIImage *)animatedImageFrameAtIndex:(NSUInteger)index {
|
||||
if (index >= _decoder.frameCount) return nil;
|
||||
dispatch_semaphore_wait(_preloadedLock, DISPATCH_TIME_FOREVER);
|
||||
UIImage *image = _preloadedFrames[index];
|
||||
dispatch_semaphore_signal(_preloadedLock);
|
||||
if (image) return image == (id)[NSNull null] ? nil : image;
|
||||
return [_decoder frameAtIndex:index decodeForDisplay:YES].image;
|
||||
}
|
||||
|
||||
- (NSTimeInterval)animatedImageDurationAtIndex:(NSUInteger)index {
|
||||
NSTimeInterval duration = [_decoder frameDurationAtIndex:index];
|
||||
|
||||
/*
|
||||
http://opensource.apple.com/source/WebCore/WebCore-7600.1.25/platform/graphics/cg/ImageSourceCG.cpp
|
||||
Many annoying ads specify a 0 duration to make an image flash as quickly as
|
||||
possible. We follow Safari and Firefox's behavior and use a duration of 100 ms
|
||||
for any frames that specify a duration of <= 10 ms.
|
||||
See <rdar://problem/7689300> and <http://webkit.org/b/36082> for more information.
|
||||
|
||||
See also: http://nullsleep.tumblr.com/post/16524517190/animated-gif-minimum-frame-delay-browser.
|
||||
*/
|
||||
if (duration < 0.011f) return 0.100f;
|
||||
return duration;
|
||||
}
|
||||
|
||||
@end
|
||||
43
Pods/YBImageBrowser/YBImageBrowser/Protocol/YBIBCellProtocol.h
generated
Normal file
@@ -0,0 +1,43 @@
|
||||
//
|
||||
// YBIBCellProtocol.h
|
||||
// YBImageBrowserDemo
|
||||
//
|
||||
// Created by 波儿菜 on 2019/6/5.
|
||||
// Copyright © 2019 波儿菜. All rights reserved.
|
||||
//
|
||||
|
||||
#import "YBIBGetBaseInfoProtocol.h"
|
||||
#import "YBIBOrientationReceiveProtocol.h"
|
||||
#import "YBIBOperateBrowserProtocol.h"
|
||||
|
||||
@protocol YBIBDataProtocol;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@protocol YBIBCellProtocol <YBIBGetBaseInfoProtocol, YBIBOperateBrowserProtocol, YBIBOrientationReceiveProtocol>
|
||||
|
||||
@required
|
||||
|
||||
/// Cell 对应的 Data
|
||||
@property (nonatomic, strong) id<YBIBDataProtocol> yb_cellData;
|
||||
|
||||
@optional
|
||||
|
||||
/**
|
||||
获取前景视图,出入场时需要用这个返回值做动效
|
||||
|
||||
@return 前景视图
|
||||
*/
|
||||
- (__kindof UIView *)yb_foregroundView;
|
||||
|
||||
/**
|
||||
页码变化了
|
||||
*/
|
||||
- (void)yb_pageChanged;
|
||||
|
||||
/// 当前 Cell 的页码
|
||||
@property (nonatomic, copy) NSInteger(^yb_selfPage)(void);
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
64
Pods/YBImageBrowser/YBImageBrowser/Protocol/YBIBDataProtocol.h
generated
Normal file
@@ -0,0 +1,64 @@
|
||||
//
|
||||
// YBIBDataProtocol.h
|
||||
// YBImageBrowserDemo
|
||||
//
|
||||
// Created by 波儿菜 on 2019/6/5.
|
||||
// Copyright © 2019 波儿菜. All rights reserved.
|
||||
//
|
||||
|
||||
#import "YBIBGetBaseInfoProtocol.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@protocol YBIBDataProtocol <YBIBGetBaseInfoProtocol>
|
||||
|
||||
@required
|
||||
|
||||
/**
|
||||
当前 Data 对应 Cell 的类类型
|
||||
|
||||
@return Class 类型
|
||||
*/
|
||||
- (Class)yb_classOfCell;
|
||||
|
||||
@optional
|
||||
|
||||
/**
|
||||
获取投影视图,当前数据模型对应外界业务的 UIView (通常为 UIImageView),做转场动效用
|
||||
|
||||
这个方法会在做出入场动效时调用,若未实现时将无法进行平滑的入场
|
||||
|
||||
@return 投影视图
|
||||
*/
|
||||
- (__kindof UIView *)yb_projectiveView;
|
||||
|
||||
/**
|
||||
通过一系列数据,计算并返回图片视图在容器中的 frame
|
||||
|
||||
这个方法会在做入场动效时调用,若未实现时将无法进行平滑的入场
|
||||
|
||||
@param containerSize 容器大小
|
||||
@param imageSize 图片大小 (逻辑像素)
|
||||
@param orientation 图片浏览器的方向
|
||||
@return 计算好的 frame
|
||||
*/
|
||||
- (CGRect)yb_imageViewFrameWithContainerSize:(CGSize)containerSize imageSize:(CGSize)imageSize orientation:(UIDeviceOrientation)orientation;
|
||||
|
||||
/**
|
||||
预加载数据,有效的预加载能提高性能,请注意管理内存
|
||||
*/
|
||||
- (void)yb_preload;
|
||||
|
||||
/**
|
||||
保存到相册
|
||||
*/
|
||||
- (void)yb_saveToPhotoAlbum;
|
||||
|
||||
/**
|
||||
是否允许保存到相册
|
||||
*/
|
||||
- (BOOL)yb_allowSaveToPhotoAlbum;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
61
Pods/YBImageBrowser/YBImageBrowser/Protocol/YBIBGetBaseInfoProtocol.h
generated
Normal file
@@ -0,0 +1,61 @@
|
||||
//
|
||||
// YBIBGetBaseInfoProtocol.h
|
||||
// YBImageBrowserDemo
|
||||
//
|
||||
// Created by 波儿菜 on 2019/6/23.
|
||||
// Copyright © 2019 波儿菜. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "YBIBAuxiliaryViewHandler.h"
|
||||
#import "YBIBWebImageMediator.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@protocol YBIBGetBaseInfoProtocol <NSObject>
|
||||
|
||||
@optional
|
||||
|
||||
/// 当前的方向
|
||||
@property (nonatomic, copy) UIDeviceOrientation(^yb_currentOrientation)(void);
|
||||
|
||||
/// 根据方向获取容器大小
|
||||
@property (nonatomic, copy) CGSize(^yb_containerSize)(UIDeviceOrientation orientation);
|
||||
|
||||
/// 容器视图 (可在上面添加子视图)
|
||||
@property (nonatomic, weak) UIView *yb_containerView;
|
||||
|
||||
/// 辅助视图处理器
|
||||
@property (nonatomic, copy) id<YBIBAuxiliaryViewHandler>(^yb_auxiliaryViewHandler)(void);
|
||||
|
||||
/// 图片下载缓存相关中介者
|
||||
@property (nonatomic, copy) id<YBIBWebImageMediator>(^yb_webImageMediator)(void);
|
||||
|
||||
/// 当前页码
|
||||
@property (nonatomic, copy) NSInteger(^yb_currentPage)(void);
|
||||
|
||||
/// 总页码数量
|
||||
@property (nonatomic, copy) NSInteger(^yb_totalPage)(void);
|
||||
|
||||
/// 判断当前展示的 cell 是否恰好在屏幕中间
|
||||
@property (nonatomic, copy) BOOL(^yb_cellIsInCenter)(void);
|
||||
|
||||
/// 是否正在转场
|
||||
@property (nonatomic, copy) BOOL(^yb_isTransitioning)(void);
|
||||
/// 是否正在展示过程转场
|
||||
@property (nonatomic, copy) BOOL(^yb_isShowTransitioning)(void);
|
||||
/// 是否正在隐藏过程转场
|
||||
@property (nonatomic, copy) BOOL(^yb_isHideTransitioning)(void);
|
||||
|
||||
/// 是否正在旋转
|
||||
@property (nonatomic, copy) BOOL(^yb_isRotating)(void);
|
||||
|
||||
/// 背景视图 (也就是 YBImageBrower 对象,不可在上面添加子视图。作用:一是可以更改透明度和颜色,入场和出场动效有用;二是可以用来比较内存指针,在做不同实例差异化功能时可能有用,虽然不提倡这么做)
|
||||
@property (nonatomic, weak) __kindof UIView *yb_backView;
|
||||
|
||||
/// 集合视图
|
||||
@property (nonatomic, copy) __kindof UICollectionView *(^yb_collectionView)(void);
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
28
Pods/YBImageBrowser/YBImageBrowser/Protocol/YBIBOperateBrowserProtocol.h
generated
Normal file
@@ -0,0 +1,28 @@
|
||||
//
|
||||
// YBIBOperateBrowserProtocol.h
|
||||
// YBImageBrowserDemo
|
||||
//
|
||||
// Created by 波儿菜 on 2019/7/8.
|
||||
// Copyright © 2019 杨波. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@protocol YBIBOperateBrowserProtocol <NSObject>
|
||||
|
||||
@optional
|
||||
|
||||
/// 隐藏图片浏览器
|
||||
@property (nonatomic, copy) void(^yb_hideBrowser)(void);
|
||||
|
||||
/// 是否隐藏状态栏
|
||||
@property (nonatomic, copy) void(^yb_hideStatusBar)(BOOL);
|
||||
|
||||
/// 是否隐藏工具视图
|
||||
@property (nonatomic, copy) void(^yb_hideToolViews)(BOOL);
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
40
Pods/YBImageBrowser/YBImageBrowser/Protocol/YBIBOrientationReceiveProtocol.h
generated
Normal file
@@ -0,0 +1,40 @@
|
||||
//
|
||||
// YBIBOrientationReceiveProtocol.h
|
||||
// YBImageBrowserDemo
|
||||
//
|
||||
// Created by 波儿菜 on 2019/7/8.
|
||||
// Copyright © 2019 杨波. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@protocol YBIBOrientationReceiveProtocol <NSObject>
|
||||
|
||||
@optional
|
||||
|
||||
/**
|
||||
图片浏览器的方向将要变化
|
||||
|
||||
@param orientation 期望的方向
|
||||
*/
|
||||
- (void)yb_orientationWillChangeWithExpectOrientation:(UIDeviceOrientation)orientation;
|
||||
|
||||
/**
|
||||
图片浏览器的方向变化动效调用,实现的变化会自动转换为动画
|
||||
|
||||
@param orientation 期望的方向
|
||||
*/
|
||||
- (void)yb_orientationChangeAnimationWithExpectOrientation:(UIDeviceOrientation)orientation;
|
||||
|
||||
/**
|
||||
图片浏览器的方向已经变化
|
||||
|
||||
@param orientation 当前的方向
|
||||
*/
|
||||
- (void)yb_orientationDidChangedWithOrientation:(UIDeviceOrientation)orientation;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
38
Pods/YBImageBrowser/YBImageBrowser/Protocol/YBImageBrowserDataSource.h
generated
Normal file
@@ -0,0 +1,38 @@
|
||||
//
|
||||
// YBImageBrowserDataSource.h
|
||||
// YBImageBrowserDemo
|
||||
//
|
||||
// Created by 波儿菜 on 2018/8/25.
|
||||
// Copyright © 2018年 波儿菜. All rights reserved.
|
||||
//
|
||||
|
||||
#import "YBIBDataProtocol.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class YBImageBrowser;
|
||||
|
||||
@protocol YBImageBrowserDataSource <NSObject>
|
||||
|
||||
@required
|
||||
|
||||
/**
|
||||
返回数据源数量
|
||||
|
||||
@param imageBrowser 图片浏览器
|
||||
@return 数量
|
||||
*/
|
||||
- (NSInteger)yb_numberOfCellsInImageBrowser:(YBImageBrowser *)imageBrowser;
|
||||
|
||||
/**
|
||||
返回当前下标对应的数据
|
||||
|
||||
@param imageBrowser 图片浏览器
|
||||
@param index 当前下标
|
||||
@return 数据
|
||||
*/
|
||||
- (id<YBIBDataProtocol>)yb_imageBrowser:(YBImageBrowser *)imageBrowser dataForCellAtIndex:(NSInteger)index;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
54
Pods/YBImageBrowser/YBImageBrowser/Protocol/YBImageBrowserDelegate.h
generated
Normal file
@@ -0,0 +1,54 @@
|
||||
//
|
||||
// YBImageBrowserDelegate.h
|
||||
// YBImageBrowserDemo
|
||||
//
|
||||
// Created by 波儿菜 on 2019/6/9.
|
||||
// Copyright © 2019 波儿菜. All rights reserved.
|
||||
//
|
||||
|
||||
#import "YBIBDataProtocol.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class YBImageBrowser;
|
||||
|
||||
@protocol YBImageBrowserDelegate <NSObject>
|
||||
|
||||
@optional
|
||||
|
||||
/**
|
||||
页码变化
|
||||
|
||||
@param imageBrowser 图片浏览器
|
||||
@param page 当前页码
|
||||
@param data 数据
|
||||
*/
|
||||
- (void)yb_imageBrowser:(YBImageBrowser *)imageBrowser pageChanged:(NSInteger)page data:(id<YBIBDataProtocol>)data;
|
||||
|
||||
/**
|
||||
响应长按手势(若实现该方法将阻止其它地方捕获到长按事件)
|
||||
|
||||
@param imageBrowser 图片浏览器
|
||||
@param data 数据
|
||||
*/
|
||||
- (void)yb_imageBrowser:(YBImageBrowser *)imageBrowser respondsToLongPressWithData:(id<YBIBDataProtocol>)data;
|
||||
|
||||
/**
|
||||
开始转场
|
||||
|
||||
@param imageBrowser 图片浏览器
|
||||
@param isShow YES 表示入场,NO 表示出场
|
||||
*/
|
||||
- (void)yb_imageBrowser:(YBImageBrowser *)imageBrowser beginTransitioningWithIsShow:(BOOL)isShow;
|
||||
|
||||
/**
|
||||
结束转场
|
||||
|
||||
@param imageBrowser 图片浏览器
|
||||
@param isShow YES 表示入场,NO 表示出场
|
||||
*/
|
||||
- (void)yb_imageBrowser:(YBImageBrowser *)imageBrowser endTransitioningWithIsShow:(BOOL)isShow;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
73
Pods/YBImageBrowser/YBImageBrowser/ToolView/YBIBSheetView.h
generated
Normal file
@@ -0,0 +1,73 @@
|
||||
//
|
||||
// YBIBSheetView.h
|
||||
// YBImageBrowserDemo
|
||||
//
|
||||
// Created by 波儿菜 on 2019/7/6.
|
||||
// Copyright © 2019 杨波. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@protocol YBIBDataProtocol;
|
||||
|
||||
typedef void(^YBIBSheetActionBlock)(id<YBIBDataProtocol> data);
|
||||
|
||||
@interface YBIBSheetAction : NSObject
|
||||
|
||||
/// 显示的名字
|
||||
@property (nonatomic, copy) NSString *name;
|
||||
|
||||
/// 点击回调闭包
|
||||
@property (nonatomic, copy, nullable) YBIBSheetActionBlock action;
|
||||
|
||||
+ (instancetype)actionWithName:(NSString *)name action:(_Nullable YBIBSheetActionBlock)action;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@interface YBIBSheetView : UIView
|
||||
|
||||
/// 数据源 (可自定义添加)
|
||||
@property (nonatomic, strong) NSMutableArray<YBIBSheetAction *> *actions;
|
||||
|
||||
/// 列表 Cell 的高度
|
||||
@property (nonatomic, assign) CGFloat cellHeight;
|
||||
|
||||
/// 列表最大高度与容器高度的比例
|
||||
@property (nonatomic, assign) CGFloat maxHeightScale;
|
||||
|
||||
/// 取消的文本
|
||||
@property (nonatomic, copy) NSString *cancelText;
|
||||
|
||||
/// 显示动画持续时间
|
||||
@property (nonatomic, assign) NSTimeInterval showDuration;
|
||||
|
||||
/// 隐藏动画持续时间
|
||||
@property (nonatomic, assign) NSTimeInterval hideDuration;
|
||||
|
||||
/// 背景透明度
|
||||
@property (nonatomic, assign) CGFloat backAlpha;
|
||||
|
||||
/**
|
||||
展示
|
||||
|
||||
@param view 指定父视图
|
||||
@param orientation 当前方向
|
||||
*/
|
||||
- (void)showToView:(UIView *)view orientation:(UIDeviceOrientation)orientation;
|
||||
|
||||
/**
|
||||
隐藏
|
||||
|
||||
@param animation 是否带动画
|
||||
*/
|
||||
- (void)hideWithAnimation:(BOOL)animation;
|
||||
|
||||
/// 获取当前数据的闭包
|
||||
@property (nonatomic, copy) id<YBIBDataProtocol>(^currentdata)(void);
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
209
Pods/YBImageBrowser/YBImageBrowser/ToolView/YBIBSheetView.m
generated
Normal file
@@ -0,0 +1,209 @@
|
||||
//
|
||||
// YBIBSheetView.m
|
||||
// YBImageBrowserDemo
|
||||
//
|
||||
// Created by 波儿菜 on 2019/7/6.
|
||||
// Copyright © 2019 杨波. All rights reserved.
|
||||
//
|
||||
|
||||
#import "YBIBSheetView.h"
|
||||
#import "YBIBUtilities.h"
|
||||
#import "YBIBCopywriter.h"
|
||||
|
||||
|
||||
@implementation YBIBSheetAction
|
||||
+ (instancetype)actionWithName:(NSString *)name action:(YBIBSheetActionBlock)action {
|
||||
YBIBSheetAction *sheetAction = [YBIBSheetAction new];
|
||||
sheetAction.name = name;
|
||||
sheetAction.action = action;
|
||||
return sheetAction;
|
||||
}
|
||||
@end
|
||||
|
||||
|
||||
@interface YBIBSheetCell : UITableViewCell
|
||||
@property (nonatomic, strong) UILabel *titleLabel;
|
||||
@property (nonatomic, strong) CALayer *line;
|
||||
@end
|
||||
@implementation YBIBSheetCell
|
||||
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
|
||||
if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) {
|
||||
self.selectionStyle = UITableViewCellSelectionStyleNone;
|
||||
_titleLabel = [UILabel new];
|
||||
_titleLabel.textColor = UIColor.darkTextColor;
|
||||
_titleLabel.font = [UIFont fontWithName:@"Avenir-Medium" size:16];
|
||||
_titleLabel.textAlignment = NSTextAlignmentCenter;
|
||||
_line = [CALayer new];
|
||||
_line.backgroundColor = UIColor.groupTableViewBackgroundColor.CGColor;
|
||||
[self.contentView addSubview:_titleLabel];
|
||||
[self.contentView.layer addSublayer:_line];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
- (void)layoutSubviews {
|
||||
[super layoutSubviews];
|
||||
CGFloat width = self.contentView.bounds.size.width, height = self.contentView.bounds.size.height;
|
||||
CGFloat lineHeight = 0.5;
|
||||
_line.frame = CGRectMake(0, height - lineHeight, width, lineHeight);
|
||||
CGFloat offset = 15;
|
||||
_titleLabel.frame = CGRectMake(offset, 0, width - offset * 2, height);
|
||||
}
|
||||
@end
|
||||
|
||||
|
||||
static CGFloat kOffsetSpace = 5;
|
||||
|
||||
@interface YBIBSheetView () <UITableViewDelegate, UITableViewDataSource>
|
||||
@property (nonatomic, strong) UITableView *tableView;
|
||||
@end
|
||||
|
||||
@implementation YBIBSheetView {
|
||||
CGRect _tableShowFrame;
|
||||
CGRect _tableHideFrame;
|
||||
}
|
||||
|
||||
#pragma mark - life cycle
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
if (self) {
|
||||
_cancelText = [YBIBCopywriter sharedCopywriter].cancel;
|
||||
_maxHeightScale = 0.7;
|
||||
_showDuration = 0.2;
|
||||
_hideDuration = 0.1;
|
||||
_cellHeight = 50;
|
||||
_backAlpha = 0.3;
|
||||
_actions = [NSMutableArray array];
|
||||
|
||||
[self addSubview:self.tableView];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark - public
|
||||
|
||||
- (void)showToView:(UIView *)view orientation:(UIDeviceOrientation)orientation {
|
||||
if (self.actions.count == 0) return;
|
||||
|
||||
[view addSubview:self];
|
||||
self.frame = view.bounds;
|
||||
|
||||
UIEdgeInsets padding = YBIBPaddingByBrowserOrientation(orientation);
|
||||
|
||||
CGFloat footerHeight = padding.bottom;
|
||||
CGFloat tableHeight = self.cellHeight * (self.actions.count + 1) + kOffsetSpace + footerHeight;
|
||||
|
||||
_tableShowFrame = self.frame;
|
||||
_tableShowFrame.size.height = MIN(self.maxHeightScale * self.bounds.size.height, tableHeight);
|
||||
_tableShowFrame.origin.y = self.bounds.size.height - _tableShowFrame.size.height;
|
||||
|
||||
_tableHideFrame = _tableShowFrame;
|
||||
_tableHideFrame.origin.y = self.bounds.size.height;
|
||||
|
||||
self.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0];
|
||||
self.tableView.frame = _tableHideFrame;
|
||||
self.tableView.tableFooterView.bounds = CGRectMake(0, 0, self.tableView.frame.size.width, footerHeight);
|
||||
[UIView animateWithDuration:self.showDuration animations:^{
|
||||
self.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:self->_backAlpha];
|
||||
self.tableView.frame = self->_tableShowFrame;
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)hideWithAnimation:(BOOL)animation {
|
||||
if (!self.superview) return;
|
||||
|
||||
void(^animationsBlock)(void) = ^{
|
||||
self.tableView.frame = self->_tableHideFrame;
|
||||
self.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0];
|
||||
};
|
||||
void(^completionBlock)(BOOL n) = ^(BOOL n){
|
||||
[self removeFromSuperview];
|
||||
};
|
||||
if (animation) {
|
||||
[UIView animateWithDuration:self.hideDuration animations:animationsBlock completion:completionBlock];
|
||||
} else {
|
||||
animationsBlock();
|
||||
completionBlock(NO);
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - touch
|
||||
|
||||
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
|
||||
CGPoint point = [touches.anyObject locationInView:self];
|
||||
if (!CGRectContainsPoint(self.tableView.frame, point)) {
|
||||
[self hideWithAnimation:YES];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - <UITableViewDataSource>
|
||||
|
||||
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
|
||||
return 2;
|
||||
}
|
||||
|
||||
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
|
||||
return section == 0 ? self.actions.count : 1;
|
||||
}
|
||||
|
||||
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
return self.cellHeight;
|
||||
}
|
||||
|
||||
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {
|
||||
return section == 0 ? CGFLOAT_MIN : kOffsetSpace;
|
||||
}
|
||||
|
||||
- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section {
|
||||
return CGFLOAT_MIN;
|
||||
}
|
||||
|
||||
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
YBIBSheetCell *cell = [tableView dequeueReusableCellWithIdentifier:NSStringFromClass(YBIBSheetCell.self)];
|
||||
if (indexPath.section == 0) {
|
||||
cell.line.hidden = NO;
|
||||
YBIBSheetAction *action = self.actions[indexPath.row];
|
||||
cell.titleLabel.text = action.name;
|
||||
} else {
|
||||
cell.line.hidden = YES;
|
||||
cell.titleLabel.text = self.cancelText;
|
||||
}
|
||||
return cell;
|
||||
}
|
||||
|
||||
#pragma mark - <UITableViewDelegate>
|
||||
|
||||
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
if (indexPath.section == 0) {
|
||||
YBIBSheetAction *action = self.actions[indexPath.row];
|
||||
if (action.action) action.action(self.currentdata());
|
||||
} else {
|
||||
[self hideWithAnimation:YES];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - getters
|
||||
|
||||
- (UITableView *)tableView {
|
||||
if (!_tableView) {
|
||||
_tableView = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStyleGrouped];
|
||||
_tableView.delegate = self;
|
||||
_tableView.dataSource = self;
|
||||
_tableView.estimatedRowHeight = 44;
|
||||
_tableView.estimatedSectionFooterHeight = 0;
|
||||
_tableView.estimatedSectionHeaderHeight = 0;
|
||||
_tableView.backgroundColor = [UIColor clearColor];
|
||||
_tableView.alwaysBounceVertical = NO;
|
||||
_tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
|
||||
if (@available(iOS 11.0, *)) {
|
||||
_tableView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
|
||||
}
|
||||
UIView *footer = [UIView new];
|
||||
footer.backgroundColor = UIColor.whiteColor;
|
||||
_tableView.tableFooterView = footer;
|
||||
[_tableView registerClass:YBIBSheetCell.self forCellReuseIdentifier:NSStringFromClass(YBIBSheetCell.self)];
|
||||
}
|
||||
return _tableView;
|
||||
}
|
||||
|
||||
@end
|
||||
67
Pods/YBImageBrowser/YBImageBrowser/ToolView/YBIBToolViewHandler.h
generated
Normal file
@@ -0,0 +1,67 @@
|
||||
//
|
||||
// YBIBToolViewHandler.h
|
||||
// YBImageBrowserDemo
|
||||
//
|
||||
// Created by 波儿菜 on 2019/7/7.
|
||||
// Copyright © 2019 杨波. All rights reserved.
|
||||
//
|
||||
|
||||
#import "YBIBSheetView.h"
|
||||
#import "YBIBTopView.h"
|
||||
#import "YBIBDataProtocol.h"
|
||||
#import "YBIBOrientationReceiveProtocol.h"
|
||||
#import "YBIBOperateBrowserProtocol.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@protocol YBIBToolViewHandler <YBIBGetBaseInfoProtocol, YBIBOperateBrowserProtocol, YBIBOrientationReceiveProtocol>
|
||||
|
||||
@required
|
||||
|
||||
/**
|
||||
容器视图准备好了,可进行子视图的添加和布局
|
||||
*/
|
||||
- (void)yb_containerViewIsReadied;
|
||||
|
||||
/**
|
||||
隐藏视图
|
||||
|
||||
@param hide 是否隐藏
|
||||
*/
|
||||
- (void)yb_hide:(BOOL)hide;
|
||||
|
||||
@optional
|
||||
|
||||
/// 当前数据
|
||||
@property (nonatomic, copy) id<YBIBDataProtocol>(^yb_currentData)(void);
|
||||
|
||||
/**
|
||||
页码变化了
|
||||
*/
|
||||
- (void)yb_pageChanged;
|
||||
|
||||
/**
|
||||
偏移量变化了
|
||||
|
||||
@param offsetX 当前偏移量
|
||||
*/
|
||||
- (void)yb_offsetXChanged:(CGFloat)offsetX;
|
||||
|
||||
/**
|
||||
响应长按手势
|
||||
*/
|
||||
- (void)yb_respondsToLongPress;
|
||||
|
||||
@end
|
||||
|
||||
@interface YBIBToolViewHandler : NSObject <YBIBToolViewHandler>
|
||||
|
||||
/// 弹出表单视图
|
||||
@property (nonatomic, strong, readonly) YBIBSheetView *sheetView;
|
||||
|
||||
/// 顶部显示页码视图
|
||||
@property (nonatomic, strong, readonly) YBIBTopView *topView;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
144
Pods/YBImageBrowser/YBImageBrowser/ToolView/YBIBToolViewHandler.m
generated
Normal file
@@ -0,0 +1,144 @@
|
||||
//
|
||||
// YBIBToolViewHandler.m
|
||||
// YBImageBrowserDemo
|
||||
//
|
||||
// Created by 波儿菜 on 2019/7/7.
|
||||
// Copyright © 2019 杨波. All rights reserved.
|
||||
//
|
||||
|
||||
#import "YBIBToolViewHandler.h"
|
||||
#import "YBIBCopywriter.h"
|
||||
#import "YBIBUtilities.h"
|
||||
|
||||
@interface YBIBToolViewHandler ()
|
||||
@property (nonatomic, strong) YBIBSheetView *sheetView;
|
||||
@property (nonatomic, strong) YBIBSheetAction *saveAction;
|
||||
@property (nonatomic, strong) YBIBTopView *topView;
|
||||
@end
|
||||
|
||||
@implementation YBIBToolViewHandler
|
||||
|
||||
#pragma mark - <YBIBToolViewHandler>
|
||||
|
||||
@synthesize yb_containerView = _yb_containerView;
|
||||
@synthesize yb_containerSize = _yb_containerSize;
|
||||
@synthesize yb_currentPage = _yb_currentPage;
|
||||
@synthesize yb_totalPage = _yb_totalPage;
|
||||
@synthesize yb_currentOrientation = _yb_currentOrientation;
|
||||
@synthesize yb_currentData = _yb_currentData;
|
||||
|
||||
- (void)yb_containerViewIsReadied {
|
||||
[self.yb_containerView addSubview:self.topView];
|
||||
[self layoutWithExpectOrientation:self.yb_currentOrientation()];
|
||||
}
|
||||
|
||||
- (void)yb_pageChanged {
|
||||
if (self.topView.operationType == YBIBTopViewOperationTypeSave) {
|
||||
self.topView.operationButton.hidden = [self currentDataShouldHideSaveButton];
|
||||
}
|
||||
[self.topView setPage:self.yb_currentPage() totalPage:self.yb_totalPage()];
|
||||
}
|
||||
|
||||
- (void)yb_respondsToLongPress {
|
||||
[self showSheetView];
|
||||
}
|
||||
|
||||
- (void)yb_hide:(BOOL)hide {
|
||||
self.topView.hidden = hide;
|
||||
[self.sheetView hideWithAnimation:NO];
|
||||
}
|
||||
|
||||
- (void)yb_orientationWillChangeWithExpectOrientation:(UIDeviceOrientation)orientation {
|
||||
[self.sheetView hideWithAnimation:NO];
|
||||
}
|
||||
|
||||
- (void)yb_orientationChangeAnimationWithExpectOrientation:(UIDeviceOrientation)orientation {
|
||||
[self layoutWithExpectOrientation:orientation];
|
||||
}
|
||||
|
||||
#pragma mark - private
|
||||
|
||||
- (BOOL)currentDataShouldHideSaveButton {
|
||||
id<YBIBDataProtocol> data = self.yb_currentData();
|
||||
BOOL allow = [data respondsToSelector:@selector(yb_allowSaveToPhotoAlbum)] && [data yb_allowSaveToPhotoAlbum];
|
||||
BOOL can = [data respondsToSelector:@selector(yb_saveToPhotoAlbum)];
|
||||
return !(allow && can);
|
||||
}
|
||||
|
||||
- (void)layoutWithExpectOrientation:(UIDeviceOrientation)orientation {
|
||||
CGSize containerSize = self.yb_containerSize(orientation);
|
||||
UIEdgeInsets padding = YBIBPaddingByBrowserOrientation(orientation);
|
||||
|
||||
self.topView.frame = CGRectMake(padding.left, padding.top, containerSize.width - padding.left - padding.right, [YBIBTopView defaultHeight]);
|
||||
}
|
||||
|
||||
- (void)showSheetView {
|
||||
if ([self currentDataShouldHideSaveButton]) {
|
||||
[self.sheetView.actions removeObject:self.saveAction];
|
||||
} else {
|
||||
if (![self.sheetView.actions containsObject:self.saveAction]) {
|
||||
[self.sheetView.actions addObject:self.saveAction];
|
||||
}
|
||||
}
|
||||
[self.sheetView showToView:self.yb_containerView orientation:self.yb_currentOrientation()];
|
||||
}
|
||||
|
||||
#pragma mark - getters
|
||||
|
||||
- (YBIBSheetView *)sheetView {
|
||||
if (!_sheetView) {
|
||||
_sheetView = [YBIBSheetView new];
|
||||
__weak typeof(self) wSelf = self;
|
||||
[_sheetView setCurrentdata:^id<YBIBDataProtocol>{
|
||||
__strong typeof(wSelf) self = wSelf;
|
||||
if (!self) return nil;
|
||||
return self.yb_currentData();
|
||||
}];
|
||||
}
|
||||
return _sheetView;
|
||||
}
|
||||
|
||||
- (YBIBSheetAction *)saveAction {
|
||||
if (!_saveAction) {
|
||||
__weak typeof(self) wSelf = self;
|
||||
_saveAction = [YBIBSheetAction actionWithName:[YBIBCopywriter sharedCopywriter].saveToPhotoAlbum action:^(id<YBIBDataProtocol> data) {
|
||||
__strong typeof(wSelf) self = wSelf;
|
||||
if (!self) return;
|
||||
if ([data respondsToSelector:@selector(yb_saveToPhotoAlbum)]) {
|
||||
[data yb_saveToPhotoAlbum];
|
||||
}
|
||||
[self.sheetView hideWithAnimation:YES];
|
||||
}];
|
||||
}
|
||||
return _saveAction;
|
||||
}
|
||||
|
||||
- (YBIBTopView *)topView {
|
||||
if (!_topView) {
|
||||
_topView = [YBIBTopView new];
|
||||
_topView.operationType = YBIBTopViewOperationTypeMore;
|
||||
__weak typeof(self) wSelf = self;
|
||||
[_topView setClickOperation:^(YBIBTopViewOperationType type) {
|
||||
__strong typeof(wSelf) self = wSelf;
|
||||
if (!self) return;
|
||||
switch (type) {
|
||||
case YBIBTopViewOperationTypeSave: {
|
||||
id<YBIBDataProtocol> data = self.yb_currentData();
|
||||
if ([data respondsToSelector:@selector(yb_saveToPhotoAlbum)]) {
|
||||
[data yb_saveToPhotoAlbum];
|
||||
}
|
||||
}
|
||||
break;
|
||||
case YBIBTopViewOperationTypeMore: {
|
||||
[self showSheetView];
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}];
|
||||
}
|
||||
return _topView;
|
||||
}
|
||||
|
||||
@end
|
||||
44
Pods/YBImageBrowser/YBImageBrowser/ToolView/YBIBTopView.h
generated
Normal file
@@ -0,0 +1,44 @@
|
||||
//
|
||||
// YBIBTopView.h
|
||||
// YBImageBrowserDemo
|
||||
//
|
||||
// Created by 波儿菜 on 2019/7/6.
|
||||
// Copyright © 2019 杨波. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
typedef NS_ENUM(NSInteger, YBIBTopViewOperationType) {
|
||||
YBIBTopViewOperationTypeSave, //保存
|
||||
YBIBTopViewOperationTypeMore //更多
|
||||
};
|
||||
|
||||
@interface YBIBTopView : UIView
|
||||
|
||||
/// 页码标签
|
||||
@property (nonatomic, strong, readonly) UILabel *pageLabel;
|
||||
|
||||
/// 操作按钮(自定义:直接修改图片或文字,然后添加点击事件)
|
||||
@property (nonatomic, strong, readonly) UIButton *operationButton;
|
||||
|
||||
/// 按钮类型
|
||||
@property (nonatomic, assign) YBIBTopViewOperationType operationType;
|
||||
|
||||
/**
|
||||
设置页码
|
||||
|
||||
@param page 当前页码
|
||||
@param totalPage 总页码数
|
||||
*/
|
||||
- (void)setPage:(NSInteger)page totalPage:(NSInteger)totalPage;
|
||||
|
||||
/// 点击操作按钮的回调
|
||||
@property (nonatomic, copy) void(^clickOperation)(YBIBTopViewOperationType type);
|
||||
|
||||
+ (CGFloat)defaultHeight;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
113
Pods/YBImageBrowser/YBImageBrowser/ToolView/YBIBTopView.m
generated
Normal file
@@ -0,0 +1,113 @@
|
||||
//
|
||||
// YBIBTopView.m
|
||||
// YBImageBrowserDemo
|
||||
//
|
||||
// Created by 波儿菜 on 2019/7/6.
|
||||
// Copyright © 2019 杨波. All rights reserved.
|
||||
//
|
||||
|
||||
#import "YBIBTopView.h"
|
||||
#import "YBIBIconManager.h"
|
||||
#import "YBIBUtilities.h"
|
||||
|
||||
@interface YBIBTopView ()
|
||||
@property (nonatomic, strong) UILabel *pageLabel;
|
||||
@property (nonatomic, strong) UIButton *operationButton;
|
||||
@end
|
||||
|
||||
@implementation YBIBTopView
|
||||
|
||||
#pragma mark - life cycle
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
if (self) {
|
||||
[self addSubview:self.pageLabel];
|
||||
[self addSubview:self.operationButton];
|
||||
|
||||
[self setOperationType:YBIBTopViewOperationTypeMore];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)layoutSubviews {
|
||||
[super layoutSubviews];
|
||||
CGFloat height = self.bounds.size.height, width = self.bounds.size.width;
|
||||
self.pageLabel.frame = CGRectMake(16, 0, width / 3, height);
|
||||
CGFloat buttonWidth = 54;
|
||||
self.operationButton.frame = CGRectMake(width - buttonWidth, 0, buttonWidth, height);
|
||||
}
|
||||
|
||||
#pragma mark - public
|
||||
|
||||
+ (CGFloat)defaultHeight {
|
||||
return 50;
|
||||
}
|
||||
|
||||
- (void)setPage:(NSInteger)page totalPage:(NSInteger)totalPage {
|
||||
if (totalPage <= 1) {
|
||||
self.pageLabel.hidden = YES;
|
||||
} else {
|
||||
self.pageLabel.hidden = NO;
|
||||
|
||||
NSString *text = [NSString stringWithFormat:@"%ld/%ld", page + (NSInteger)1, totalPage];
|
||||
NSShadow *shadow = [NSShadow new];
|
||||
shadow.shadowBlurRadius = 4;
|
||||
shadow.shadowOffset = CGSizeMake(0, 1);
|
||||
shadow.shadowColor = UIColor.darkGrayColor;
|
||||
NSMutableAttributedString *attr = [[NSMutableAttributedString alloc] initWithString:text attributes:@{NSShadowAttributeName:shadow}];
|
||||
self.pageLabel.attributedText = attr;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - event
|
||||
|
||||
- (void)clickOperationButton:(UIButton *)button {
|
||||
if (self.clickOperation) self.clickOperation(self.operationType);
|
||||
}
|
||||
|
||||
#pragma mark - getters & setters
|
||||
|
||||
- (void)setOperationType:(YBIBTopViewOperationType)operationType {
|
||||
_operationType = operationType;
|
||||
|
||||
UIImage *image = nil;
|
||||
switch (operationType) {
|
||||
case YBIBTopViewOperationTypeSave:
|
||||
image = [YBIBIconManager sharedManager].toolSaveImage();
|
||||
break;
|
||||
case YBIBTopViewOperationTypeMore:
|
||||
image = [YBIBIconManager sharedManager].toolMoreImage();
|
||||
break;
|
||||
}
|
||||
|
||||
[self.operationButton setImage:image forState:UIControlStateNormal];
|
||||
}
|
||||
|
||||
- (UILabel *)pageLabel {
|
||||
if (!_pageLabel) {
|
||||
_pageLabel = [UILabel new];
|
||||
_pageLabel.textColor = [UIColor whiteColor];
|
||||
_pageLabel.font = [UIFont boldSystemFontOfSize:16];
|
||||
_pageLabel.textAlignment = NSTextAlignmentLeft;
|
||||
_pageLabel.adjustsFontSizeToFitWidth = YES;
|
||||
}
|
||||
return _pageLabel;
|
||||
}
|
||||
|
||||
- (UIButton *)operationButton {
|
||||
if (!_operationButton) {
|
||||
_operationButton = [UIButton buttonWithType:UIButtonTypeCustom];
|
||||
_operationButton.titleLabel.font = [UIFont boldSystemFontOfSize:16];
|
||||
_operationButton.titleLabel.adjustsFontSizeToFitWidth = YES;
|
||||
[_operationButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
|
||||
[_operationButton addTarget:self action:@selector(clickOperationButton:) forControlEvents:UIControlEventTouchUpInside];
|
||||
_operationButton.layer.shadowColor = UIColor.darkGrayColor.CGColor;
|
||||
_operationButton.layer.shadowOffset = CGSizeMake(0, 1);
|
||||
_operationButton.layer.shadowOpacity = 1;
|
||||
_operationButton.layer.shadowRadius = 4;
|
||||
}
|
||||
return _operationButton;
|
||||
}
|
||||
|
||||
@end
|
||||
17
Pods/YBImageBrowser/YBImageBrowser/WebImageMediator/YBIBDefaultWebImageMediator.h
generated
Normal file
@@ -0,0 +1,17 @@
|
||||
//
|
||||
// YBIBDefaultWebImageMediator.h
|
||||
// YBImageBrowserDemo
|
||||
//
|
||||
// Created by 波儿菜 on 2019/8/27.
|
||||
// Copyright © 2019 杨波. All rights reserved.
|
||||
//
|
||||
|
||||
#import "YBIBWebImageMediator.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface YBIBDefaultWebImageMediator : NSObject <YBIBWebImageMediator>
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
74
Pods/YBImageBrowser/YBImageBrowser/WebImageMediator/YBIBDefaultWebImageMediator.m
generated
Normal file
@@ -0,0 +1,74 @@
|
||||
//
|
||||
// YBIBDefaultWebImageMediator.m
|
||||
// YBImageBrowserDemo
|
||||
//
|
||||
// Created by 波儿菜 on 2019/8/27.
|
||||
// Copyright © 2019 杨波. All rights reserved.
|
||||
//
|
||||
|
||||
#import "YBIBDefaultWebImageMediator.h"
|
||||
#import "YBIBUtilities.h"
|
||||
#if __has_include(<SDWebImage/SDWebImage.h>)
|
||||
#import <SDWebImage/SDWebImage.h>
|
||||
#else
|
||||
#import "SDWebImage.h"
|
||||
#endif
|
||||
|
||||
@implementation YBIBDefaultWebImageMediator
|
||||
|
||||
#pragma mark - <YBIBWebImageMediator>
|
||||
|
||||
- (id)yb_downloadImageWithURL:(NSURL *)URL requestModifier:(nullable YBIBWebImageRequestModifierBlock)requestModifier progress:(nonnull YBIBWebImageProgressBlock)progress success:(nonnull YBIBWebImageSuccessBlock)success failed:(nonnull YBIBWebImageFailedBlock)failed {
|
||||
if (!URL) return nil;
|
||||
|
||||
SDWebImageContext *context = nil;
|
||||
if (requestModifier) {
|
||||
SDWebImageDownloaderRequestModifier *modifier = [SDWebImageDownloaderRequestModifier requestModifierWithBlock:requestModifier];
|
||||
context = @{SDWebImageContextDownloadRequestModifier:modifier};
|
||||
}
|
||||
|
||||
SDWebImageDownloaderOptions options = SDWebImageDownloaderLowPriority | SDWebImageDownloaderAvoidDecodeImage;
|
||||
|
||||
SDWebImageDownloadToken *token = [[SDWebImageDownloader sharedDownloader] downloadImageWithURL:URL options:options context:context progress:^(NSInteger receivedSize, NSInteger expectedSize, NSURL * _Nullable targetURL) {
|
||||
if (progress) progress(receivedSize, expectedSize);
|
||||
} completed:^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, BOOL finished) {
|
||||
if (error) {
|
||||
if (failed) failed(error, finished);
|
||||
} else {
|
||||
if (success) success(data, finished);
|
||||
}
|
||||
}];
|
||||
return token;
|
||||
}
|
||||
|
||||
- (void)yb_cancelTaskWithDownloadToken:(id)token {
|
||||
if (token && [token isKindOfClass:SDWebImageDownloadToken.class]) {
|
||||
[((SDWebImageDownloadToken *)token) cancel];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)yb_storeToDiskWithImageData:(NSData *)data forKey:(NSURL *)key {
|
||||
if (!key) return;
|
||||
NSString *cacheKey = [SDWebImageManager.sharedManager cacheKeyForURL:key];
|
||||
if (!cacheKey) return;
|
||||
|
||||
YBIB_DISPATCH_ASYNC(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
|
||||
[[SDImageCache sharedImageCache] storeImageDataToDisk:data forKey:cacheKey];
|
||||
})
|
||||
}
|
||||
|
||||
- (void)yb_queryCacheOperationForKey:(NSURL *)key completed:(YBIBWebImageCacheQueryCompletedBlock)completed {
|
||||
#define QUERY_CACHE_FAILED if (completed) {completed(nil, nil); return;}
|
||||
if (!key) QUERY_CACHE_FAILED
|
||||
NSString *cacheKey = [SDWebImageManager.sharedManager cacheKeyForURL:key];
|
||||
if (!cacheKey) QUERY_CACHE_FAILED
|
||||
#undef QUERY_CACHE_FAILED
|
||||
|
||||
// 'NSData' of image must be read to ensure decoding correctly.
|
||||
SDImageCacheOptions options = SDImageCacheQueryMemoryData | SDImageCacheAvoidDecodeImage;
|
||||
[[SDImageCache sharedImageCache] queryCacheOperationForKey:cacheKey options:options done:^(UIImage * _Nullable image, NSData * _Nullable data, SDImageCacheType cacheType) {
|
||||
if (completed) completed(image, data);
|
||||
}];
|
||||
}
|
||||
|
||||
@end
|
||||
62
Pods/YBImageBrowser/YBImageBrowser/WebImageMediator/YBIBWebImageMediator.h
generated
Normal file
@@ -0,0 +1,62 @@
|
||||
//
|
||||
// YBIBWebImageMediator.h
|
||||
// YBImageBrowserDemo
|
||||
//
|
||||
// Created by 波儿菜 on 2019/8/27.
|
||||
// Copyright © 2019 杨波. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
typedef NSURLRequest * _Nullable (^YBIBWebImageRequestModifierBlock)(NSURLRequest *request);
|
||||
typedef void(^YBIBWebImageProgressBlock)(NSInteger receivedSize, NSInteger expectedSize);
|
||||
typedef void(^YBIBWebImageSuccessBlock)(NSData * _Nullable imageData, BOOL finished);
|
||||
typedef void(^YBIBWebImageFailedBlock)(NSError * _Nullable error, BOOL finished);
|
||||
typedef void(^YBIBWebImageCacheQueryCompletedBlock)(UIImage * _Nullable image, NSData * _Nullable imageData);
|
||||
|
||||
@protocol YBIBWebImageMediator <NSObject>
|
||||
|
||||
@required
|
||||
|
||||
/**
|
||||
下载图片
|
||||
|
||||
@param URL 图片地址
|
||||
@param requestModifier 修改默认 NSURLRequest 的闭包
|
||||
@param progress 进度回调
|
||||
@param success 成功回调
|
||||
@param failed 失败回调
|
||||
@return 下载 token (可为空)
|
||||
*/
|
||||
- (id)yb_downloadImageWithURL:(NSURL *)URL requestModifier:(nullable YBIBWebImageRequestModifierBlock)requestModifier progress:(YBIBWebImageProgressBlock)progress success:(YBIBWebImageSuccessBlock)success failed:(YBIBWebImageFailedBlock)failed;
|
||||
|
||||
/**
|
||||
缓存图片数据到磁盘
|
||||
|
||||
@param data 图片数据
|
||||
@param key 缓存标识
|
||||
*/
|
||||
- (void)yb_storeToDiskWithImageData:(nullable NSData *)data forKey:(NSURL *)key;
|
||||
|
||||
/**
|
||||
读取图片数据
|
||||
|
||||
@param key 缓存标识
|
||||
@param completed 读取回调
|
||||
*/
|
||||
- (void)yb_queryCacheOperationForKey:(NSURL *)key completed:(YBIBWebImageCacheQueryCompletedBlock)completed;
|
||||
|
||||
@optional
|
||||
|
||||
/**
|
||||
取消下载
|
||||
|
||||
@param token 下载 token
|
||||
*/
|
||||
- (void)yb_cancelTaskWithDownloadToken:(id)token;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
BIN
Pods/YBImageBrowser/YBImageBrowser/YBImageBrowser.bundle/ybib_loading@2x.png
generated
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
Pods/YBImageBrowser/YBImageBrowser/YBImageBrowser.bundle/ybib_loading@3x.png
generated
Normal file
|
After Width: | Height: | Size: 4.0 KiB |
BIN
Pods/YBImageBrowser/YBImageBrowser/YBImageBrowser.bundle/ybib_more@2x.png
generated
Normal file
|
After Width: | Height: | Size: 844 B |
BIN
Pods/YBImageBrowser/YBImageBrowser/YBImageBrowser.bundle/ybib_more@3x.png
generated
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
Pods/YBImageBrowser/YBImageBrowser/YBImageBrowser.bundle/ybib_save@2x.png
generated
Normal file
|
After Width: | Height: | Size: 862 B |
BIN
Pods/YBImageBrowser/YBImageBrowser/YBImageBrowser.bundle/ybib_save@3x.png
generated
Normal file
|
After Width: | Height: | Size: 960 B |
111
Pods/YBImageBrowser/YBImageBrowser/YBImageBrowser.h
generated
Normal file
@@ -0,0 +1,111 @@
|
||||
//
|
||||
// YBImageBrowser.h
|
||||
// YBImageBrowserDemo
|
||||
//
|
||||
// Created by 波儿菜 on 2019/6/5.
|
||||
// Copyright © 2019 波儿菜. All rights reserved.
|
||||
//
|
||||
|
||||
#import "YBIBCollectionView.h"
|
||||
#import "YBImageBrowserDataSource.h"
|
||||
#import "YBImageBrowserDelegate.h"
|
||||
#import "YBIBDataProtocol.h"
|
||||
#import "YBIBCellProtocol.h"
|
||||
#import "YBIBAnimatedTransition.h"
|
||||
#import "YBIBAuxiliaryViewHandler.h"
|
||||
#import "YBIBToolViewHandler.h"
|
||||
#import "YBIBWebImageMediator.h"
|
||||
#import "YBIBImageData.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface YBImageBrowser : UIView
|
||||
|
||||
/// 数据源数组
|
||||
@property (nonatomic, copy) NSArray<id<YBIBDataProtocol>> *dataSourceArray;
|
||||
|
||||
/// 数据源代理
|
||||
@property (nonatomic, weak) id<YBImageBrowserDataSource> dataSource;
|
||||
|
||||
/// 状态回调代理
|
||||
@property (nonatomic, weak) id<YBImageBrowserDelegate> delegate;
|
||||
|
||||
/**
|
||||
展示图片浏览器
|
||||
|
||||
@param view 指定父视图(view 的大小不能为 CGSizeZero,但允许变化)
|
||||
@param containerSize 容器大小(当 view 的大小允许变化时,必须指定确切的 containerSize)
|
||||
*/
|
||||
- (void)showToView:(UIView *)view containerSize:(CGSize)containerSize;
|
||||
- (void)showToView:(UIView *)view;
|
||||
- (void)show;
|
||||
|
||||
/**
|
||||
隐藏图片浏览器(不建议外部持有图片浏览器重复使用)
|
||||
*/
|
||||
- (void)hide;
|
||||
|
||||
/// 当前页码
|
||||
@property (nonatomic, assign) NSInteger currentPage;
|
||||
|
||||
/// 分页间距
|
||||
@property (nonatomic, assign) CGFloat distanceBetweenPages;
|
||||
|
||||
/// 当前图片浏览器的方向
|
||||
@property (nonatomic, assign, readonly) UIDeviceOrientation currentOrientation;
|
||||
|
||||
/// 图片浏览器支持的方向 (仅当前控制器不支持旋转时有效,否则将跟随控制器旋转)
|
||||
@property (nonatomic, assign) UIInterfaceOrientationMask supportedOrientations;
|
||||
|
||||
/// 是否自动隐藏 id<YBIBImageData> 设置的映射视图,默认为 YES
|
||||
@property (nonatomic, assign) BOOL autoHideProjectiveView;
|
||||
|
||||
/// 是否正在转场
|
||||
@property (nonatomic, assign, readonly, getter=isTransitioning) BOOL transitioning;
|
||||
/// 是否正在进行展示过程转场
|
||||
@property (nonatomic, assign, readonly, getter=isShowTransitioning) BOOL showTransitioning;
|
||||
/// 是否正在进行隐藏过程转场
|
||||
@property (nonatomic, assign, readonly, getter=isHideTransitioning) BOOL hideTransitioning;
|
||||
|
||||
/// 预加载数量 (默认为 2,低内存设备默认为 0)
|
||||
@property (nonatomic, assign) NSUInteger preloadCount;
|
||||
|
||||
/**
|
||||
重载数据,请保证数据源被正确修改
|
||||
*/
|
||||
- (void)reloadData;
|
||||
|
||||
/**
|
||||
获取当前展示的数据对象
|
||||
|
||||
@return 数据对象
|
||||
*/
|
||||
- (id<YBIBDataProtocol>)currentData;
|
||||
|
||||
/// 是否隐藏状态栏,默认为 YES(该值为 YES 时需要在 info.plist 中添加 View controller-based status bar appearance : NO 才能生效)
|
||||
@property (nonatomic, assign) BOOL shouldHideStatusBar;
|
||||
|
||||
/// 工具视图处理器
|
||||
/// 赋值可自定义,实现者可以直接用 UIView,或者创建一个中介者管理一系列的 UIView。
|
||||
/// 内部消息是按照数组下标顺序调度的,所以如果有多个处理器注意添加 UIView 的视图层级。
|
||||
@property (nonatomic, copy) NSArray<id<YBIBToolViewHandler>> *toolViewHandlers;
|
||||
/// 默认工具视图处理器
|
||||
@property (nonatomic, weak, readonly) YBIBToolViewHandler *defaultToolViewHandler;
|
||||
|
||||
/// Toast/Loading 处理器 (赋值可自定义)
|
||||
@property (nonatomic, strong) id<YBIBAuxiliaryViewHandler> auxiliaryViewHandler;
|
||||
|
||||
/// 转场实现类 (赋值可自定义)
|
||||
@property (nonatomic, strong) id<YBIBAnimatedTransition> animatedTransition;
|
||||
/// 默认转场实现类 (可配置其属性)
|
||||
@property (nonatomic, weak, readonly) YBIBAnimatedTransition *defaultAnimatedTransition;
|
||||
|
||||
/// 图片下载缓存相关的中介者(赋值可自定义)
|
||||
@property (nonatomic, strong) id<YBIBWebImageMediator> webImageMediator;
|
||||
|
||||
/// 核心集合视图
|
||||
@property (nonatomic, strong, readonly) YBIBCollectionView *collectionView;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
538
Pods/YBImageBrowser/YBImageBrowser/YBImageBrowser.m
generated
Normal file
@@ -0,0 +1,538 @@
|
||||
//
|
||||
// YBImageBrowser.m
|
||||
// YBImageBrowserDemo
|
||||
//
|
||||
// Created by 波儿菜 on 2019/6/5.
|
||||
// Copyright © 2019 波儿菜. All rights reserved.
|
||||
//
|
||||
|
||||
#import "YBImageBrowser.h"
|
||||
#import "YBIBUtilities.h"
|
||||
#import "YBIBCellProtocol.h"
|
||||
#import "YBIBDataMediator.h"
|
||||
#import "YBIBScreenRotationHandler.h"
|
||||
#import "NSObject+YBImageBrowser.h"
|
||||
#import "YBImageBrowser+Internal.h"
|
||||
#if __has_include("YBIBDefaultWebImageMediator.h")
|
||||
#import "YBIBDefaultWebImageMediator.h"
|
||||
#endif
|
||||
|
||||
@interface YBImageBrowser () <UICollectionViewDelegate, UICollectionViewDataSource>
|
||||
@property (nonatomic, strong) YBIBCollectionView *collectionView;
|
||||
@property (nonatomic, strong) YBIBDataMediator *dataMediator;
|
||||
@property (nonatomic, strong) YBIBScreenRotationHandler *rotationHandler;
|
||||
@end
|
||||
|
||||
@implementation YBImageBrowser {
|
||||
BOOL _originStatusBarHidden;
|
||||
}
|
||||
|
||||
#pragma mark - life cycle
|
||||
|
||||
- (void)dealloc {
|
||||
self.hiddenProjectiveView = nil;
|
||||
[self showStatusBar];
|
||||
}
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
if (self) {
|
||||
self.backgroundColor = UIColor.blackColor;
|
||||
UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(respondsToLongPress:)];
|
||||
[self addGestureRecognizer:longPress];
|
||||
[self initValue];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)initValue {
|
||||
_transitioning = _showTransitioning = _hideTransitioning = NO;
|
||||
_defaultAnimatedTransition = _animatedTransition = [YBIBAnimatedTransition new];
|
||||
_toolViewHandlers = @[[YBIBToolViewHandler new]];
|
||||
_defaultToolViewHandler = _toolViewHandlers[0];
|
||||
_auxiliaryViewHandler = [YBIBAuxiliaryViewHandler new];
|
||||
_shouldHideStatusBar = YES;
|
||||
_autoHideProjectiveView = YES;
|
||||
#if __has_include("YBIBDefaultWebImageMediator.h")
|
||||
_webImageMediator = [YBIBDefaultWebImageMediator new];
|
||||
#endif
|
||||
}
|
||||
|
||||
#pragma mark - private
|
||||
|
||||
- (void)build {
|
||||
[self addSubview:self.collectionView];
|
||||
self.collectionView.frame = self.bounds;
|
||||
self.collectionView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
|
||||
|
||||
[self addSubview:self.containerView];
|
||||
self.containerView.frame = self.bounds;
|
||||
self.containerView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
|
||||
|
||||
[self buildToolView];
|
||||
|
||||
[self layoutIfNeeded];
|
||||
|
||||
[self collectionViewScrollToPage:self.currentPage];
|
||||
[self.rotationHandler startObserveDeviceOrientation];
|
||||
}
|
||||
|
||||
- (void)buildToolView {
|
||||
for (id<YBIBToolViewHandler> handler in self.toolViewHandlers) {
|
||||
[self implementGetBaseInfoProtocol:handler];
|
||||
[self implementOperateBrowserProtocol:handler];
|
||||
__weak typeof(self) wSelf = self;
|
||||
if ([handler respondsToSelector:@selector(setYb_currentData:)]) {
|
||||
[handler setYb_currentData:^id<YBIBDataProtocol>{
|
||||
__strong typeof(wSelf) self = wSelf;
|
||||
if (!self) return nil;
|
||||
return self.currentData;
|
||||
}];
|
||||
}
|
||||
[handler yb_containerViewIsReadied];
|
||||
[handler yb_hide:NO];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)rebuild {
|
||||
self.hiddenProjectiveView = nil;
|
||||
[self showStatusBar];
|
||||
[self.containerView removeFromSuperview];
|
||||
_containerView = nil;
|
||||
[self.collectionView removeFromSuperview];
|
||||
_collectionView = nil;
|
||||
[self.dataMediator clear];
|
||||
[self.rotationHandler clear];
|
||||
}
|
||||
|
||||
- (void)collectionViewScrollToPage:(NSInteger)page {
|
||||
[self.collectionView scrollToPage:page];
|
||||
[self pageNumberChanged];
|
||||
}
|
||||
|
||||
- (void)pageNumberChanged {
|
||||
id<YBIBDataProtocol> data = self.currentData;
|
||||
UIView *projectiveView = nil;
|
||||
if ([data respondsToSelector:@selector(yb_projectiveView)]) {
|
||||
projectiveView = [data yb_projectiveView];
|
||||
}
|
||||
self.hiddenProjectiveView = projectiveView;
|
||||
|
||||
if (self.delegate && [self.delegate respondsToSelector:@selector(yb_imageBrowser:pageChanged:data:)]) {
|
||||
[self.delegate yb_imageBrowser:self pageChanged:self.currentPage data:data];
|
||||
}
|
||||
for (id<YBIBToolViewHandler> handler in self.toolViewHandlers) {
|
||||
if ([handler respondsToSelector:@selector(yb_pageChanged)]) {
|
||||
[handler yb_pageChanged];
|
||||
}
|
||||
}
|
||||
NSArray *visibleCells = self.collectionView.visibleCells;
|
||||
for (UICollectionViewCell<YBIBCellProtocol> *cell in visibleCells) {
|
||||
if ([cell respondsToSelector:@selector(yb_pageChanged)]) {
|
||||
[cell yb_pageChanged];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)showStatusBar {
|
||||
if (self.shouldHideStatusBar) {
|
||||
[UIApplication sharedApplication].statusBarHidden = _originStatusBarHidden;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)hideStatusBar {
|
||||
if (self.shouldHideStatusBar) {
|
||||
[UIApplication sharedApplication].statusBarHidden = YES;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - public
|
||||
|
||||
- (void)show {
|
||||
[self showToView:[UIApplication sharedApplication].keyWindow];
|
||||
}
|
||||
|
||||
- (void)showToView:(UIView *)view {
|
||||
[self showToView:view containerSize:view.bounds.size];
|
||||
}
|
||||
|
||||
- (void)showToView:(UIView *)view containerSize:(CGSize)containerSize {
|
||||
[self.rotationHandler startObserveStatusBarOrientation];
|
||||
|
||||
[view addSubview:self];
|
||||
self.frame = view.bounds;
|
||||
self.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
|
||||
|
||||
_originStatusBarHidden = [UIApplication sharedApplication].isStatusBarHidden;
|
||||
|
||||
[self.rotationHandler configContainerSize:containerSize];
|
||||
|
||||
[self.dataMediator preloadWithPage:self.currentPage];
|
||||
|
||||
__kindof UIView *startView;
|
||||
UIImage *startImage;
|
||||
CGRect endFrame = CGRectZero;
|
||||
id<YBIBDataProtocol> data = [self.dataMediator dataForCellAtIndex:self.currentPage];
|
||||
if ([data respondsToSelector:@selector(yb_projectiveView)]) {
|
||||
startView = data.yb_projectiveView;
|
||||
self.hiddenProjectiveView = startView;
|
||||
if ([startView isKindOfClass:UIImageView.class]) {
|
||||
startImage = ((UIImageView *)startView).image;
|
||||
} else {
|
||||
startImage = YBIBSnapshotView(startView);
|
||||
}
|
||||
}
|
||||
if ([data respondsToSelector:@selector(yb_imageViewFrameWithContainerSize:imageSize:orientation:)]) {
|
||||
endFrame = [data yb_imageViewFrameWithContainerSize:self.bounds.size imageSize:startImage.size orientation:self.rotationHandler.currentOrientation];
|
||||
}
|
||||
|
||||
[self setTransitioning:YES isShow:YES];
|
||||
[self.animatedTransition yb_showTransitioningWithContainer:self startView:startView startImage:startImage endFrame:endFrame orientation:self.rotationHandler.currentOrientation completion:^{
|
||||
[self hideStatusBar];
|
||||
[self build];
|
||||
[self setTransitioning:NO isShow:YES];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)hide {
|
||||
__kindof UIView *startView;
|
||||
__kindof UIView *endView;
|
||||
UICollectionViewCell<YBIBCellProtocol> *cell = (UICollectionViewCell<YBIBCellProtocol> *)self.collectionView.centerCell;
|
||||
if ([cell respondsToSelector:@selector(yb_foregroundView)]) {
|
||||
startView = cell.yb_foregroundView;
|
||||
}
|
||||
if ([cell.yb_cellData respondsToSelector:@selector(yb_projectiveView)]) {
|
||||
endView = cell.yb_cellData.yb_projectiveView;
|
||||
}
|
||||
|
||||
for (id<YBIBToolViewHandler> handler in self.toolViewHandlers) {
|
||||
[handler yb_hide:YES];
|
||||
}
|
||||
[self showStatusBar];
|
||||
|
||||
[self setTransitioning:YES isShow:NO];
|
||||
[self.animatedTransition yb_hideTransitioningWithContainer:self startView:startView endView:endView orientation:self.rotationHandler.currentOrientation completion:^{
|
||||
[self rebuild];
|
||||
[self removeFromSuperview];
|
||||
[self setTransitioning:NO isShow:NO];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)reloadData {
|
||||
[self.dataMediator clear];
|
||||
NSInteger page = self.currentPage;
|
||||
[self.collectionView reloadData];
|
||||
self.currentPage = page;
|
||||
}
|
||||
|
||||
- (id<YBIBDataProtocol>)currentData {
|
||||
return [self.dataMediator dataForCellAtIndex:self.currentPage];
|
||||
}
|
||||
|
||||
#pragma mark - internal
|
||||
|
||||
- (void)setHiddenProjectiveView:(NSObject *)hiddenProjectiveView {
|
||||
if (_hiddenProjectiveView && [_hiddenProjectiveView respondsToSelector:@selector(setAlpha:)]) {
|
||||
CGFloat originAlpha = _hiddenProjectiveView.ybib_originAlpha;
|
||||
if (originAlpha != 1) {
|
||||
[_hiddenProjectiveView setValue:@(1) forKey:@"alpha"];
|
||||
[UIView animateWithDuration:0.2 animations:^{
|
||||
[self->_hiddenProjectiveView setValue:@(originAlpha) forKey:@"alpha"];
|
||||
}];
|
||||
} else {
|
||||
[_hiddenProjectiveView setValue:@(originAlpha) forKey:@"alpha"];
|
||||
}
|
||||
}
|
||||
_hiddenProjectiveView = hiddenProjectiveView;
|
||||
|
||||
if (!self.autoHideProjectiveView) return;
|
||||
|
||||
if (hiddenProjectiveView && [hiddenProjectiveView respondsToSelector:@selector(setAlpha:)]) {
|
||||
hiddenProjectiveView.ybib_originAlpha = ((NSNumber *)[hiddenProjectiveView valueForKey:@"alpha"]).floatValue;
|
||||
[hiddenProjectiveView setValue:@(0) forKey:@"alpha"];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)implementOperateBrowserProtocol:(id<YBIBOperateBrowserProtocol>)obj {
|
||||
__weak typeof(self) wSelf = self;
|
||||
if ([obj respondsToSelector:@selector(setYb_hideBrowser:)]) {
|
||||
[obj setYb_hideBrowser:^{
|
||||
__strong typeof(wSelf) self = wSelf;
|
||||
if (!self) return;
|
||||
[self hide];
|
||||
}];
|
||||
}
|
||||
if ([obj respondsToSelector:@selector(setYb_hideStatusBar:)]) {
|
||||
[obj setYb_hideStatusBar:^(BOOL hide) {
|
||||
__strong typeof(wSelf) self = wSelf;
|
||||
if (!self) return;
|
||||
hide ? [self hideStatusBar] : [self showStatusBar];
|
||||
}];
|
||||
}
|
||||
if ([obj respondsToSelector:@selector(setYb_hideToolViews:)]) {
|
||||
[obj setYb_hideToolViews:^(BOOL hide) {
|
||||
__strong typeof(wSelf) self = wSelf;
|
||||
if (!self) return;
|
||||
for (id<YBIBToolViewHandler> handler in self.toolViewHandlers) {
|
||||
[handler yb_hide:hide];
|
||||
}
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)implementGetBaseInfoProtocol:(id<YBIBGetBaseInfoProtocol>)obj {
|
||||
__weak typeof(self) wSelf = self;
|
||||
if ([obj respondsToSelector:@selector(setYb_currentOrientation:)]) {
|
||||
[obj setYb_currentOrientation:^UIDeviceOrientation{
|
||||
__strong typeof(wSelf) self = wSelf;
|
||||
if (!self) return UIDeviceOrientationPortrait;
|
||||
return self.rotationHandler.currentOrientation;
|
||||
}];
|
||||
}
|
||||
if ([obj respondsToSelector:@selector(setYb_containerSize:)]) {
|
||||
[obj setYb_containerSize:^CGSize(UIDeviceOrientation orientation) {
|
||||
__strong typeof(wSelf) self = wSelf;
|
||||
if (!self) return CGSizeZero;
|
||||
return [self.rotationHandler containerSizeWithOrientation:orientation];
|
||||
}];
|
||||
}
|
||||
if ([obj respondsToSelector:@selector(setYb_auxiliaryViewHandler:)]) {
|
||||
[obj setYb_auxiliaryViewHandler:^id<YBIBAuxiliaryViewHandler>{
|
||||
__strong typeof(wSelf) self = wSelf;
|
||||
if (!self) return nil;
|
||||
return self.auxiliaryViewHandler;
|
||||
}];
|
||||
}
|
||||
if ([obj respondsToSelector:@selector(setYb_webImageMediator:)]) {
|
||||
[obj setYb_webImageMediator:^id<YBIBWebImageMediator> {
|
||||
__strong typeof(wSelf) self = wSelf;
|
||||
if (!self) return nil;
|
||||
NSAssert(self.webImageMediator, @"'webImageMediator' should not be nil.");
|
||||
return self.webImageMediator;
|
||||
}];
|
||||
}
|
||||
if ([obj respondsToSelector:@selector(setYb_currentPage:)]) {
|
||||
[obj setYb_currentPage:^NSInteger{
|
||||
__strong typeof(wSelf) self = wSelf;
|
||||
if (!self) return 0;
|
||||
return self.currentPage;
|
||||
}];
|
||||
}
|
||||
if ([obj respondsToSelector:@selector(setYb_totalPage:)]) {
|
||||
[obj setYb_totalPage:^NSInteger{
|
||||
__strong typeof(wSelf) self = wSelf;
|
||||
if (!self) return 0;
|
||||
return [self.dataMediator numberOfCells];
|
||||
}];
|
||||
}
|
||||
if ([obj respondsToSelector:@selector(setYb_backView:)]) {
|
||||
obj.yb_backView = self;
|
||||
}
|
||||
if ([obj respondsToSelector:@selector(setYb_containerView:)]) {
|
||||
obj.yb_containerView = self.containerView;
|
||||
}
|
||||
if ([obj respondsToSelector:@selector(setYb_collectionView:)]) {
|
||||
[obj setYb_collectionView:^__kindof UICollectionView *{
|
||||
__strong typeof(wSelf) self = wSelf;
|
||||
if (!self) return nil;
|
||||
return self.collectionView;
|
||||
}];
|
||||
}
|
||||
if ([obj respondsToSelector:@selector(setYb_cellIsInCenter:)]) {
|
||||
[obj setYb_cellIsInCenter:^BOOL{
|
||||
__strong typeof(wSelf) self = wSelf;
|
||||
CGFloat pageF = self.collectionView.contentOffset.x / self.collectionView.bounds.size.width;
|
||||
// '0.001' is admissible error.
|
||||
return ABS(pageF - (NSInteger)pageF) <= 0.001;
|
||||
}];
|
||||
}
|
||||
if ([obj respondsToSelector:@selector(setYb_isTransitioning:)]) {
|
||||
[obj setYb_isTransitioning:^BOOL{
|
||||
__strong typeof(wSelf) self = wSelf;
|
||||
if (!self) return NO;
|
||||
return self.isTransitioning;
|
||||
}];
|
||||
}
|
||||
if ([obj respondsToSelector:@selector(setYb_isShowTransitioning:)]) {
|
||||
[obj setYb_isShowTransitioning:^BOOL{
|
||||
__strong typeof(wSelf) self = wSelf;
|
||||
if (!self) return NO;
|
||||
return self.isShowTransitioning;
|
||||
}];
|
||||
}
|
||||
if ([obj respondsToSelector:@selector(setYb_isHideTransitioning:)]) {
|
||||
[obj setYb_isHideTransitioning:^BOOL{
|
||||
__strong typeof(wSelf) self = wSelf;
|
||||
if (!self) return NO;
|
||||
return self.isHideTransitioning;
|
||||
}];
|
||||
}
|
||||
if ([obj respondsToSelector:@selector(setYb_isRotating:)]) {
|
||||
[obj setYb_isRotating:^BOOL{
|
||||
__strong typeof(wSelf) self = wSelf;
|
||||
if (!self) return NO;
|
||||
return self.rotationHandler.isRotating;
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - <UICollectionViewDataSource>
|
||||
|
||||
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
|
||||
return [self.dataMediator numberOfCells];
|
||||
}
|
||||
|
||||
- (UICollectionViewCell *)collectionView:(YBIBCollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
|
||||
id<YBIBDataProtocol> data = [self.dataMediator dataForCellAtIndex:indexPath.row];
|
||||
|
||||
UICollectionViewCell<YBIBCellProtocol> *cell = [collectionView dequeueReusableCellWithReuseIdentifier:[collectionView reuseIdentifierForCellClass:data.yb_classOfCell] forIndexPath:indexPath];
|
||||
|
||||
[self implementGetBaseInfoProtocol:cell];
|
||||
[self implementOperateBrowserProtocol:cell];
|
||||
|
||||
if ([cell respondsToSelector:@selector(setYb_selfPage:)]) {
|
||||
[cell setYb_selfPage:^NSInteger{
|
||||
return indexPath.row;
|
||||
}];
|
||||
}
|
||||
|
||||
cell.yb_cellData = data;
|
||||
|
||||
if ([cell respondsToSelector:@selector(yb_pageChanged)]) {
|
||||
[cell yb_pageChanged];
|
||||
}
|
||||
|
||||
[self.dataMediator preloadWithPage:indexPath.row];
|
||||
|
||||
return cell;
|
||||
}
|
||||
|
||||
#pragma mark - <UICollectionViewDelegate>
|
||||
|
||||
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
|
||||
CGFloat pageF = scrollView.contentOffset.x / scrollView.bounds.size.width;
|
||||
NSInteger page = (NSInteger)(pageF + 0.5);
|
||||
|
||||
for (id<YBIBToolViewHandler> handler in self.toolViewHandlers) {
|
||||
if ([handler respondsToSelector:@selector(yb_offsetXChanged:)]) {
|
||||
[handler yb_offsetXChanged:pageF];
|
||||
}
|
||||
}
|
||||
|
||||
if (!scrollView.isDecelerating && !scrollView.isDragging) {
|
||||
// Return if not scrolled by finger.
|
||||
return;
|
||||
}
|
||||
if (page < 0 || page > [self.dataMediator numberOfCells] - 1) return;
|
||||
if (self.rotationHandler.isRotating) return;
|
||||
|
||||
if (page != _currentPage) {
|
||||
_currentPage = page;
|
||||
[self pageNumberChanged];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - event
|
||||
|
||||
- (void)respondsToLongPress:(UILongPressGestureRecognizer *)sender {
|
||||
if (sender.state == UIGestureRecognizerStateBegan) {
|
||||
if ([self.delegate respondsToSelector:@selector(yb_imageBrowser:respondsToLongPressWithData:)]) {
|
||||
[self.delegate yb_imageBrowser:self respondsToLongPressWithData:[self currentData]];
|
||||
} else {
|
||||
for (id<YBIBToolViewHandler> handler in self.toolViewHandlers) {
|
||||
if ([handler respondsToSelector:@selector(yb_respondsToLongPress)]) {
|
||||
[handler yb_respondsToLongPress];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - getters & setters
|
||||
|
||||
- (YBIBContainerView *)containerView {
|
||||
if (!_containerView) {
|
||||
_containerView = [YBIBContainerView new];
|
||||
_containerView.backgroundColor = UIColor.clearColor;
|
||||
_containerView.layer.masksToBounds = YES;
|
||||
}
|
||||
return _containerView;
|
||||
}
|
||||
|
||||
- (YBIBCollectionView *)collectionView {
|
||||
if (!_collectionView) {
|
||||
_collectionView = [YBIBCollectionView new];
|
||||
_collectionView.delegate = self;
|
||||
_collectionView.dataSource = self;
|
||||
}
|
||||
return _collectionView;
|
||||
}
|
||||
- (void)setCurrentPage:(NSInteger)currentPage {
|
||||
NSInteger maxPage = self.dataMediator.numberOfCells - 1;
|
||||
if (currentPage > maxPage) {
|
||||
currentPage = maxPage;
|
||||
}
|
||||
_currentPage = currentPage;
|
||||
if (self.collectionView.superview) {
|
||||
[self collectionViewScrollToPage:currentPage];
|
||||
}
|
||||
}
|
||||
- (void)setDistanceBetweenPages:(CGFloat)distanceBetweenPages {
|
||||
self.collectionView.layout.distanceBetweenPages = distanceBetweenPages;
|
||||
}
|
||||
- (CGFloat)distanceBetweenPages {
|
||||
return self.collectionView.layout.distanceBetweenPages;
|
||||
}
|
||||
|
||||
- (void)setTransitioning:(BOOL)transitioning isShow:(BOOL)isShow {
|
||||
_transitioning = transitioning;
|
||||
_showTransitioning = transitioning && isShow;
|
||||
_hideTransitioning = transitioning && !isShow;
|
||||
|
||||
// Make 'self.userInteractionEnabled' always 'YES' to block external interaction.
|
||||
self.containerView.userInteractionEnabled = !transitioning;
|
||||
self.collectionView.userInteractionEnabled = !transitioning;
|
||||
|
||||
if (transitioning) {
|
||||
if ([self.delegate respondsToSelector:@selector(yb_imageBrowser:beginTransitioningWithIsShow:)]) {
|
||||
[self.delegate yb_imageBrowser:self beginTransitioningWithIsShow:isShow];
|
||||
}
|
||||
} else {
|
||||
if ([self.delegate respondsToSelector:@selector(yb_imageBrowser:endTransitioningWithIsShow:)]) {
|
||||
[self.delegate yb_imageBrowser:self endTransitioningWithIsShow:isShow];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (YBIBDataMediator *)dataMediator {
|
||||
if (!_dataMediator) {
|
||||
_dataMediator = [[YBIBDataMediator alloc] initWithBrowser:self];
|
||||
_dataMediator.dataCacheCountLimit = YBIBLowMemory() ? 9 : 27;
|
||||
_dataMediator.preloadCount = YBIBLowMemory() ? 0 : 2;
|
||||
}
|
||||
return _dataMediator;
|
||||
}
|
||||
- (void)setPreloadCount:(NSUInteger)preloadCount {
|
||||
self.dataMediator.preloadCount = preloadCount;
|
||||
}
|
||||
- (NSUInteger)preloadCount {
|
||||
return self.dataMediator.preloadCount;
|
||||
}
|
||||
|
||||
- (YBIBScreenRotationHandler *)rotationHandler {
|
||||
if (!_rotationHandler) {
|
||||
_rotationHandler = [[YBIBScreenRotationHandler alloc] initWithBrowser:self];
|
||||
}
|
||||
return _rotationHandler;
|
||||
}
|
||||
- (void)setSupportedOrientations:(UIInterfaceOrientationMask)supportedOrientations {
|
||||
self.rotationHandler.supportedOrientations = supportedOrientations;
|
||||
}
|
||||
- (UIInterfaceOrientationMask)supportedOrientations {
|
||||
return self.rotationHandler.supportedOrientations;
|
||||
}
|
||||
- (UIDeviceOrientation)currentOrientation {
|
||||
return self.rotationHandler.currentOrientation;
|
||||
}
|
||||
|
||||
@end
|
||||