首次提交

This commit is contained in:
启星
2025-08-08 11:05:33 +08:00
parent 1b3bb91b4a
commit adc1a2a25d
8803 changed files with 708874 additions and 0 deletions

View File

@@ -0,0 +1,49 @@
//
// KNActionSheet.h
// test
//
// Created by LuKane on 2019/12/18.
// Copyright © 2019 LuKane. All rights reserved.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
typedef void(^ActionSheetBlock)(NSInteger buttonIndex);
@interface KNActionSheet : UIView
+ (KNActionSheet *)share;
- (instancetype)initWithTitle:(NSString *)title
cancelTitle:(NSString *)cancelTitle
titleArray:(NSMutableArray <NSString *> *)titleArray
actionSheetBlock:(ActionSheetBlock)sheetBlock;
- (instancetype)initWithTitle:(NSString *)title
cancelTitle:(NSString *)cancelTitle
titleArray:(NSMutableArray <NSString *> *)titleArray
destructiveArray:(NSMutableArray <NSString *> *)destructiveArray
actionSheetBlock:(ActionSheetBlock)sheetBlock;
- (instancetype)initWithTitle:(NSString *)title
titleColor:(nullable UIColor *)titleColor
cancelTitle:(NSString *)cancelTitle
titleArray:(NSMutableArray <NSString *> *)titleArray
actionSheetBlock:(ActionSheetBlock)sheetBlock;
- (instancetype)initWithTitle:(NSString *)title
titleColor:(nullable UIColor *)titleColor
cancelTitle:(NSString *)cancelTitle
titleArray:(NSMutableArray <NSString *> *)titleArray
destructiveArray:(NSMutableArray <NSString *> *)destructiveArray
actionSheetBlock:(ActionSheetBlock)sheetBlock;
- (void)showOnView:(UIView *)view;
- (void)dismiss;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,334 @@
//
// KNActionSheet.m
// test
//
// Created by LuKane on 2019/12/18.
// Copyright © 2019 LuKane. All rights reserved.
//
#import "KNActionSheet.h"
#import "KNActionSheetItem.h"
#ifndef ScreenWidth
#define ScreenWidth [UIScreen mainScreen].bounds.size.width
#endif
#ifndef ScreenHeight
#define ScreenHeight [UIScreen mainScreen].bounds.size.height
#endif
#define kKNActionCoverBackgroundColor [UIColor colorWithRed:30/255.f green:30/255.f blue:30/255.f alpha:1.f]
#define kKNActionBgViewBackgroundColor [UIColor colorWithRed:220/255.f green:220/255.f blue:220/255.f alpha:1.f]
#define kKNActionDuration 0.3
@interface KNActionSheet()<KNActionSheetItemDelegate>{
NSString *_title;
UIColor *_titleColor;
NSString *_cancelTitle;
ActionSheetBlock _sheetBlock;
NSMutableArray *_titleArray;
NSMutableArray *_destruciveArray;
KNActionSheetItem *_cancelItem;
UIView *_coverView;
UIView *_bgView;
UIView *_bottomView;
NSMutableArray *_itemsArr;
NSMutableArray *_lineArr;
BOOL _isShow;
CGFloat _kKNActionItemHeight;
CGFloat _padding;
}
@end
@implementation KNActionSheet
+ (KNActionSheet *)share{
static dispatch_once_t onceToken;
static KNActionSheet *share;
dispatch_once(&onceToken, ^{
share = [[self alloc] init];
});
return share;
}
- (instancetype)initWithTitle:(NSString *)title
cancelTitle:(NSString *)cancelTitle
titleArray:(NSMutableArray <NSString *> *)titleArray
actionSheetBlock:(ActionSheetBlock)sheetBlock{
return [self initWithTitle:title titleColor:nil cancelTitle:cancelTitle titleArray:titleArray actionSheetBlock:sheetBlock];
}
- (instancetype)initWithTitle:(NSString *)title
cancelTitle:(NSString *)cancelTitle
titleArray:(NSMutableArray <NSString *> *)titleArray
destructiveArray:(NSMutableArray <NSString *> *)destructiveArray
actionSheetBlock:(ActionSheetBlock)sheetBlock{
return [self initWithTitle:title titleColor:nil cancelTitle:cancelTitle titleArray:titleArray destructiveArray:destructiveArray actionSheetBlock:sheetBlock];
}
- (instancetype)initWithTitle:(NSString *)title
titleColor:(nullable UIColor *)titleColor
cancelTitle:(NSString *)cancelTitle
titleArray:(NSMutableArray <NSString *> *)titleArray
actionSheetBlock:(ActionSheetBlock)sheetBlock{
if (self = [super init]) {
_title = title;
_titleColor = titleColor;
_cancelTitle = cancelTitle;
_titleArray = titleArray;
_sheetBlock = sheetBlock;
[self setupSubViews];
}
return self;
}
- (instancetype)initWithTitle:(NSString *)title
titleColor:(nullable UIColor *)titleColor
cancelTitle:(NSString *)cancelTitle
titleArray:(NSMutableArray <NSString *> *)titleArray
destructiveArray:(NSMutableArray <NSString *> *)destructiveArray
actionSheetBlock:(ActionSheetBlock)sheetBlock{
if (self = [super init]) {
_title = title;
_titleColor = titleColor;
_cancelTitle = cancelTitle;
_titleArray = titleArray;
_sheetBlock = sheetBlock;
_destruciveArray = destructiveArray;
[self setupSubViews];
}
return self;
}
- (void)setupSubViews{
[self.subviews enumerateObjectsUsingBlock:^(__kindof UIView * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
[obj removeFromSuperview];
}];
_kKNActionItemHeight = 48.7;
_padding = 0.3;
if (ScreenWidth <= 375) {
_kKNActionItemHeight = 48.5;
_padding = 0.5;
}
if ([self isEmptyArray:_titleArray]) {
return;
}
if (![self isEmptyArray:_destruciveArray]) {
if (_destruciveArray.count > _titleArray.count) {
return;
}
}
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(deviceWillOrientation)
name:UIApplicationWillChangeStatusBarOrientationNotification
object:nil];
[self setBackgroundColor:[UIColor clearColor]];
[self setFrame:[UIApplication sharedApplication].windows.lastObject.bounds];
UIView *coverView = [[UIView alloc] initWithFrame:self.bounds];
[coverView setBackgroundColor:kKNActionCoverBackgroundColor];
[coverView setAlpha:0];
[coverView addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(coverViewDidClick)]];
[self addSubview:coverView];
_coverView = coverView;
UIView *bgView = [[UIView alloc] init];
[bgView setBackgroundColor:[UIColor whiteColor]];
[self addSubview:bgView];
_bgView = bgView;
if ([self isEmptyString:_cancelTitle]) {
_cancelTitle = @"取消";
}
_itemsArr = [NSMutableArray array];
_lineArr = [NSMutableArray array];
if (![self isEmptyString:_title]) {
KNActionSheetItem *item = [[KNActionSheetItem alloc] init];
item.userInteractionEnabled = false;
if (_titleColor) {
item.color = _titleColor;
}
item.title = _title;
[_itemsArr addObject:item];
[bgView addSubview:item];
CALayer *line = [CALayer layer];
[line setBackgroundColor:[kKNActionBgViewBackgroundColor CGColor]];
[bgView.layer addSublayer:line];
[_lineArr addObject:line];
}
for (NSInteger i = 0; i < _titleArray.count; i++) {
KNActionSheetItem *item = [[KNActionSheetItem alloc] init];
for (NSInteger j = 0; j < _destruciveArray.count; j++) {
NSInteger jIndex = [_destruciveArray[j] integerValue];
if (jIndex < _titleArray.count) {
if (i == jIndex) {
item.color = [UIColor redColor];
}
}
}
item.title = _titleArray[i];
item.tag = i;
item.delegate = self;
[_itemsArr addObject:item];
[bgView addSubview:item];
CALayer *line = [CALayer layer];
[line setBackgroundColor:[kKNActionBgViewBackgroundColor CGColor]];
[bgView.layer addSublayer:line];
[_lineArr addObject:line];
}
KNActionSheetItem *cancelItem = [[KNActionSheetItem alloc] init];
cancelItem.title = _cancelTitle;
[cancelItem addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(coverViewDidClick)]];
[bgView addSubview:cancelItem];
_cancelItem = cancelItem;
}
- (void)deviceWillOrientation{
[self dismiss];
}
- (void)actionSheetItemDidClick:(NSInteger)index{
if (_sheetBlock) {
_sheetBlock(index);
}
[self dismiss];
}
- (void)coverViewDidClick{
if (_sheetBlock) {
_sheetBlock(-1);
}
[self dismiss];
}
- (void)showOnView:(UIView *)view{
[_coverView setAlpha:0];
[_bgView setTransform:CGAffineTransformIdentity];
[view addSubview:self];
_isShow = true;
}
- (void)dismiss{
[UIView animateWithDuration:kKNActionDuration animations:^{
[self->_coverView setAlpha:0];
[self->_bgView setTransform:CGAffineTransformIdentity];
} completion:^(BOOL finished) {
[self removeFromSuperview];
}];
_isShow = false;
}
- (BOOL)isEmptyString:(NSString *)string{
if(string == nil || string == NULL || [string isKindOfClass:[NSNull class]] || [[string stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] length] == 0){
return true;
}
return false;
}
- (BOOL)isEmptyArray:(NSArray *)array{
if(array == nil || [array isKindOfClass:[NSNull class]] || array.count == 0){
return true;
}
return false;
}
- (void)layoutSubviews{
[super layoutSubviews];
_coverView.frame = self.bounds;
if (@available(iOS 11.0, *)){
UIEdgeInsets insets = self.safeAreaInsets;
CGFloat height = _kKNActionItemHeight * (_itemsArr.count + 1) + 8;
_bgView.frame = CGRectMake(0, self.frame.size.height, self.frame.size.width, height + insets.bottom);
[self addRectCorners:UIRectCornerTopLeft | UIRectCornerTopRight width:13 view:_bgView];
for (NSInteger i = 0; i < _itemsArr.count; i++) {
KNActionSheetItem *item = _itemsArr[i];
if (i == 0) {
[self addRectCorners:UIRectCornerTopLeft | UIRectCornerTopRight width:13 view:item];
}
item.frame = CGRectMake(0, (_kKNActionItemHeight + _padding) * i, _bgView.frame.size.width, _kKNActionItemHeight);
CALayer *line = _lineArr[i];
if (i != _itemsArr.count - 1) {
line.frame = CGRectMake(0, CGRectGetMaxY(item.frame), _bgView.frame.size.width, _padding);
}else{
line.frame = CGRectMake(0, CGRectGetMaxY(item.frame), _bgView.frame.size.width, _padding + 8);
}
}
_cancelItem.frame = CGRectMake(0, _bgView.frame.size.height - _kKNActionItemHeight - insets.bottom, _bgView.frame.size.width, _kKNActionItemHeight);
if (_isShow) {
_isShow = false;
[UIView animateWithDuration:kKNActionDuration animations:^{
[self->_coverView setAlpha:0.3];
[self->_bgView setTransform:CGAffineTransformMakeTranslation(0, -self->_bgView.frame.size.height)];
}];
}
} else {
CGFloat height = _kKNActionItemHeight * (_itemsArr.count + 1) + 8;
_bgView.frame = CGRectMake(0, self.frame.size.height, self.frame.size.width, height);
[self addRectCorners:UIRectCornerTopLeft | UIRectCornerTopRight width:13 view:_bgView];
for (NSInteger i = 0; i < _itemsArr.count; i++) {
KNActionSheetItem *item = _itemsArr[i];
if (i == 0) {
[self addRectCorners:UIRectCornerTopLeft | UIRectCornerTopRight width:13 view:item];
}
item.frame = CGRectMake(0, (_kKNActionItemHeight + _padding) * i, _bgView.frame.size.width, _kKNActionItemHeight);
CALayer *line = _lineArr[i];
if (i != _itemsArr.count - 1) {
line.frame = CGRectMake(0, CGRectGetMaxY(item.frame), _bgView.frame.size.width, _padding);
}else{
line.frame = CGRectMake(0, CGRectGetMaxY(item.frame), _bgView.frame.size.width, _padding + 8);
}
}
_cancelItem.frame = CGRectMake(0, _bgView.frame.size.height - _kKNActionItemHeight, _bgView.frame.size.width, _kKNActionItemHeight);
if (_isShow) {
_isShow = false;
[UIView animateWithDuration:kKNActionDuration animations:^{
[self->_coverView setAlpha:0.3];
[self->_bgView setTransform:CGAffineTransformMakeTranslation(0, -self->_bgView.frame.size.height)];
}];
}
}
}
- (void)dealloc{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)addRectCorners:(UIRectCorner)corners width:(CGFloat)width view:(UIView *)view{
if (width > view.frame.size.width) {
return;
}
UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:self.bounds byRoundingCorners:corners cornerRadii:CGSizeMake(width, width)];
CAShapeLayer *maskLayer = [[CAShapeLayer alloc] init];
maskLayer.frame = view.bounds;
maskLayer.path = maskPath.CGPath;
view.layer.mask = maskLayer;
}
@end

View File

@@ -0,0 +1,34 @@
//
// KNActionSheetItem.h
// test
//
// Created by LuKane on 2019/12/18.
// Copyright © 2019 LuKane. All rights reserved.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@protocol KNActionSheetItemDelegate <NSObject>
/// item did click
/// @param index self's tag
- (void)actionSheetItemDidClick:(NSInteger)index;
@end
@interface KNActionSheetItem : UIView
/// delegate
@property (nonatomic,weak ) id<KNActionSheetItemDelegate> delegate;
/// title
@property (nonatomic,copy ) NSString *title;
/// title color
@property (nonatomic,copy ) UIColor *color;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,69 @@
//
// KNActionSheetItem.m
// test
//
// Created by LuKane on 2019/12/18.
// Copyright © 2019 LuKane. All rights reserved.
//
#import "KNActionSheetItem.h"
@implementation KNActionSheetItem
- (instancetype)init{
if (self = [super init]) {
_color = [UIColor blackColor];
[self setBackgroundColor:[UIColor whiteColor]];
}
return self;
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
[self setBackgroundColor:[UIColor colorWithRed:220 / 255.f green:220 / 255.f blue:220 / 255.f alpha:1.f]];
}
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
[self setBackgroundColor:[UIColor whiteColor]];
[self setAlpha:1.f];
UITouch *touch = [[event allTouches] anyObject];
CGPoint point = [touch locationInView:[touch view]];
CGFloat x = point.x;
CGFloat y = point.y;
if(x < self.frame.size.width && x > 0 && y >0 && y < self.frame.size.height){
if([_delegate respondsToSelector:@selector(actionSheetItemDidClick:)]){
[_delegate actionSheetItemDidClick:self.tag];
}
}
}
- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
[self setBackgroundColor:[UIColor whiteColor]];
[self setAlpha:1.f];
}
- (void)setTitle:(NSString *)title{
_title = title;
[self setNeedsDisplay];
}
- (void)setColor:(UIColor *)color{
_color = color;
[self setNeedsDisplay];
}
- (void)drawRect:(CGRect)rect{
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
dict[NSFontAttributeName] = [UIFont systemFontOfSize:16];
dict[NSForegroundColorAttributeName] = _color;
NSMutableParagraphStyle *paragrap = [[NSMutableParagraphStyle defaultParagraphStyle] mutableCopy];
paragrap.alignment = NSTextAlignmentCenter;
dict[NSParagraphStyleAttributeName] = paragrap;
[_title drawInRect:(CGRect){{0,self.bounds.size.height * 0.3},self.bounds.size} withAttributes:dict];
}
@end

View File

@@ -0,0 +1,58 @@
//
// KNPhotoAVPlayerActionBar.h
// KNPhotoBrowser
//
// Created by LuKane on 2019/6/14.
// Copyright © 2019 LuKane. All rights reserved.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@protocol KNPhotoAVPlayerActionBarDelegate <NSObject>
@optional
/**
actionBar pause or stop btn did click
@param isNeedPlay isNeedPlay
*/
- (void)photoAVPlayerActionBarClickWithIsPlay:(BOOL)isNeedPlay;
/**
actionBar value has changed by slider
@param value value
*/
- (void)photoAVPlayerActionBarChangeValue:(float)value;
@end
@interface KNPhotoAVPlayerActionBar : UIView
/**
current play time of the video
*/
@property (nonatomic,assign) float currentTime;
/**
duration of the video
*/
@property (nonatomic,assign) float allDuration;
@property (nonatomic,weak ) id<KNPhotoAVPlayerActionBarDelegate> delegate;
/**
setter or getter of isPlaying of ActionBar
*/
@property (nonatomic,assign) BOOL isPlaying;
/**
reset all information of ActionBar
*/
- (void)resetActionBarAllInfo;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,220 @@
//
// KNPhotoAVPlayerActionBar.m
// KNPhotoBrowser
//
// Created by LuKane on 2019/6/14.
// Copyright © 2019 LuKane. All rights reserved.
//
#import "KNPhotoAVPlayerActionBar.h"
#import <objc/runtime.h>
@interface KNPhotoAVPlayerSlider : UISlider
@end
@implementation KNPhotoAVPlayerSlider
- (instancetype)initWithFrame:(CGRect)frame{
if (self = [super initWithFrame:frame]) {
NSBundle *bundle = [NSBundle bundleForClass:NSClassFromString(@"KNPhotoBrowser")];
if(UIScreen.mainScreen.scale < 3) {
[self setThumbImage:[UIImage imageNamed:@"KNPhotoBrowser.bundle/circlePoint@2x.png" inBundle:bundle compatibleWithTraitCollection:nil] forState:UIControlStateNormal];
}else {
[self setThumbImage:[UIImage imageNamed:@"KNPhotoBrowser.bundle/circlePoint@3x.png" inBundle:bundle compatibleWithTraitCollection:nil] forState:UIControlStateNormal];
}
[self setMinimumTrackTintColor:[UIColor whiteColor]];
[self setMaximumTrackTintColor:[[UIColor whiteColor] colorWithAlphaComponent:0.5]];
}
return self;
}
- (CGRect)minimumValueImageRectForBounds:(CGRect)bounds{
CGRect frame = [super minimumValueImageRectForBounds:bounds];
return CGRectMake(frame.origin.x, frame.origin.y, frame.size.width, 15);
}
- (CGRect)maximumValueImageRectForBounds:(CGRect)bounds{
CGRect frame = [super maximumValueImageRectForBounds:bounds];
return CGRectMake(frame.origin.x, frame.origin.y, frame.size.width, 15);
}
- (CGRect)trackRectForBounds:(CGRect)bounds{
CGRect frame = [super trackRectForBounds:bounds];
return CGRectMake(frame.origin.x, frame.origin.y, frame.size.width, 3);
}
- (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 KNPhotoAVPlayerActionBar ()
@property (nonatomic,strong) UIButton *pauseStopBtn;
@property (nonatomic,strong) UILabel *preTimeLabel;
@property (nonatomic,strong) UILabel *endTimeLabel;
@property (nonatomic,strong) KNPhotoAVPlayerSlider *slider;
@property (nonatomic, strong) NSBundle *bundle;
@end
@implementation KNPhotoAVPlayerActionBar {
BOOL _isDragging;
}
/****************************** == lazy == ********************************/
- (NSBundle *)bundle {
if (!_bundle) {
_bundle = [NSBundle bundleForClass:NSClassFromString(@"KNPhotoBrowser")];
}
return _bundle;
}
- (UIButton *)pauseStopBtn{
if (!_pauseStopBtn) {
_pauseStopBtn = [UIButton buttonWithType:UIButtonTypeCustom];
if(UIScreen.mainScreen.scale < 3) {
[_pauseStopBtn setImage:[UIImage imageNamed:@"KNPhotoBrowser.bundle/pause@2x.png" inBundle:self.bundle compatibleWithTraitCollection:nil] forState:UIControlStateNormal];
}else {
[_pauseStopBtn setImage:[UIImage imageNamed:@"KNPhotoBrowser.bundle/pause@3x.png" inBundle:self.bundle compatibleWithTraitCollection:nil] forState:UIControlStateNormal];
}
[_pauseStopBtn addTarget:self action:@selector(pauseStopBtnDidClick) forControlEvents:UIControlEventTouchUpInside];
}
return _pauseStopBtn;
}
- (UILabel *)preTimeLabel{
if (!_preTimeLabel) {
_preTimeLabel = [UILabel new];
[_preTimeLabel setText:@"00:00"];
[_preTimeLabel setFont:[UIFont systemFontOfSize:11]];
[_preTimeLabel setTextAlignment:NSTextAlignmentCenter];
[_preTimeLabel setAdjustsFontSizeToFitWidth:true];
[_preTimeLabel setTextColor:UIColor.whiteColor];
}
return _preTimeLabel;
}
- (UILabel *)endTimeLabel{
if (!_endTimeLabel) {
_endTimeLabel = [[UILabel alloc] init];
[_endTimeLabel setText:self.preTimeLabel.text];
[_endTimeLabel setFont:self.preTimeLabel.font];
[_endTimeLabel setTextColor:self.preTimeLabel.textColor];
[_endTimeLabel setAdjustsFontSizeToFitWidth:true];
[_endTimeLabel setTextAlignment:self.preTimeLabel.textAlignment];
}
return _endTimeLabel;
}
- (KNPhotoAVPlayerSlider *)slider{
if (!_slider) {
_slider = [[KNPhotoAVPlayerSlider alloc] init];
[_slider addTarget:self action:@selector(actionBarSliderFinished:) forControlEvents:UIControlEventTouchUpInside | UIControlEventTouchCancel | UIControlEventTouchUpOutside];
[_slider addTarget:self action:@selector(actionBarSliderDown:) forControlEvents:UIControlEventTouchDown];
}
return _slider;
}
/****************************** == lazy == ********************************/
- (instancetype)initWithFrame:(CGRect)frame{
if (self = [super initWithFrame:frame]) {
self.layer.cornerRadius = 5;
self.clipsToBounds = true;
[self addGestureRecognizer:[[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(dragViewMoved:)]];
[self addSubview:self.pauseStopBtn];
[self addSubview:self.preTimeLabel];
[self addSubview:self.endTimeLabel];
[self addSubview:self.slider];
}
return self;
}
- (void)dragViewMoved:(UIPanGestureRecognizer *)panGestureRecognizer{
}
- (void)pauseStopBtnDidClick{
if ([_delegate respondsToSelector:@selector(photoAVPlayerActionBarClickWithIsPlay:)]) {
[_delegate photoAVPlayerActionBarClickWithIsPlay:!_isPlaying];
}
}
- (void)actionBarSliderFinished:(KNPhotoAVPlayerSlider *)slider{
if ([_delegate respondsToSelector:@selector(photoAVPlayerActionBarChangeValue:)]) {
[_delegate photoAVPlayerActionBarChangeValue:slider.value];
}
_isDragging = false;
[_slider setUserInteractionEnabled:true];
}
- (void)actionBarSliderDown:(KNPhotoAVPlayerSlider *)slider{
_isDragging = true;
[slider setUserInteractionEnabled:false];
}
- (void)setIsPlaying:(BOOL)isPlaying{
_isPlaying = isPlaying;
if (isPlaying) {
_isDragging = false;
[_slider setUserInteractionEnabled:true];
if(UIScreen.mainScreen.scale < 3) {
[_pauseStopBtn setImage:[UIImage imageNamed:@"KNPhotoBrowser.bundle/pause@2x.png" inBundle:self.bundle compatibleWithTraitCollection:nil] forState:UIControlStateNormal];
}else {
[_pauseStopBtn setImage:[UIImage imageNamed:@"KNPhotoBrowser.bundle/pause@3x.png" inBundle:self.bundle compatibleWithTraitCollection:nil] forState:UIControlStateNormal];
}
}else{
if(UIScreen.mainScreen.scale < 3) {
[_pauseStopBtn setImage:[UIImage imageNamed:@"KNPhotoBrowser.bundle/play@2x.png" inBundle:self.bundle compatibleWithTraitCollection:nil] forState:UIControlStateNormal];
}else {
[_pauseStopBtn setImage:[UIImage imageNamed:@"KNPhotoBrowser.bundle/play@3x.png" inBundle:self.bundle compatibleWithTraitCollection:nil] forState:UIControlStateNormal];
}
}
}
- (void)setCurrentTime:(float)currentTime{
if (isnan(currentTime)) return;
_currentTime = currentTime;
[_preTimeLabel setText:[self caluTimeFormatWithSeconds:currentTime]];
if (_isDragging == false) {
[_slider setValue:currentTime animated:false];
}
}
- (void)setAllDuration:(float)allDuration{
if (isnan(allDuration)) return;
_allDuration = allDuration;
_slider.maximumValue = allDuration;
[_endTimeLabel setText:[self caluTimeFormatWithSeconds:allDuration]];
}
- (NSString *)caluTimeFormatWithSeconds:(NSInteger)seconds{
if(seconds > 60 * 60){ // one hour or more than
return [NSString stringWithFormat:@"%02zd:%02zd:%02zd",(NSInteger)(seconds / 3600),(NSInteger)((seconds % 3600) / 60) , (NSInteger)(seconds % 60)];
}else{ // in one hour
return [NSString stringWithFormat:@"%02zd:%02zd", (NSInteger)((seconds % 3600) / 60), (NSInteger)(seconds % 60)];
}
}
- (void)resetActionBarAllInfo{
self.isPlaying = false;
_preTimeLabel.text = @"00:00";
_endTimeLabel.text = @"00:00";
}
- (void)layoutSubviews{
[super layoutSubviews];
self.pauseStopBtn.frame = CGRectMake(10, 10, 20, 20);
self.preTimeLabel.frame = CGRectMake(30, 5, 55, 30);
self.endTimeLabel.frame = CGRectMake(self.bounds.size.width - 55, 5, 55, 30);
self.slider.frame = CGRectMake(85, 0, CGRectGetMinX(self.endTimeLabel.frame) - CGRectGetMaxX(self.preTimeLabel.frame), 40);
}
@end

View File

@@ -0,0 +1,59 @@
//
// KNPhotoAVPlayerActionView.h
// KNPhotoBrowser
//
// Created by LuKane on 2019/6/14.
// Copyright © 2019 LuKane. All rights reserved.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@protocol KNPhotoAVPlayerActionViewDelegate <NSObject>
@optional
/**
actionView's Pause imageView
*/
- (void)photoAVPlayerActionViewPauseOrStop;
/**
actionView's dismiss imageView
*/
- (void)photoAVPlayerActionViewDismiss;
/**
actionView
*/
- (void)photoAVPlayerActionViewDidClickIsHidden:(BOOL)isHidden;
@end
@interface KNPhotoAVPlayerActionView : UIView
/**
avPlayerActionView need hidden or not
*/
- (void)avplayerActionViewNeedHidden:(BOOL)isHidden;
@property (nonatomic,weak ) id<KNPhotoAVPlayerActionViewDelegate> delegate;
/**
player is buffering or not
*/
@property (nonatomic,assign) BOOL isBuffering;
/**
current player is playing
*/
@property (nonatomic,assign) BOOL isPlaying;
/**
* current player is downloading
*/
@property (nonatomic,assign) BOOL isDownloading;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,162 @@
//
// KNPhotoAVPlayerActionView.m
// KNPhotoBrowser
//
// Created by LuKane on 2019/6/14.
// Copyright © 2019 LuKane. All rights reserved.
//
#import "KNPhotoAVPlayerActionView.h"
#import "KNPhotoBrowserPch.h"
#import <objc/runtime.h>
@interface KNPhotoAVPlayerActionView()
/**
stop || play view
*/
@property (nonatomic,weak ) UIImageView *pauseImgView;
/**
dismiss view
*/
@property (nonatomic,weak ) UIImageView *dismissImgView;
/**
loading view
*/
@property (nonatomic,strong) UIActivityIndicatorView *indicatorView;
@end
@implementation KNPhotoAVPlayerActionView
- (UIActivityIndicatorView *)indicatorView{
if (!_indicatorView) {
_indicatorView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite];
[_indicatorView setHidesWhenStopped:true];
}
return _indicatorView;
}
- (instancetype)initWithFrame:(CGRect)frame{
if (self = [super initWithFrame:frame]) {
[self setBackgroundColor:UIColor.clearColor];
[self addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(actionViewDidClick)]];
[self setupSubViews];
}
return self;
}
- (void)setupSubViews{
NSBundle *bundle = [NSBundle bundleForClass:NSClassFromString(@"KNPhotoBrowser")];
// 1.stop || play imageView
UIImageView *pauseImgView = [[UIImageView alloc] init];
[pauseImgView setUserInteractionEnabled:true];
[pauseImgView addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(pauseImageViewDidClick)]];
if(UIScreen.mainScreen.scale < 3) {
[pauseImgView setImage:[UIImage imageNamed:@"KNPhotoBrowser.bundle/playCenter@2x" inBundle:bundle compatibleWithTraitCollection:nil]];
}else {
[pauseImgView setImage:[UIImage imageNamed:@"KNPhotoBrowser.bundle/playCenter@3x" inBundle:bundle compatibleWithTraitCollection:nil]];
}
[self addSubview:pauseImgView];
_pauseImgView = pauseImgView;
// 2.dismiss imageView
UIImageView *dismissImageView = [[UIImageView alloc] init];
[dismissImageView setUserInteractionEnabled:true];
[dismissImageView addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(dismissImageViewDidClick)]];
if(UIScreen.mainScreen.scale < 3) {
[dismissImageView setImage:[UIImage imageNamed:@"KNPhotoBrowser.bundle/dismiss@2x" inBundle:bundle compatibleWithTraitCollection:nil]];
}else {
[dismissImageView setImage:[UIImage imageNamed:@"KNPhotoBrowser.bundle/dismiss@3x" inBundle:bundle compatibleWithTraitCollection:nil]];
}
[dismissImageView setHidden:true];
[self addSubview:dismissImageView];
_dismissImgView = dismissImageView;
// 3.loading imageView
[self addSubview:self.indicatorView];
}
- (void)layoutSubviews{
[super layoutSubviews];
_pauseImgView.frame = CGRectMake((self.frame.size.width - 80) * 0.5, (self.frame.size.height - 80) * 0.5, 80, 80);
CGFloat y = 25;
CGFloat x = 10;
if(PBDeviceHasBang){
y = 45;
x = 20;
}
if(!isPortrait){
y = 15;
x = 35;
}
_dismissImgView.frame = CGRectMake(x, y, 20, 20);
_indicatorView.frame = CGRectMake((self.frame.size.width - 30) * 0.5, (self.frame.size.height - 30) * 0.5, 30, 30);
}
- (void)pauseImageViewDidClick{
if (_pauseImgView.hidden == false) {
_pauseImgView.hidden = true;
}
if ([_delegate respondsToSelector:@selector(photoAVPlayerActionViewPauseOrStop)]) {
[_delegate photoAVPlayerActionViewPauseOrStop];
}
}
- (void)dismissImageViewDidClick{
if ([_delegate respondsToSelector:@selector(photoAVPlayerActionViewDismiss)]) {
[_delegate photoAVPlayerActionViewDismiss];
}
}
- (void)actionViewDidClick{
[_dismissImgView setHidden:!_dismissImgView.hidden];
if ([_delegate respondsToSelector:@selector(photoAVPlayerActionViewDidClickIsHidden:)]) {
[_delegate photoAVPlayerActionViewDidClickIsHidden:_dismissImgView.isHidden];
}
}
/**
avPlayerActionView need hidden or not
*/
- (void)avplayerActionViewNeedHidden:(BOOL)isHidden{
if (isHidden == true) {
[_dismissImgView setHidden:true];
[_indicatorView setHidden:true];
[_pauseImgView setHidden:true];
}else {
[_pauseImgView setHidden:false];
}
}
- (void)setIsBuffering:(BOOL)isBuffering{
_isBuffering = isBuffering;
if (isBuffering) {
[_indicatorView startAnimating];
}else{
[_indicatorView stopAnimating];
}
}
- (void)setIsPlaying:(BOOL)isPlaying{
_isPlaying = isPlaying;
[_pauseImgView setHidden:isPlaying];
}
- (void)setIsDownloading:(BOOL)isDownloading{
_isDownloading = isDownloading;
[_pauseImgView setHidden:isDownloading];
}
@end

View File

@@ -0,0 +1,84 @@
//
// KNPhotoAVPlayerView.h
// KNPhotoBrowser
//
// Created by LuKane on 2019/6/14.
// Copyright © 2019 LuKane. All rights reserved.
//
#import <UIKit/UIKit.h>
#import <AVFoundation/AVFoundation.h>
#import "KNPhotoPlayerProtocol.h"
#import "KNPhotoBrowser.h"
NS_ASSUME_NONNULL_BEGIN
@interface KNPhotoAVPlayerView : UIView
/// create locate player with photoItems
/// @param photoItems photoItem
/// @param placeHolder placeHolder image
- (void)playerOnLinePhotoItems:(KNPhotoItems *)photoItems placeHolder:(UIImage *_Nullable)placeHolder;
/// reset AVPlayer
- (void)playerWillReset;
/// AVPlayer will be swiped by hand
- (void)playerWillSwipe;
/// AVPlayer will cancel swipe
- (void)playerWillSwipeCancel;
/// AVPlayer play as rate
/// @param rate rate
- (void)playerRate:(CGFloat)rate;
/**
* is or not need Video placeHolder
*/
@property (nonatomic,assign) BOOL isNeedVideoPlaceHolder;
/**
* auto play when you need
*/
@property (nonatomic,assign) BOOL isNeedAutoPlay;
/**
* player view
*/
@property (nonatomic,strong,nullable) UIView *playerView;
/**
* player background view (as locate current location for swipe)
*/
@property (nonatomic,strong,nullable) UIView *playerBgView;
/**
* placeHolder imageView
*/
@property (nonatomic,strong,nullable) UIImageView *placeHolderImgView;
/**
* layer of player
*/
@property (nonatomic,strong,nullable) AVPlayerLayer *playerLayer;
/**
* if video has played ,even though one seconds : TRUE
*/
@property (nonatomic,assign) BOOL isBeginPlayed;
/**
* default is solo ambient : TRUE `AVAudioSessionCategorySoloAmbient`
* if set false, that will be `AVAudioSessionCategoryAmbient`
*/
@property (nonatomic, assign) BOOL isSoloAmbient;
/**
* delegate
*/
@property (nonatomic,weak ) id<KNPhotoPlayerViewDelegate> delegate;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,361 @@
//
// KNPhotoAVPlayerView.m
// KNPhotoBrowser
//
// Created by LuKane on 2019/6/14.
// Copyright © 2019 LuKane. All rights reserved.
//
#import "KNPhotoAVPlayerView.h"
#import "KNPhotoAVPlayerActionBar.h"
#import "KNPhotoAVPlayerActionView.h"
#import "KNPhotoBrowserPch.h"
@interface KNPhotoAVPlayerView ()<KNPhotoAVPlayerActionViewDelegate,KNPhotoAVPlayerActionBarDelegate>
@property (nonatomic,strong) AVPlayer *player;
@property (nonatomic,strong) AVPlayerItem *item;
@property (nonatomic,strong) KNPhotoAVPlayerActionView *actionView;
@property (nonatomic,strong) KNPhotoAVPlayerActionBar *actionBar;
@property (nonatomic,copy ) NSString *url;
@property (nonatomic,strong) UIImage *placeHolder;
@property (nonatomic,strong) id timeObserver;
@property (nonatomic,assign) BOOL isPlaying;
@property (nonatomic,assign) BOOL isGetAllPlayItem;
@property (nonatomic,assign) BOOL isDragging;
@property (nonatomic,assign) BOOL isEnterBackground;
@property (nonatomic,assign) BOOL isAddObserver;
@end
@implementation KNPhotoAVPlayerView
- (KNPhotoAVPlayerActionView *)actionView{
if (!_actionView) {
_actionView = [[KNPhotoAVPlayerActionView alloc] init];
[_actionView addGestureRecognizer:[[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(photoAVPlayerActionViewDidLongPress:)]];
_actionView.delegate = self;
_actionView.isBuffering = false;
_actionView.isPlaying = false;
}
return _actionView;
}
- (KNPhotoAVPlayerActionBar *)actionBar{
if (!_actionBar) {
_actionBar = [[KNPhotoAVPlayerActionBar alloc] init];
_actionBar.backgroundColor = [UIColor colorWithRed:45/255.0 green:45/255.0 blue:45/255.0 alpha:1.];
_actionBar.delegate = self;
_actionBar.isPlaying = false;
_actionBar.hidden = true;
}
return _actionBar;
}
- (UIView *)playerBgView{
if (!_playerBgView) {
_playerBgView = [[UIView alloc] init];
}
return _playerBgView;
}
- (UIView *)playerView{
if (!_playerView) {
_playerView = [[UIView alloc] init];
_playerView.backgroundColor = UIColor.clearColor;
}
return _playerView;
}
- (UIImageView *)placeHolderImgView{
if (!_placeHolderImgView) {
_placeHolderImgView = [[UIImageView alloc] init];
_placeHolderImgView.contentMode = UIViewContentModeScaleAspectFit;
}
return _placeHolderImgView;
}
- (instancetype)initWithFrame:(CGRect)frame{
if (self = [super initWithFrame:frame]) {
self.player = [AVPlayer playerWithPlayerItem:_item];
self.playerLayer = [AVPlayerLayer playerLayerWithPlayer:_player];
[self.playerView.layer addSublayer:_playerLayer];
[self.playerBgView addSubview:self.placeHolderImgView];
[self.playerBgView addSubview:self.playerView];
[self addSubview:self.playerBgView];
[self addSubview:self.actionView];
[self addSubview:self.actionBar];
}
return self;
}
- (void)playerOnLinePhotoItems:(KNPhotoItems *)photoItems placeHolder:(UIImage *_Nullable)placeHolder{
[self removePlayerItemObserver];
[self removeTimeObserver];
[self addObserverAndAudioSession];
_url = photoItems.url;
_placeHolder = placeHolder;
if (placeHolder) {
_placeHolderImgView.image = placeHolder;
}
if ([photoItems.url hasPrefix:@"http"]) {
_item = [AVPlayerItem playerItemWithURL:[NSURL URLWithString:photoItems.url]];
}else {
_item = [AVPlayerItem playerItemWithAsset:[AVURLAsset URLAssetWithURL:[NSURL fileURLWithPath:_url] options:nil]];
}
_item.canUseNetworkResourcesForLiveStreamingWhilePaused = true;
[self.player replaceCurrentItemWithPlayerItem:_item];
[_actionView avplayerActionViewNeedHidden:false];
_isEnterBackground = _isAddObserver = _isDragging = _isPlaying = false;
[self addPlayerItemObserver];
/// default rate
_player.rate = 1.0;
[_player pause];
}
- (void)addObserverAndAudioSession{
// AudioSession setting
AVAudioSession *session = [AVAudioSession sharedInstance];
[session setActive:true error:nil];
if(_isSoloAmbient == true) {
[session setCategory:AVAudioSessionCategorySoloAmbient error:nil];
}else {
[session setCategory:AVAudioSessionCategoryAmbient error:nil];
}
// Notification
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationWillResignActive) name:UIApplicationWillResignActiveNotification object:nil];
}
/// notification function
- (void)applicationWillResignActive{
_isEnterBackground = true;
if (_isPlaying) [self photoAVPlayerActionBarClickWithIsPlay:false];
}
/// remove item observer
- (void)removePlayerItemObserver{
if (_item && _isAddObserver) {
[_item removeObserver:self forKeyPath:@"status" context:nil];
[_item removeObserver:self forKeyPath:@"loadedTimeRanges" context:nil];
_isAddObserver = false;
}
}
/// add item observer
- (void)addPlayerItemObserver{
if (_item) {
[_item addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:nil];
[_item addObserver:self forKeyPath:@"loadedTimeRanges" options:NSKeyValueObservingOptionNew context:nil];
_isAddObserver = true;
}
__weak typeof(self) weakself = self;
self.timeObserver = [_player addPeriodicTimeObserverForInterval:CMTimeMake(1, 1) queue:dispatch_get_main_queue() usingBlock:^(CMTime time) {
weakself.isBeginPlayed = true;
__strong typeof(weakself) strongself = weakself;
if (CMTimeGetSeconds(time) == strongself.actionBar.allDuration) {
if (strongself.actionBar.allDuration != 0) {
[strongself videoDidPlayToEndTime];
}
strongself.actionBar.currentTime = 0;
}else{
if (strongself.isDragging == true) {
strongself.isDragging = false;
return;
}
strongself.actionBar.currentTime = CMTimeGetSeconds(time);
}
}];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(videoDidPlayToEndTime)
name:AVPlayerItemDidPlayToEndTimeNotification
object:nil];
}
/// remove time observer
- (void)removeTimeObserver{
if (_timeObserver && _player) {
@try {
[_player removeTimeObserver: _timeObserver];
} @catch (NSException *exception) {
} @finally {
_timeObserver = nil;
}
}
}
- (void)videoDidPlayToEndTime{
_isGetAllPlayItem = false;
_isPlaying = false;
if (_player) {
__weak typeof(self) weakself = self;
if (_player.currentItem.status == AVPlayerStatusReadyToPlay) {
[_player seekToTime:CMTimeMake(1, 1) completionHandler:^(BOOL finished) {
if (finished) {
weakself.actionBar.currentTime = 0;
weakself.actionBar.isPlaying = false;
weakself.actionView.isPlaying = false;
}
}];
}
}
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
if (object != self.item) return;
if (_isEnterBackground) return;
if ([keyPath isEqualToString:@"status"]) { // play
if (_player.currentItem.status == AVPlayerStatusReadyToPlay) {
_actionView.isBuffering = false;
}
}else if ([keyPath isEqualToString:@"loadedTimeRanges"]) { // buffering
if (!_isGetAllPlayItem) {
_isGetAllPlayItem = true;
_actionBar.allDuration = CMTimeGetSeconds(_player.currentItem.duration);
}
} else {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
/// function
- (void)playerWillReset{
[_player pause];
_isPlaying = false;
[self removeTimeObserver];
[self removePlayerItemObserver];
}
- (void)playerWillSwipe{
[_actionView avplayerActionViewNeedHidden:true];
_actionBar.hidden = true;
}
- (void)playerWillSwipeCancel {
}
- (void)playerRate:(CGFloat)rate{
if (_isPlaying == false) {
return;
}
_player.rate = rate;
}
/// setter
- (void)setIsNeedAutoPlay:(BOOL)isNeedAutoPlay {
_isNeedAutoPlay = isNeedAutoPlay;
if (isNeedAutoPlay) {
_actionView.isBuffering = true;
[self photoAVPlayerActionViewPauseOrStop];
}
}
- (void)setIsNeedVideoPlaceHolder:(BOOL)isNeedVideoPlaceHolder{
_isNeedVideoPlaceHolder = isNeedVideoPlaceHolder;
self.placeHolderImgView.hidden = !isNeedVideoPlaceHolder;
}
- (void)photoAVPlayerActionViewDidLongPress:(UILongPressGestureRecognizer *)longPress{
if (_isPlaying == false) {
return;
}
if ([_delegate respondsToSelector:@selector(photoPlayerLongPress:)]) {
[_delegate photoPlayerLongPress:longPress];
}
}
/// delegate
/**
actionView's Pause imageView
*/
- (void)photoAVPlayerActionViewPauseOrStop{
_isEnterBackground = false;
if (_isPlaying == false) {
[_player play];
_actionBar.isPlaying = true;
_actionView.isPlaying = true;
}else {
[_player pause];
_actionView.isPlaying = false;
_actionBar.isPlaying = false;
}
_isPlaying = !_isPlaying;
}
- (void)photoAVPlayerActionViewDismiss{
if ([_delegate respondsToSelector:@selector(photoPlayerViewDismiss)]) {
[_delegate photoPlayerViewDismiss];
}
}
- (void)photoAVPlayerActionViewDidClickIsHidden:(BOOL)isHidden{
[_actionBar setHidden:isHidden];
}
- (void)photoAVPlayerActionBarClickWithIsPlay:(BOOL)isNeedPlay{
if (isNeedPlay) {
[_player play];
_actionView.isPlaying = true;
_actionBar.isPlaying = true;
_isPlaying = true;
}else {
[_player pause];
_actionView.isPlaying = false;
_actionBar.isPlaying = false;
_isPlaying = false;
}
}
- (void)photoAVPlayerActionBarChangeValue:(float)value{
_isDragging = true;
[_player seekToTime:CMTimeMake(value, 1) completionHandler:^(BOOL finished) {
}];
}
- (void)layoutSubviews{
[super layoutSubviews];
self.playerBgView.frame = CGRectMake(10, 0, self.frame.size.width - 20, self.frame.size.height);
self.playerView.frame = self.playerBgView.bounds;
self.playerLayer.frame = self.playerView.bounds;
self.actionView.frame = self.playerBgView.frame;
self.placeHolderImgView.frame = self.playerBgView.bounds;
if (PBDeviceHasBang) {
self.actionBar.frame = CGRectMake(15, self.frame.size.height - 70, self.frame.size.width - 30, 40);
}else {
self.actionBar.frame = CGRectMake(15, self.frame.size.height - 50, self.frame.size.width - 30, 40);
}
}
- (void)dealloc{
[self removeObserverAndAudioSesstion];
if (_player && self.timeObserver) {
[_player removeTimeObserver:self.timeObserver];
self.timeObserver = nil;
}
}
- (void)removeObserverAndAudioSesstion{
[[NSNotificationCenter defaultCenter] removeObserver:self];
[[AVAudioSession sharedInstance] setActive:false withOptions:AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation error:nil];
}
@end

View File

@@ -0,0 +1,95 @@
//
// KNPhotoLocateAVPlayerView.h
// KNPhotoBrowser
//
// Created by LuKane on 2021/5/12.
// Copyright © 2021 LuKane. All rights reserved.
//
#import <UIKit/UIKit.h>
#import <AVFoundation/AVFoundation.h>
#import "KNPhotoPlayerProtocol.h"
#import "KNPhotoBrowser.h"
#import "KNPhotoDownloadMgr.h"
@class KNProgressHUD;
NS_ASSUME_NONNULL_BEGIN
@interface KNPhotoLocateAVPlayerView : UIView
/// create locate player with photoItems
/// @param photoItems photoItems
/// @param progressHUD progressHUD
/// @param placeHolder placeHolder image
- (void)playerLocatePhotoItems:(KNPhotoItems *)photoItems progressHUD:(KNProgressHUD *)progressHUD placeHolder:(UIImage *_Nullable)placeHolder;
/// reset AVPlayer
- (void)playerWillReset;
/// AVPlayer will be swiped by hand
- (void)playerWillSwipe;
/// AVPlayer will cancel swipe
- (void)playerWillSwipeCancel;
/// AVPlayer play as rate
/// @param rate rate
- (void)playerRate:(CGFloat)rate;
/// when dismiss, should cancel download task first
- (void)cancelDownloadMgrTask;
/// playerdownload
/// @param downloadBlock download callBack
- (void)playerDownloadBlock:(PhotoDownLoadBlock)downloadBlock;
/**
* is or not need Video placeHolder
*/
@property (nonatomic,assign) BOOL isNeedVideoPlaceHolder;
/**
* auto play when you need
*/
@property (nonatomic,assign) BOOL isNeedAutoPlay;
/**
* player view
*/
@property (nonatomic,strong,nullable) UIView *playerView;
/**
* player background view (as locate current location for swipe)
*/
@property (nonatomic,strong,nullable) UIView *playerBgView;
/**
* placeHolder imageView
*/
@property (nonatomic,strong,nullable) UIImageView *placeHolderImgView;
/**
* layer of player
*/
@property (nonatomic,strong,nullable) AVPlayerLayer *playerLayer;
/**
* if video has played ,even though one seconds : TRUE
*/
@property (nonatomic,assign) BOOL isBeginPlayed;
/**
* default is solo ambient : TRUE `AVAudioSessionCategorySoloAmbient`
* if set false, that will be `AVAudioSessionCategoryAmbient`
*/
@property (nonatomic, assign) BOOL isSoloAmbient;
/**
* delegate
*/
@property (nonatomic,weak ) id<KNPhotoPlayerViewDelegate> delegate;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,454 @@
//
// KNPhotoLocateAVPlayerView.m
// KNPhotoBrowser
//
// Created by LuKane on 2021/5/12.
// Copyright © 2021 LuKane. All rights reserved.
//
#import "KNPhotoLocateAVPlayerView.h"
#import "KNPhotoAVPlayerActionBar.h"
#import "KNPhotoAVPlayerActionView.h"
#import "KNPhotoBrowserPch.h"
#import "KNProgressHUD.h"
#import "KNReachability.h"
@interface KNPhotoLocateAVPlayerView()<KNPhotoAVPlayerActionViewDelegate,KNPhotoAVPlayerActionBarDelegate>
@property (nonatomic,strong) AVPlayer *player;
@property (nonatomic,strong) AVPlayerItem *item;
@property (nonatomic,strong) KNPhotoAVPlayerActionView *actionView;
@property (nonatomic,strong) KNPhotoAVPlayerActionBar *actionBar;
@property (nonatomic,copy ) NSString *url;
@property (nonatomic,strong) UIImage *placeHolder;
@property (nonatomic,strong) id timeObserver;
@property (nonatomic,assign) BOOL isPlaying;
@property (nonatomic,assign) BOOL isGetAllPlayItem;
@property (nonatomic,assign) BOOL isDragging;
@property (nonatomic,assign) BOOL isEnterBackground;
@property (nonatomic,assign) BOOL isAddObserver;
@property (nonatomic,strong) KNPhotoDownloadMgr *downloadMgr;
@property (nonatomic,strong) KNPhotoItems *photoItems;
@property (nonatomic,weak ) KNProgressHUD *progressHUD;
@property (nonatomic,copy ) PhotoDownLoadBlock downloadBlock;
@end
@implementation KNPhotoLocateAVPlayerView
- (KNPhotoAVPlayerActionView *)actionView{
if (!_actionView) {
_actionView = [[KNPhotoAVPlayerActionView alloc] init];
[_actionView addGestureRecognizer:[[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(photoAVPlayerActionViewDidLongPress:)]];
_actionView.delegate = self;
_actionView.isBuffering = false;
_actionView.isPlaying = false;
}
return _actionView;
}
- (KNPhotoAVPlayerActionBar *)actionBar{
if (!_actionBar) {
_actionBar = [[KNPhotoAVPlayerActionBar alloc] init];
_actionBar.backgroundColor = [UIColor colorWithRed:45/255.0 green:45/255.0 blue:45/255.0 alpha:1.];
_actionBar.delegate = self;
_actionBar.isPlaying = false;
_actionBar.hidden = true;
}
return _actionBar;
}
- (UIView *)playerBgView{
if (!_playerBgView) {
_playerBgView = [[UIView alloc] init];
}
return _playerBgView;
}
- (UIView *)playerView{
if (!_playerView) {
_playerView = [[UIView alloc] init];
_playerView.backgroundColor = UIColor.clearColor;
}
return _playerView;
}
- (UIImageView *)placeHolderImgView{
if (!_placeHolderImgView) {
_placeHolderImgView = [[UIImageView alloc] init];
_placeHolderImgView.contentMode = UIViewContentModeScaleAspectFit;
}
return _placeHolderImgView;
}
- (instancetype)initWithFrame:(CGRect)frame{
if (self = [super initWithFrame:frame]) {
self.player = [AVPlayer playerWithPlayerItem:_item];
self.playerLayer = [AVPlayerLayer playerLayerWithPlayer:_player];
[self.playerView.layer addSublayer:_playerLayer];
[self.playerBgView addSubview:self.placeHolderImgView];
[self.playerBgView addSubview:self.playerView];
[self addSubview:self.playerBgView];
[self addSubview:self.actionView];
[self addSubview:self.actionBar];
_downloadBlock = nil;
}
return self;
}
- (void)playerLocatePhotoItems:(KNPhotoItems *)photoItems progressHUD:(KNProgressHUD *)progressHUD placeHolder:(UIImage *_Nullable)placeHolder{
[self cancelDownloadMgrTask];
[self removePlayerItemObserver];
[self removeTimeObserver];
[self addObserverAndAudioSession];
_downloadBlock = nil;
_url = photoItems.url;
_placeHolder = placeHolder;
_progressHUD = progressHUD;
_photoItems = photoItems;
_downloadMgr = [[KNPhotoDownloadMgr alloc] init];
if (placeHolder) {
_placeHolderImgView.image = placeHolder;
}
_item = nil;
if ([photoItems.url hasPrefix:@"http"]) {
KNPhotoDownloadFileMgr *fileMgr = [[KNPhotoDownloadFileMgr alloc] init];
if ([fileMgr startCheckIsExistVideo:photoItems]) {
progressHUD.hidden = true;
_actionView.isBuffering = true;
NSString *filePath = [fileMgr startGetFilePath:photoItems];
_item = [AVPlayerItem playerItemWithAsset:[AVURLAsset URLAssetWithURL:[NSURL fileURLWithPath:filePath] options:nil]];
}
}else {
progressHUD.hidden = true;
_actionView.isBuffering = true;
_item = [AVPlayerItem playerItemWithAsset:[AVURLAsset URLAssetWithURL:[NSURL fileURLWithPath:_url] options:nil]];
}
_item.canUseNetworkResourcesForLiveStreamingWhilePaused = true;
[self.player replaceCurrentItemWithPlayerItem:_item];
[_actionView avplayerActionViewNeedHidden:false];
_isEnterBackground = _isAddObserver = _isDragging = _isPlaying = false;
if (_item != nil) [self addPlayerItemObserver];
/// default rate
_player.rate = 1.0;
[_player pause];
}
- (void)addObserverAndAudioSession{
// AudioSession setting
AVAudioSession *session = [AVAudioSession sharedInstance];
[session setActive:true error:nil];
if(_isSoloAmbient == true) {
[session setCategory:AVAudioSessionCategorySoloAmbient error:nil];
}else {
[session setCategory:AVAudioSessionCategoryAmbient error:nil];
}
// Notification
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationWillResignActive) name:UIApplicationWillResignActiveNotification object:nil];
}
/// notification function
- (void)applicationWillResignActive{
_isEnterBackground = true;
if (_isPlaying) [self photoAVPlayerActionBarClickWithIsPlay:false];
}
/// remove item observer
- (void)removePlayerItemObserver{
if (_item && _isAddObserver) {
[_item removeObserver:self forKeyPath:@"status" context:nil];
[_item removeObserver:self forKeyPath:@"loadedTimeRanges" context:nil];
_isAddObserver = false;
}
}
/// add item observer
- (void)addPlayerItemObserver{
if (_item) {
[_item addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:nil];
[_item addObserver:self forKeyPath:@"loadedTimeRanges" options:NSKeyValueObservingOptionNew context:nil];
_isAddObserver = true;
}
__weak typeof(self) weakself = self;
self.timeObserver = [_player addPeriodicTimeObserverForInterval:CMTimeMake(1, 1) queue:dispatch_get_main_queue() usingBlock:^(CMTime time) {
weakself.isBeginPlayed = true;
__strong typeof(weakself) strongself = weakself;
if (CMTimeGetSeconds(time) == strongself.actionBar.allDuration) {
if (strongself.actionBar.allDuration != 0) {
[strongself videoDidPlayToEndTime];
}
strongself.actionBar.currentTime = 0;
}else{
if (strongself.isDragging == true) {
strongself.isDragging = false;
return;
}
strongself.actionBar.currentTime = CMTimeGetSeconds(time);
}
}];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(videoDidPlayToEndTime)
name:AVPlayerItemDidPlayToEndTimeNotification
object:nil];
}
/// remove time observer
- (void)removeTimeObserver{
if (_timeObserver && _player) {
@try {
[_player removeTimeObserver: _timeObserver];
} @catch (NSException *exception) {
} @finally {
_timeObserver = nil;
}
}
}
- (void)videoDidPlayToEndTime{
_isGetAllPlayItem = false;
_isPlaying = false;
if (_player) {
__weak typeof(self) weakself = self;
if (_player.currentItem.status == AVPlayerStatusReadyToPlay) {
[_player seekToTime:CMTimeMake(1, 1) completionHandler:^(BOOL finished) {
if (finished) {
weakself.actionBar.currentTime = 0;
weakself.actionBar.isPlaying = false;
weakself.actionView.isPlaying = false;
}
}];
}
}
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
if (object != self.item) return;
if (_isEnterBackground) return;
if ([keyPath isEqualToString:@"status"]) { // play
if (_player.currentItem.status == AVPlayerStatusReadyToPlay) {
_placeHolderImgView.hidden = true;
}
_actionView.isBuffering = false;
}else if ([keyPath isEqualToString:@"loadedTimeRanges"]) { // buffering
if (!_isGetAllPlayItem) {
_isGetAllPlayItem = true;
_actionBar.allDuration = CMTimeGetSeconds(_player.currentItem.duration);
}
_actionView.isBuffering = false;
} else {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
/// function
- (void)playerWillReset{
[_player pause];
_isPlaying = false;
[self removeTimeObserver];
[self removePlayerItemObserver];
}
- (void)playerWillSwipe{
[_actionView avplayerActionViewNeedHidden:true];
_actionBar.hidden = true;
_progressHUD.hidden = true;
}
/// AVPlayer will cancel swipe
- (void)playerWillSwipeCancel{
KNPhotoDownloadFileMgr *fileMgr = [[KNPhotoDownloadFileMgr alloc] init];
if ([self.photoItems.url hasPrefix:@"http"]) {
if ([fileMgr startCheckIsExistVideo:self.photoItems] == false && _progressHUD.progress != 1.0) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(PhotoBrowserAnimateTime * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
self->_progressHUD.hidden = false;
});
}else {
_progressHUD.hidden = true;
}
}else {
_progressHUD.hidden = true;
}
}
- (void)playerRate:(CGFloat)rate{
if (_isPlaying == false) {
return;
}
_player.rate = rate;
}
/// when dismiss, should cancel download task first
- (void)cancelDownloadMgrTask{
if (_downloadMgr) [_downloadMgr cancelTask];
}
/// playerdownload
/// @param downloadBlock download callBack
- (void)playerDownloadBlock:(PhotoDownLoadBlock)downloadBlock{
_downloadBlock = downloadBlock;
}
/// setter
- (void)setIsNeedAutoPlay:(BOOL)isNeedAutoPlay {
_isNeedAutoPlay = isNeedAutoPlay;
if (isNeedAutoPlay) {
[self photoAVPlayerActionViewPauseOrStop];
}
}
- (void)setIsNeedVideoPlaceHolder:(BOOL)isNeedVideoPlaceHolder{
_isNeedVideoPlaceHolder = isNeedVideoPlaceHolder;
self.placeHolderImgView.hidden = !isNeedVideoPlaceHolder;
}
- (void)photoAVPlayerActionViewDidLongPress:(UILongPressGestureRecognizer *)longPress{
if (_isPlaying == false) {
return;
}
if ([_delegate respondsToSelector:@selector(photoPlayerLongPress:)]) {
[_delegate photoPlayerLongPress:longPress];
}
}
/// delegate
/**
actionView's Pause imageView
*/
- (void)photoAVPlayerActionViewPauseOrStop{
KNPhotoDownloadFileMgr *fileMgr = [[KNPhotoDownloadFileMgr alloc] init];
if ([_photoItems.url hasPrefix:@"http"] == true && [fileMgr startCheckIsExistVideo:_photoItems] == false) {
if (![[KNReachability reachabilityForInternetConnection] isReachable]) { // no network
[_progressHUD setHidden:true];
return;
}
_actionView.isDownloading = true;
[_progressHUD setHidden:false];
[_progressHUD setProgress:0.0];
__weak typeof(self) weakself = self;
[_downloadMgr downloadVideoWithPhotoItems:_photoItems downloadBlock:^(KNPhotoDownloadState downloadState, float progress) {
[weakself.progressHUD setProgress:progress];
if (downloadState == KNPhotoDownloadStateSuccess) {
dispatch_async(dispatch_get_main_queue(), ^{
KNPhotoDownloadFileMgr *manager = [[KNPhotoDownloadFileMgr alloc] init];
NSString *filePath = [manager startGetFilePath:weakself.photoItems];
weakself.item = [AVPlayerItem playerItemWithAsset:[AVURLAsset URLAssetWithURL:[NSURL fileURLWithPath:filePath] options:nil]];
weakself.item.canUseNetworkResourcesForLiveStreamingWhilePaused = true;
[weakself.player replaceCurrentItemWithPlayerItem:weakself.item];
[weakself.player play];
weakself.player.muted = true;
weakself.progressHUD.progress = 1.0;
weakself.isPlaying = true;
weakself.actionBar.isPlaying = true;
weakself.actionView.isBuffering = true;
weakself.actionView.isPlaying = true;
[weakself addPlayerItemObserver];
});
}
if (downloadState == KNPhotoDownloadStateUnknow || downloadState == KNPhotoDownloadStateFailure) {
[weakself.progressHUD setProgress:0.0];
}
if (weakself.downloadBlock) {
weakself.downloadBlock(downloadState, progress);
}
}];
}else {
_progressHUD.hidden = true;
if (_isPlaying == false) {
[_player play];
_player.muted = true;
_actionBar.isPlaying = true;
_actionView.isPlaying = true;
}else {
[_player pause];
_actionView.isPlaying = false;
_actionBar.isPlaying = false;
}
_isPlaying = !_isPlaying;
}
_isEnterBackground = false;
}
- (void)photoAVPlayerActionViewDismiss{
[self cancelDownloadMgrTask];
if ([_delegate respondsToSelector:@selector(photoPlayerViewDismiss)]) {
[_delegate photoPlayerViewDismiss];
}
}
- (void)photoAVPlayerActionViewDidClickIsHidden:(BOOL)isHidden{
[_actionBar setHidden:isHidden];
}
- (void)photoAVPlayerActionBarClickWithIsPlay:(BOOL)isNeedPlay{
if (isNeedPlay) {
[_player play];
_player.muted = true;
_actionView.isPlaying = true;
_actionBar.isPlaying = true;
_isPlaying = true;
}else {
[_player pause];
_actionView.isPlaying = false;
_actionBar.isPlaying = false;
_isPlaying = false;
}
}
- (void)photoAVPlayerActionBarChangeValue:(float)value{
_isDragging = true;
[_player seekToTime:CMTimeMake(value, 1) completionHandler:^(BOOL finished) {
}];
}
- (void)layoutSubviews{
[super layoutSubviews];
self.playerBgView.frame = CGRectMake(10, 0, self.frame.size.width - 20, self.frame.size.height);
self.playerView.frame = self.playerBgView.bounds;
self.playerLayer.frame = self.playerView.bounds;
self.actionView.frame = self.playerBgView.frame;
self.placeHolderImgView.frame = self.playerBgView.bounds;
if (PBDeviceHasBang) {
self.actionBar.frame = CGRectMake(15, self.frame.size.height - 70, self.frame.size.width - 30, 40);
}else {
self.actionBar.frame = CGRectMake(15, self.frame.size.height - 50, self.frame.size.width - 30, 40);
}
}
- (void)dealloc{
[self removeObserverAndAudioSesstion];
if (_player && self.timeObserver) {
[_player removeTimeObserver:self.timeObserver];
self.timeObserver = nil;
}
}
- (void)removeObserverAndAudioSesstion{
[[NSNotificationCenter defaultCenter] removeObserver:self];
[[AVAudioSession sharedInstance] setActive:false withOptions:AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation error:nil];
}
@end

View File

@@ -0,0 +1,26 @@
//
// KNPhotoPlayerProtocol.h
// KNPhotoBrowser
//
// Created by LuKane on 2021/5/12.
// Copyright © 2021 LuKane. All rights reserved.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@protocol KNPhotoPlayerViewDelegate <NSObject>
/**
avplayer dimiss
*/
- (void)photoPlayerViewDismiss;
/**
avplayer long press
*/
- (void)photoPlayerLongPress:(UILongPressGestureRecognizer *)longPress;
@end
NS_ASSUME_NONNULL_END

Binary file not shown.

After

Width:  |  Height:  |  Size: 799 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -0,0 +1,271 @@
//
// KNPhotoBrowser.h
// KNPhotoBrowser
//
// Created by LuKane on 16/8/18.
// Copyright © 2016年 LuKane. All rights reserved.
//
/**
* 如果 bug ,希望各位在 github 上通过'邮箱' 或者直接 issue 指出, 谢谢
* github地址 : https://github.com/LuKane/KNPhotoBrowser
* 项目会越来越丰富,也希望大家一起来增加功能 , 欢迎 Star
*/
#import <UIKit/UIKit.h>
#import <AVFoundation/AVFoundation.h>
#import "KNActionSheet.h"
@class KNPhotoBrowser;
NS_ASSUME_NONNULL_BEGIN
typedef NS_ENUM(NSInteger, KNPhotoDownloadState) {
KNPhotoDownloadStateUnknow,
KNPhotoDownloadStateDownloading,
KNPhotoDownloadStateSuccess,
KNPhotoDownloadStateFailure,
KNPhotoDownloadStateRepeat,
KNPhotoDownloadStateSaveFailure,
KNPhotoDownloadStateSaveSuccess
};
/****************************** == KNPhotoItems == ********************************/
@interface KNPhotoItems : NSObject
/// if it is network image or (net or locate video), set `url` , do not set `sourceImage`
@property (nonatomic,copy ) NSString *url;
/// if it is locate image, set `sourceImage`, do not set `url`
@property (nonatomic,strong) UIImage *sourceImage;
/// is locate gif image or not, default is false.
/// if is locate gif image, set it true.
/// if is network gif image or video, do not set it
@property (nonatomic,assign) BOOL isLocateGif;
/// sourceView is current control to show image or video.
/// 1. if the sourceView is kind of `UIImageView` or `UIButton` , just only only only set the `sourceView`.
/// 2. if the sourceView is the custom view , set the `sourceView`, but do not forget set `sourceLinkArr` && `sourceLinkProperyName`.
@property (nonatomic,strong) UIView *sourceView;
/**
eg:
CustomSourceView2 *superV = [[CustomSourceView2 alloc] init];
[xxxView addsubView: superV];
CustomSourceImageView2 *superV1 = [[CustomSourceImageView2 alloc] init];
[superV addsubView: superV1];
SDAnimatedImageView *imgV = [[SDAnimatedImageView alloc] init];
[superV1 addsubView: imgV];
NSMutableArray *arr = [NSMutableArray array];
[arr addObject:@"CustomSourceImageView2"];
[arr addObject:@"SDAnimatedImageView"];
items.sourceLinkArr = [arr copy];
*/
/// Class of sourceView' s subview (if set sourceLinkArr , then must set sourceLinkProperyName when it's not `UIImageView` or `UIButton`)
@property (nonatomic,strong) NSArray<NSString *> *sourceLinkArr;
/**
eg:
if the lastObject is kind of UIImageView , the `sourceLinkProperyName` is `image`
if the lastObject is kind of UIButton , the `sourceLinkProperyName` is `currentBackgroundImage` or `currentImage`
*/
/// the property'name of the sourceLinkArr lastObject
@property (nonatomic,copy ) NSString *sourceLinkProperyName;
/// is video or not, defalut is false
@property (nonatomic,assign) BOOL isVideo;
/// when `isVideo` is true, and the video is net type, try to set videoPlaceHolderImageUrl, it is like the placeHolderImage of the net video
@property (nonatomic,copy ) NSString *videoPlaceHolderImageUrl;
/// video is downloading or other state, default is unknow
@property (nonatomic,assign) KNPhotoDownloadState downloadState;
/// video is downloading, current progress
@property (nonatomic,assign) float downloadProgress;
@end
@interface UIDevice(PBExtension)
/// device judge did have auth of Album
/// @param authorBlock callBack
+ (void)deviceAlbumAuth:(void (^)(BOOL isAuthor))authorBlock;
/// device shake
+ (void)deviceShake;
@end
/****************************** == KNPhotoBrowserDelegate == ********************************/
@protocol KNPhotoBrowserDelegate <NSObject>
@optional
/// photoBrowser will dismiss with currentIndex
/// @param photoBrowser browser
/// @param index current index
- (void)photoBrowser:(KNPhotoBrowser *)photoBrowser willDismissWithIndex:(NSInteger)index;
/// photoBrowser right top button did click with currentIndex (you can custom you right button, but if you custom your right button, that you need implementate your target action)
/// @param photoBrowser browser
/// @param index current index
- (void)photoBrowser:(KNPhotoBrowser *)photoBrowser rightBtnOperationActionWithIndex:(NSInteger)index;
/// photoBrowser image long press (image or gif) with currentIndex
/// @param photoBrowser photoBrowser
/// @param index current index
- (void)photoBrowser:(KNPhotoBrowser *)photoBrowser imageLongPressWithIndex:(NSInteger)index;
/// photoBrowser remove image or video source with relative index
/// @param photoBrowser browser
/// @param relativeIndex relative index
- (void)photoBrowser:(KNPhotoBrowser *)photoBrowser removeSourceWithRelativeIndex:(NSInteger)relativeIndex;
/// photoBrowser remove image or video source with absolute index
/// @param photoBrowser browser
/// @param absoluteIndex absolute index
- (void)photoBrowser:(KNPhotoBrowser *)photoBrowser removeSourceWithAbsoluteIndex:(NSInteger)absoluteIndex;
/// photoBrowser scroll to current Index
/// @param photoBrowser browser
/// @param index current index
- (void)photoBrowser:(KNPhotoBrowser *)photoBrowser scrollToLocateWithIndex:(NSInteger)index;
/// photoBrowser did long press with gestureRecognizer and index
/// @param photoBrowser browser
/// @param longPress gesture Recognize
/// @param index current index
- (void)photoBrowser:(KNPhotoBrowser *)photoBrowser videoLongPress:(UILongPressGestureRecognizer *)longPress index:(NSInteger)index;
/// download image or video success or failure or failure reason call back. [If video player is download by auto, it can use delegate. it is only use function `removeImageOrVideoOnPhotoBrowser` can use this delegate]
/// @param photoBrowser photoBrowser
/// @param state state
/// @param progress progress
/// @param photoItemRe relative photoItem
/// @param photoItemAb absolute photoItem
- (void)photoBrowser:(KNPhotoBrowser *)photoBrowser
state:(KNPhotoDownloadState)state
progress:(float)progress
photoItemRelative:(KNPhotoItems *)photoItemRe
photoItemAbsolute:(KNPhotoItems *)photoItemAb;
/// photoBrowser will layout subviews
- (void)photoBrowserWillLayoutSubviews;
@end
/****************************** == KNPhotoBrowser == ********************************/
@interface KNPhotoBrowser : UIViewController
/// current Index
@property (nonatomic,assign) NSInteger currentIndex;
/// itemsArr contain KNPhotoItems : url | sourceView.....
@property (nonatomic,strong) NSArray<KNPhotoItems *> *itemsArr;
/// delegate
@property (nonatomic,weak ) id<KNPhotoBrowserDelegate> delegate;
/// image' control animated mode , default is `UIViewContentModeScaleToFill`
@property (nonatomic,assign) UIViewContentMode animatedMode;
/// image' control presented mode , default is `UIViewContentModeScaleAspectFit`
@property (nonatomic,assign) UIViewContentMode presentedMode;
/// when source image && image && video is not ready, create one image with color to holder, default is UIColor.clear
@property (nonatomic,strong) UIColor *placeHolderColor;
/// is or not need pageNumView , default is false
@property (nonatomic,assign) BOOL isNeedPageNumView;
/// is or not need pageControl , default is false (but if photobrowser contain video,then hidden)
@property (nonatomic,assign) BOOL isNeedPageControl;
/// is or not need RightTopBtn , default is false
@property (nonatomic,assign) BOOL isNeedRightTopBtn;
/// is or not need image or video longPress , default is false.
/// image long press : delegate function `photoBrowser: imageLongPressWithIndex:`.
/// video long press : delegate function `photoBrowser: videoLongPress: index:`.
@property (nonatomic,assign) BOOL isNeedLongPress;
/// is or not need prefetch image, maxCount is 8 (KNPhotoBrowserPch.h)
@property (nonatomic,assign) BOOL isNeedPrefetch;
/// is or not need pan Gesture, default is false
@property (nonatomic,assign) BOOL isNeedPanGesture;
/// is or not need auto play video, default is false
@property (nonatomic,assign) BOOL isNeedAutoPlay;
/// is or not need online play video, default is false [That means auto download video first]
@property (nonatomic,assign) BOOL isNeedOnlinePlay;
/// is or not solo ambient, default is true `AVAudioSessionCategorySoloAmbient`. If set false ,that will be `AVAudioSessionCategoryAmbient`
@property (nonatomic,assign) BOOL isSoloAmbient;
/// the `numView` & `pageControl` & `operationBtn` is or not need follow photoBrowser , default is false.
/// when touch photoBrowser, they will be hidden.
/// when you cancel, they will be showed.
/// when dismiss the photoBrowser immediately, they will be hidden immediately.
@property (nonatomic,assign) BOOL isNeedFollowAnimated;
/// remove current image or video on photobrowser
- (void)removeImageOrVideoOnPhotoBrowser;
/// download photo or video to Album, but it must be authed at first
- (void)downloadImageOrVideoToAlbum;
/// player's rate immediately to use, default is 1.0 , range is [0.5 <= rate <= 2.0]
- (void)setImmediatelyPlayerRate:(CGFloat)rate;
/**
you can use the next function, use the `- (void)createOverlayViewArrOnTopView: animated: followAnimated:`
*/
- (void)createCustomViewArrOnTopView:(NSArray<UIView *> *)customViewArr
animated:(BOOL)animated
followAnimated:(BOOL)followAnimated;
/**
create overlay view on the topView(photoBrowser controller's view)
for example: create a scrollView on the photoBrowser controller's view, when photoBrowser has scrolled , you can use delegate's function to do something you want
delegate's function: 'photoBrowser:scrollToLocateWithIndex:(NSInteger)index'
'CustomViewController' in Demo, you can see it how to use
@param overlayViewArr overlayViewArr
@param animated need animated or not, with photoBrowser present
@param followAnimated need animated or not for follow photoBrowser
*/
- (void)createOverlayViewArrOnTopView:(NSArray<UIView *> *)overlayViewArr
animated:(BOOL)animated
followAnimated:(BOOL)followAnimated;
/// photoBrowser will present.
/// if `which is already presenting`, use the `- (void)present:(UIViewController *)controller` to instead
- (void)present;
/*
by the way , you alse can present as you wish, like:
[controller presentViewController:photoBrowser animated:false completion:^{
}];
*/
/// photoBrowser will present base on current controller
/// @param controller current controller
- (void)presentOn:(UIViewController *)controller;
/// photoBrowser dismiss
- (void)dismiss;
@end
NS_ASSUME_NONNULL_END

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,40 @@
//
// KNPhotoBrowserImageView.h
// KNPhotoBrowser
//
// Created by LuKane on 16/8/17.
// Copyright © 2016年 LuKane. All rights reserved.
//
#import <UIKit/UIKit.h>
#import <SDWebImage/SDAnimatedImageView.h>
@class KNProgressHUD;
@class KNPhotoItems;
typedef void(^PhotoBrowerSingleTap)(void);
typedef void(^PhotoBrowerLongPressTap)(void);
@interface KNPhotoBrowserImageView : UIView
// all base control that can scroll
@property (nonatomic, strong) UIScrollView *scrollView;
@property (nonatomic, strong) SDAnimatedImageView *imageView;
// single tap
@property (nonatomic,copy ) PhotoBrowerSingleTap singleTap;
// longPress tap
@property (nonatomic,copy ) PhotoBrowerLongPressTap longPressTap;
/// set image with url
/// @param url url
/// @param progressHUD progressHUD
/// @param placeHolder placeHolder image
/// @param photoItem current photoItems
- (void)imageWithUrl:(NSURL *)url
progressHUD:(KNProgressHUD *)progressHUD
placeHolder:(UIImage *)placeHolder
photoItem:(KNPhotoItems *)photoItem;
@end

View File

@@ -0,0 +1,263 @@
//
// KNPhotoBrowserImageView.m
// KNPhotoBrowser
//
// Created by LuKane on 16/8/17.
// Copyright © 2016 LuKane. All rights reserved.
//
#import "KNPhotoBrowserImageView.h"
#import <SDWebImage/UIImageView+WebCache.h>
#import <SDWebImage/SDImageCache.h>
#import "KNProgressHUD.h"
#import "KNPhotoBrowserPch.h"
#import "KNPhotoBrowser.h"
#import "KNReachability.h"
@interface KNPhotoBrowserImageView()<UIScrollViewDelegate>{
NSURL *_url;
UIImage *_placeHolder;
}
@end
@implementation KNPhotoBrowserImageView
- (SDAnimatedImageView *)imageView{
if (!_imageView) {
_imageView = [[SDAnimatedImageView alloc] init];
[_imageView setUserInteractionEnabled:true];
[_imageView setFrame:CGRectMake(0, 0, self.bounds.size.width, self.bounds.size.height)];
_imageView.layer.cornerRadius = 0.1;
_imageView.clipsToBounds = true;
}
return _imageView;
}
- (UIScrollView *)scrollView{
if (!_scrollView) {
_scrollView = [[UIScrollView alloc] init];
[_scrollView setFrame:CGRectMake(0, 0, self.bounds.size.width, self.bounds.size.height)];
[_scrollView addSubview:self.imageView];
[_scrollView setDelegate:self];
[_scrollView setClipsToBounds:true];
}
return _scrollView;
}
- (instancetype)initWithFrame:(CGRect)frame{
if (self = [super initWithFrame:frame]) {
[self addSubview:self.scrollView];
[self initDefaultData];
if (@available(iOS 11.0, *)){
_scrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
}
}
return self;
}
- (void)initDefaultData{
// 1.tap && doubleTap && longpress
UITapGestureRecognizer *tap =
[[UITapGestureRecognizer alloc] initWithTarget:self
action:@selector(scrollViewDidTap)];
UITapGestureRecognizer *doubleTap =
[[UITapGestureRecognizer alloc] initWithTarget:self
action:@selector(scrollViewDidDoubleTap:)];
UILongPressGestureRecognizer *longPress =
[[UILongPressGestureRecognizer alloc] initWithTarget:self
action:@selector(longPressDidPress:)];
// 2.set gesture require
[tap setNumberOfTapsRequired:1];
[tap setNumberOfTouchesRequired:1];
[doubleTap setNumberOfTapsRequired:2];
[doubleTap setNumberOfTouchesRequired:1];
// 3.conflict resolution
[tap requireGestureRecognizerToFail:doubleTap];
// 4.add gesture
[self addGestureRecognizer:tap];
[self addGestureRecognizer:doubleTap];
[self addGestureRecognizer:longPress];
}
#pragma mark -
- (void)scrollViewDidTap{
if(_singleTap){
_singleTap();
}
}
#pragma mark -
- (void)longPressDidPress:(UILongPressGestureRecognizer *)longPress{
if(longPress.state == UIGestureRecognizerStateBegan){
if(_longPressTap){
_longPressTap();
}
}
}
#pragma mark -
- (void)scrollViewDidDoubleTap:(UITapGestureRecognizer *)doubleTap{
// if image is download, if not ,just return;
if(!_imageView.image) return;
if(_scrollView.zoomScale <= 1){
// 1.catch the postion of the gesture
// 2.contentOffset.x of scrollView + location x of gesture
CGFloat x = [doubleTap locationInView:self].x + _scrollView.contentOffset.x;
// 3.contentOffset.y + location y of gesture
CGFloat y = [doubleTap locationInView:self].y + _scrollView.contentOffset.y;
[_scrollView zoomToRect:(CGRect){{x,y},CGSizeZero} animated:true];
}else{
// set scrollView zoom to original
[_scrollView setZoomScale:1.f animated:true];
}
}
- (void)imageWithUrl:(NSURL *)url
progressHUD:(KNProgressHUD *)progressHUD
placeHolder:(UIImage *)placeHolder
photoItem:(KNPhotoItems *)photoItem{
[progressHUD setHidden:true];
_url = url;
_placeHolder = placeHolder;
if(!url){
if (photoItem.isLocateGif == true) {
[_imageView setImage:photoItem.sourceImage];
}else {
[_imageView setImage:placeHolder];
}
[self layoutSubviews];
return;
}
if (![[KNReachability reachabilityForInternetConnection] isReachable]) { // no network
[progressHUD setHidden:true];
}else {
[progressHUD setHidden:false];
}
__weak typeof(self) weakSelf = self;
UIImage *image = [[SDImageCache sharedImageCache] imageFromCacheForKey:[url absoluteString]];
if(image){
[progressHUD setHidden:true];
}
// SDWebImage download image
[_imageView sd_setImageWithURL:url placeholderImage:placeHolder options:SDWebImageRetryFailed progress:^(NSInteger receivedSize, NSInteger expectedSize, NSURL * _Nullable targetURL) {
CGFloat progress = ((CGFloat)receivedSize / expectedSize);
dispatch_async(dispatch_get_main_queue(), ^{
if(progressHUD){
progressHUD.progress = progress;
}
});
} completed:^(UIImage * _Nullable image, NSError * _Nullable error, SDImageCacheType cacheType, NSURL * _Nullable imageURL) {
[self->_scrollView setZoomScale:1.f animated:true];
if(!error){
[progressHUD setProgress:1.f];
[weakSelf layoutSubviews];
}else{
[progressHUD setHidden:true];
}
}];
[self layoutSubviews];
}
- (void)layoutSubviews{
[super layoutSubviews];
_scrollView.frame = CGRectMake(10, 0, self.bounds.size.width - 20, self.bounds.size.height);
[self reloadFrames];
}
- (void)reloadFrames{
CGRect frame = _scrollView.frame;
if(_imageView.image){
CGSize imageSize = _imageView.image.size;
CGRect imageFrame = CGRectMake(0, 0, imageSize.width, imageSize.height);
// if scrollView.width <= height : that means Screen is not landscap
if (frame.size.width <= frame.size.height) {
// let width of the image set as width of scrollView, height become radio
CGFloat ratio = frame.size.width / imageFrame.size.width;
imageFrame.size.height = imageFrame.size.height * ratio;
imageFrame.size.width = frame.size.width;
}else{
if (frame.size.width / frame.size.height <= imageSize.width / imageSize.height) {
imageFrame.size.width = frame.size.width;
imageFrame.size.height = (frame.size.width / imageSize.width) * imageSize.height;
}else {
// let width of the image set as width of scrollView, height become radio
CGFloat ratio = frame.size.height / imageFrame.size.height;
imageFrame.size.width = imageFrame.size.width * ratio;
imageFrame.size.height = frame.size.height;
}
}
[_imageView setFrame:(CGRect){CGPointZero,imageFrame.size}];
// set scrollView contentsize
_scrollView.contentSize = _imageView.frame.size;
// set scrollView.contentsize as image.size , and get center of the image
_imageView.center = [self centerOfScrollViewContent:_scrollView];
// get the radio of scrollView.height and image.height
CGFloat maxScale = frame.size.height / imageFrame.size.height;
// get radio of the width
CGFloat widthRadit = frame.size.width / imageFrame.size.width;
// get the max radio
maxScale = widthRadit > maxScale?widthRadit:maxScale;
// if the max radio >= PhotoBrowerImageMaxScale, get max radio , else PhotoBrowerImageMaxScale
maxScale = maxScale > 2 ? maxScale:2;
// set max and min radio of scrollView
_scrollView.minimumZoomScale = 1;
_scrollView.maximumZoomScale = maxScale;
// set scrollView zoom original
_scrollView.zoomScale = 1.0f;
}else{
frame.origin = CGPointZero;
_imageView.frame = frame;
_scrollView.contentSize = _imageView.bounds.size;
}
_scrollView.contentOffset = CGPointZero;
}
- (CGPoint)centerOfScrollViewContent:(UIScrollView *)scrollView{
// scrollView.bounds.size.width > scrollView.contentSize.width :that means scrollView.size > image.size
CGFloat offsetX = (scrollView.bounds.size.width > scrollView.contentSize.width)?
(scrollView.bounds.size.width - scrollView.contentSize.width) * 0.5 : 0.0;
CGFloat offsetY = (scrollView.bounds.size.height > scrollView.contentSize.height)?
(scrollView.bounds.size.height - scrollView.contentSize.height) * 0.5 : 0.0;
CGPoint actualCenter = CGPointMake(scrollView.contentSize.width * 0.5 + offsetX,
scrollView.contentSize.height * 0.5 + offsetY);
return actualCenter;
}
#pragma mark UIScrollViewDelegate
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView{
// zoom the subviews of the scrollView
return self.imageView;
}
- (void)scrollViewDidZoom:(UIScrollView *)scrollView{
// reset the center of image when dragging everytime
_imageView.center = [self centerOfScrollViewContent:scrollView];
}
@end

View File

@@ -0,0 +1,18 @@
//
// KNPhotoBrowserNumView.h
// KNPhotoBrowser
//
// Created by LuKane on 16/9/2.
// Copyright © 2016年 LuKane. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface KNPhotoBrowserNumView : UILabel
- (void)setCurrentNum:(NSInteger)currentNum totalNum:(NSInteger)totalNum;
@property (nonatomic, assign) NSInteger currentNum;
@property (nonatomic, assign) NSInteger totalNum;
@end

View File

@@ -0,0 +1,42 @@
//
// KNPhotoBrowserNumView.m
// KNPhotoBrowser
//
// Created by LuKane on 16/9/2.
// Copyright © 2016 LuKane. All rights reserved.
//
#import "KNPhotoBrowserNumView.h"
@implementation KNPhotoBrowserNumView
- (instancetype)init{
if (self = [super init]) {
[self setFont:[UIFont boldSystemFontOfSize:20]];
[self setTextColor:[UIColor whiteColor]];
[self setTextAlignment:NSTextAlignmentCenter];
}
return self;
}
- (void)setCurrentNum:(NSInteger)currentNum totalNum:(NSInteger)totalNum{
_currentNum = currentNum;
_totalNum = totalNum;
[self changeText];
}
- (void)changeText{
self.text = [NSString stringWithFormat:@"%zd / %zd",_currentNum,_totalNum];
}
- (void)setCurrentNum:(NSInteger)currentNum{
_currentNum = currentNum;
[self changeText];
}
- (void)setTotalNum:(NSInteger)totalNum{
_totalNum = totalNum;
[self changeText];
}
@end

View File

@@ -0,0 +1,51 @@
//
// KNPhotoBrowser.h
// KNPhotoBrowser
//
// Created by LuKane on 16/8/18.
// Copyright © 2016年 LuKane. All rights reserved.
//
/**
* 如果 bug ,希望各位在 github 上通过'邮箱' 或者直接 issue 指出, 谢谢
* github地址 : https://github.com/LuKane/KNPhotoBrowser
* 项目会越来越丰富,也希望大家一起来增加功能 , 欢迎 Star
*/
#ifndef ScreenWidth
#define ScreenWidth [UIScreen mainScreen].bounds.size.width
#endif
#ifndef ScreenHeight
#define ScreenHeight [UIScreen mainScreen].bounds.size.height
#endif
#ifndef PBViewWidth
#define PBViewWidth self.view.bounds.size.width
#endif
#ifndef PBViewHeight
#define PBViewHeight self.view.bounds.size.height
#endif
#define PBDeviceHasBang \
({\
BOOL hasBang = false;\
if (@available(iOS 11.0, *)) {\
hasBang = [UIApplication sharedApplication].keyWindow.safeAreaInsets.bottom;\
}\
(hasBang);\
})
/// Portrait
#ifndef isPortrait
#define isPortrait ([[UIApplication sharedApplication] statusBarOrientation] == UIInterfaceOrientationPortrait || [[UIApplication sharedApplication] statusBarOrientation] == UIInterfaceOrientationPortraitUpsideDown)
#endif
#define PhotoBrowserAnimateTime 0.3
// define SDWebImagePrefetcher max number
#define PhotoBrowserPrefetchNum 8

View File

@@ -0,0 +1,68 @@
//
// KNPhotoDownloadMgr.h
// KNPhotoBrowser
//
// Created by LuKane on 2019/7/29.
// Copyright © 2019 LuKane. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "KNPhotoBrowser.h"
NS_ASSUME_NONNULL_BEGIN
typedef void(^PhotoDownLoadBlock)(KNPhotoDownloadState downloadState, float progress);
@interface KNPhotoDownloadMgr: NSObject <NSURLSessionDelegate>
/// single
+ (instancetype)shareInstance;
/// default file path
/// [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, true) lastObject] stringByAppendingPathComponent:@"KNPhotoBrowserData"];
@property (nonatomic,copy, readonly) NSString *filePath;
/**
download video, when finish it, it will be renamed!
eg: url = "https://www.xxxxxx/xxxx/123.mp4"
rename = "123" to MD5 encryption, and append ".mp4"
@param photoItems current item
@param downloadBlock block
*/
- (void)downloadVideoWithPhotoItems:(KNPhotoItems *)photoItems
downloadBlock:(PhotoDownLoadBlock)downloadBlock;
/// cancel all download task
- (void)cancelTask;
@end
@interface KNPhotoDownloadFileMgr: NSObject
/// default file path
/// [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, true) lastObject] stringByAppendingPathComponent:@"KNPhotoBrowserData"];
@property (nonatomic,copy, readonly) NSString *filePath;
/// check out filePath has contain current video or not
/// @param photoItems photoItems
- (BOOL)startCheckIsExistVideo:(KNPhotoItems *)photoItems;
/// get filePath of current video(filePath is like : "123" to MD5 encryption, and append ".mp4" )
/// @param photoItems photoItems
- (NSString *)startGetFilePath:(KNPhotoItems *)photoItems;
/// remove video by photoItems
/// @param photoItems photoItems
- (void)removeVideoByPhotoItems:(KNPhotoItems *)photoItems;
/// remove video by url string
/// @param urlString url string
- (void)removeVideoByURLString:(NSString *)urlString;
/// remove all video
- (void)removeAllVideo;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,236 @@
//
// KNPhotoDownloadMgr.m
// KNPhotoBrowser
//
// Created by LuKane on 2019/7/29.
// Copyright © 2019 LuKane. All rights reserved.
//
#import "KNPhotoDownloadMgr.h"
#import <CommonCrypto/CommonDigest.h>
@interface KNPhotoDownloadMgr(){
NSURLSessionDownloadTask *_downloadTask;
}
@property (nonatomic,copy ) PhotoDownLoadBlock downloadBlock;
@property (nonatomic,strong) KNPhotoItems *item;
@property (nonatomic,strong) KNPhotoItems *tempItem;
@end
@implementation KNPhotoDownloadMgr
static KNPhotoDownloadMgr *_mgr = nil;
+ (instancetype)shareInstance{
if (_mgr == nil) {
_mgr = [[KNPhotoDownloadMgr alloc] init];
}
return _mgr;
}
+ (instancetype)allocWithZone:(struct _NSZone *)zone{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_mgr = [super allocWithZone:zone];
});
return _mgr;
}
- (id)copyWithZone:(NSZone *)zone{
return self;
}
- (id)mutableCopyWithZone:(NSZone *)zone{
return self;
}
- (instancetype)init{
if (self = [super init]) {
_filePath = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, true) lastObject] stringByAppendingPathComponent:@"KNPhotoBrowserData"];
}
return self;
}
- (void)downloadVideoWithPhotoItems:(KNPhotoItems *)photoItems downloadBlock:(PhotoDownLoadBlock)downloadBlock{
if (photoItems.url == nil) {
return;
}
if (_tempItem == photoItems) {
_downloadBlock(KNPhotoDownloadStateRepeat,0.0);
return;
}
_item = [[KNPhotoItems alloc] init];
_item.url = photoItems.url;
_tempItem = photoItems;
_downloadBlock = downloadBlock;
[self cancelTask];
if (photoItems.isVideo == true) {
NSURL *url = [NSURL URLWithString:photoItems.url];
if ([url.scheme containsString:@"http"]) {
[self startDownLoadWithURL:url.absoluteString];
}
}else {
_downloadBlock(KNPhotoDownloadStateUnknow,0.0);
}
}
/// cancel all download task
- (void)cancelTask{
_item.downloadState = KNPhotoDownloadStateFailure;
[_downloadTask cancel];
}
- (void)startDownLoadWithURL:(NSString *)url{
if (_item.downloadState == KNPhotoDownloadStateDownloading) return;
_item.downloadState = KNPhotoDownloadStateDownloading;
_item.downloadProgress = 0.0;
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:[NSOperationQueue mainQueue]];
_downloadTask = [session downloadTaskWithURL:[NSURL URLWithString:url]];
[_downloadTask resume];
}
#pragma mark - NSURLSession Delegate --> NSURLSessionTaskDelegate
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite {
CGFloat progress = (CGFloat)totalBytesWritten / (CGFloat)totalBytesExpectedToWrite;
if (progress < 0) progress = 0;
if (progress > 1) progress = 1;
_item.downloadProgress = progress;
_item.downloadState = KNPhotoDownloadStateDownloading;
if (_downloadBlock) {
// NSLog(@"%lld-%lld == > %f",totalBytesWritten,totalBytesExpectedToWrite,progress);
_downloadBlock(KNPhotoDownloadStateDownloading,progress);
}
}
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(nullable NSError *)error {
_item.downloadState = KNPhotoDownloadStateSuccess;
_item.downloadProgress = 1.0;
if (error) {
_item.downloadState = KNPhotoDownloadStateFailure;
_item.downloadProgress = 0.0;
}
if (_downloadBlock) {
_downloadBlock(_item.downloadState,_item.downloadProgress);
}
_tempItem = nil;
}
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location {
NSString *file = [_filePath stringByAppendingPathComponent:[NSString stringWithFormat:@"%@.%@",[self md5:_item.url.lastPathComponent.stringByDeletingPathExtension],_item.url.pathExtension]];
[[NSFileManager defaultManager] copyItemAtURL:location toURL:[NSURL fileURLWithPath:file] error:nil];
}
- (NSString *)md5:(NSString *)str{
const char *cStr = [str UTF8String];
unsigned char result[16];
CC_MD5(cStr, (CC_LONG)strlen(cStr), result); // This is the md5 call
return [NSString stringWithFormat:
@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
result[0], result[1], result[2], result[3],
result[4], result[5], result[6], result[7],
result[8], result[9], result[10], result[11],
result[12], result[13], result[14], result[15]
];
}
@end
@implementation KNPhotoDownloadFileMgr
- (instancetype)init{
if (self = [super init]) {
_filePath = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, true) lastObject] stringByAppendingPathComponent:@"KNPhotoBrowserData"];
}
return self;
}
/// check is contain video or not
- (BOOL)startCheckIsExistVideo:(KNPhotoItems *)photoItems {
if (photoItems == nil || photoItems.url == nil) {
return false;
}
NSFileManager *fileMgr = [NSFileManager defaultManager];
BOOL isDir = false;
BOOL existed = [fileMgr fileExistsAtPath:_filePath isDirectory:&isDir];
if (!(isDir && existed)) {
[fileMgr createDirectoryAtPath:_filePath withIntermediateDirectories:true attributes:nil error:nil];
return false;
}else {
NSString *path = [_filePath stringByAppendingPathComponent:[NSString stringWithFormat:@"%@.%@",[self md5:photoItems.url.lastPathComponent.stringByDeletingPathExtension],photoItems.url.pathExtension]];
return [fileMgr fileExistsAtPath:path];
}
}
/// get video filepath , but it must download before
- (NSString *)startGetFilePath:(KNPhotoItems *)photoItems {
NSString *path = [_filePath stringByAppendingPathComponent:[NSString stringWithFormat:@"%@.%@",[self md5:photoItems.url.lastPathComponent.stringByDeletingPathExtension],photoItems.url.pathExtension]];
return path;
}
- (NSString *)md5:(NSString *)str{
const char *cStr = [str UTF8String];
unsigned char result[16];
CC_MD5(cStr, (CC_LONG)strlen(cStr), result); // This is the md5 call
return [NSString stringWithFormat:
@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
result[0], result[1], result[2], result[3],
result[4], result[5], result[6], result[7],
result[8], result[9], result[10], result[11],
result[12], result[13], result[14], result[15]
];
}
/// remove video by photoItems
/// @param photoItems photoItems
- (void)removeVideoByPhotoItems:(KNPhotoItems *)photoItems{
if (photoItems == nil) {
return;
}
[self removeVideoByURLString:photoItems.url];
}
/// remove video by url string
/// @param urlString url string
- (void)removeVideoByURLString:(NSString *)urlString{
if (urlString == nil) {
return;
}
if ([urlString stringByReplacingOccurrencesOfString:@" " withString:@""].length == 0) {
return;
}
NSFileManager *fileMgr = [NSFileManager defaultManager];
BOOL isDir = false;
BOOL existed = [fileMgr fileExistsAtPath:_filePath isDirectory:&isDir];
if ((isDir && existed)) {
NSError *err;
NSString *path = [_filePath stringByAppendingPathComponent:[NSString stringWithFormat:@"%@.%@",[self md5:urlString.lastPathComponent.stringByDeletingPathExtension],urlString.pathExtension]];
[fileMgr removeItemAtPath:path error:&err];
}
}
/// remove all video
- (void)removeAllVideo{
NSDirectoryEnumerator *enumerator = [[NSFileManager defaultManager] enumeratorAtPath:_filePath];
for (NSString *fileName in enumerator) {
[[NSFileManager defaultManager] removeItemAtPath:[_filePath stringByAppendingPathComponent:fileName] error:nil];
}
}
@end

View File

@@ -0,0 +1,36 @@
//
// KNPhotoImageCell.h
// KNPhotoBrowser
//
// Created by LuKane on 2018/12/14.
// Copyright © 2018 LuKane. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "KNPhotoBrowserImageView.h"
#import "KNProgressHUD.h"
@class KNPhotoItems;
NS_ASSUME_NONNULL_BEGIN
typedef void(^PhotoBrowerSingleTap)(void);
typedef void(^PhotoBrowerLongPressTap)(void);
@interface KNPhotoImageCell : UICollectionViewCell
/// set image with url
/// @param url url
/// @param placeHolder placeHolder image
/// @param photoItem curent photoItem
- (void)imageWithUrl:(NSString *)url placeHolder:(UIImage *)placeHolder photoItem:(KNPhotoItems *)photoItem;
@property (nonatomic,strong) KNPhotoBrowserImageView *photoBrowerImageView;
@property (nonatomic,copy ) PhotoBrowerSingleTap singleTap;
@property (nonatomic,copy ) PhotoBrowerLongPressTap longPressTap;
@property (nonatomic,assign) UIViewContentMode presentedMode;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,77 @@
//
// KNPhotoImageCell.m
// KNPhotoBrowser
//
// Created by LuKane on 2018/12/14.
// Copyright © 2018 LuKane. All rights reserved.
//
#import "KNPhotoImageCell.h"
#import "KNProgressHUD.h"
@implementation KNPhotoImageCell{
KNProgressHUD *_progressHUD;
}
- (instancetype)initWithFrame:(CGRect)frame{
if (self = [super initWithFrame:frame]) {
[self setupImageView];
}
return self;
}
- (void)setupImageView{
// 1.photoBrowerView
KNPhotoBrowserImageView *photoBrowerView = [[KNPhotoBrowserImageView alloc] initWithFrame:[UIScreen mainScreen].bounds];
_photoBrowerImageView = photoBrowerView;
[self.contentView addSubview:photoBrowerView];
// single tap
__weak typeof(self) weakself = self;
photoBrowerView.singleTap = ^{
if (weakself.singleTap) {
weakself.singleTap();
}
};
// long press
photoBrowerView.longPressTap = ^{
if (weakself.longPressTap) {
weakself.longPressTap();
}
};
// 2.progressHUD
KNProgressHUD *progressHUD = [[KNProgressHUD alloc] initWithFrame:(CGRect){{([UIScreen mainScreen].bounds.size.width - 40) * 0.5,([UIScreen mainScreen].bounds.size.height - 40) * 0.5},{40,40}}];
_progressHUD = progressHUD;
[self.contentView addSubview:progressHUD];
}
- (void)imageWithUrl:(NSString *)url
placeHolder:(UIImage *)placeHolder
photoItem:(nonnull KNPhotoItems *)photoItem{
[_photoBrowerImageView imageWithUrl:[NSURL URLWithString:url]
progressHUD:_progressHUD
placeHolder:placeHolder
photoItem:photoItem];
}
- (void)setPresentedMode:(UIViewContentMode)presentedMode {
_presentedMode = presentedMode;
_photoBrowerImageView.imageView.contentMode = self.presentedMode;
}
- (void)prepareForReuse{
[super prepareForReuse];
[_photoBrowerImageView.scrollView setZoomScale:1.f animated:false];
}
- (void)layoutSubviews{
[super layoutSubviews];
[_photoBrowerImageView.scrollView setZoomScale:1.f animated:false];
_photoBrowerImageView.frame = self.bounds;
_progressHUD.center = self.contentView.center;
}
@end

View File

@@ -0,0 +1,54 @@
//
// KNPhotoVideoCell.h
// KNPhotoBrowser
//
// Created by LuKane on 2019/6/11.
// Copyright © 2019 LuKane. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "KNPhotoAVPlayerView.h"
#import "KNPhotoLocateAVPlayerView.h"
NS_ASSUME_NONNULL_BEGIN
@protocol KNPhotoVideoCellDelegate <NSObject>
/// avplayer will dismmiss
- (void)photoVideoAVPlayerDismiss;
/// avplayer long press
/// @param longPress press
- (void)photoVideoAVPlayerLongPress:(UILongPressGestureRecognizer *)longPress;
@end
@interface KNPhotoVideoCell : UICollectionViewCell
/// play video on line with photoItems and placeHolder's image
/// @param photoItems photoItems
/// @param placeHolder placeHolder image
- (void)playerOnLinePhotoItems:(KNPhotoItems *)photoItems placeHolder:(UIImage *_Nullable)placeHolder;
/// play video by download first with photoItems and placeHolder's image
/// @param photoItems photoItems
/// @param placeHolder placeHolder image
- (void)playerLocatePhotoItems:(KNPhotoItems *)photoItems placeHolder:(UIImage *_Nullable)placeHolder;
- (void)playerWillEndDisplay;
@property (nonatomic,assign) BOOL isNeedAutoPlay;
@property (nonatomic,assign) BOOL isNeedVideoPlaceHolder;
@property (nonatomic,assign) BOOL isSoloAmbient;
@property (nonatomic,weak ) KNPhotoAVPlayerView *onlinePlayerView;
@property (nonatomic,weak ) KNPhotoLocateAVPlayerView *locatePlayerView;
@property (nonatomic,weak ) KNProgressHUD *progressHUD;
@property (nonatomic,weak ) id<KNPhotoVideoCellDelegate> delegate;
@property (nonatomic,assign) UIViewContentMode presentedMode;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,104 @@
//
// KNPhotoVideoCell.m
// KNPhotoBrowser
//
// Created by LuKane on 2019/6/11.
// Copyright © 2019 LuKane. All rights reserved.
//
#import "KNPhotoVideoCell.h"
#import "KNProgressHUD.h"
@interface KNPhotoVideoCell()<KNPhotoPlayerViewDelegate>
@end
@implementation KNPhotoVideoCell
- (instancetype)initWithFrame:(CGRect)frame{
if (self = [super initWithFrame:frame]) {
KNPhotoAVPlayerView *playerView = [[KNPhotoAVPlayerView alloc] initWithFrame:[UIScreen mainScreen].bounds];
[playerView setDelegate:self];
[self.contentView addSubview:playerView];
_onlinePlayerView = playerView;
KNPhotoLocateAVPlayerView *locatePlayerView = [[KNPhotoLocateAVPlayerView alloc] initWithFrame:[UIScreen mainScreen].bounds];
[locatePlayerView setDelegate:self];
[self.contentView addSubview:locatePlayerView];
_locatePlayerView = locatePlayerView;
KNProgressHUD *progressHUD = [[KNProgressHUD alloc] initWithFrame:(CGRect){{([UIScreen mainScreen].bounds.size.width - 40) * 0.5,([UIScreen mainScreen].bounds.size.height - 40) * 0.5},{40,40}}];
[self.contentView addSubview:progressHUD];
_progressHUD = progressHUD;
}
return self;
}
- (void)playerOnLinePhotoItems:(KNPhotoItems *)photoItems placeHolder:(UIImage * _Nullable)placeHolder {
_onlinePlayerView.isSoloAmbient = _isSoloAmbient;
[_onlinePlayerView playerOnLinePhotoItems:photoItems placeHolder:placeHolder];
_onlinePlayerView.hidden = false;
_locatePlayerView.hidden = true;
_progressHUD.hidden = true;
}
- (void)playerLocatePhotoItems:(KNPhotoItems *)photoItems placeHolder:(UIImage *)placeHolder {
_locatePlayerView.isSoloAmbient = _isSoloAmbient;
[_locatePlayerView playerLocatePhotoItems:photoItems progressHUD:_progressHUD placeHolder:placeHolder];
_onlinePlayerView.hidden = true;
_locatePlayerView.hidden = false;
_progressHUD.hidden = true;
}
- (void)setPresentedMode:(UIViewContentMode)presentedMode{
_presentedMode = presentedMode;
_onlinePlayerView.placeHolderImgView.contentMode = self.presentedMode;
_locatePlayerView.placeHolderImgView.contentMode = self.presentedMode;
}
- (void)playerWillEndDisplay{
[_onlinePlayerView playerWillReset];
[_locatePlayerView playerWillReset];
}
/// setter
- (void)setIsNeedAutoPlay:(BOOL)isNeedAutoPlay{
_isNeedAutoPlay = isNeedAutoPlay;
if (isNeedAutoPlay == true) {
if (_onlinePlayerView.isHidden == false) {
[_onlinePlayerView setIsNeedAutoPlay:true];
}
if (_locatePlayerView.isHidden == false) {
[_locatePlayerView setIsNeedAutoPlay:true];
}
}
}
/// setter
- (void)setIsNeedVideoPlaceHolder:(BOOL)isNeedVideoPlaceHolder{
_isNeedVideoPlaceHolder = isNeedVideoPlaceHolder;
_onlinePlayerView.isNeedVideoPlaceHolder = isNeedVideoPlaceHolder;
_locatePlayerView.isNeedVideoPlaceHolder = isNeedVideoPlaceHolder;
}
/// delegate function
- (void)photoPlayerViewDismiss{
[_onlinePlayerView playerWillReset];
[_locatePlayerView playerWillReset];
if ([_delegate respondsToSelector:@selector(photoVideoAVPlayerDismiss)]) {
[_delegate photoVideoAVPlayerDismiss];
}
}
/// delegate function
- (void)photoPlayerLongPress:(UILongPressGestureRecognizer *)longPress{
if ([_delegate respondsToSelector:@selector(photoVideoAVPlayerLongPress:)]) {
[_delegate photoVideoAVPlayerLongPress:longPress];
}
}
- (void)layoutSubviews{
[super layoutSubviews];
_onlinePlayerView.frame = self.bounds;
_locatePlayerView.frame = self.bounds;
_progressHUD.center = self.contentView.center;
}
@end

View File

@@ -0,0 +1,18 @@
//
// KNProgressHUD.h
// KNPhotoBrowser
//
// Created by LuKane on 16/8/17.
// Copyright © 2016年 LuKane. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface KNProgressHUD : UIView
/**
* progress, range is from 0 to 1
*/
@property (nonatomic, assign) CGFloat progress;
@end

View File

@@ -0,0 +1,116 @@
//
// KNProgressHUD.m
// KNPhotoBrowser
//
// Created by LuKane on 16/8/17.
// Copyright © 2016 LuKane. All rights reserved.
//
#import "KNProgressHUD.h"
@interface KNProgressHUD()
@property (nonatomic, strong) CAShapeLayer *sectorLayer;
@property (nonatomic, strong) CAShapeLayer *loadingLayer;
@property (nonatomic, strong) CAShapeLayer *sharpLayer;
@end
@implementation KNProgressHUD
- (instancetype)initWithFrame:(CGRect)frame{
if (self = [super initWithFrame:frame]) {
[self setBackgroundColor:[UIColor clearColor]];
}
return self;
}
- (void)drawRect:(CGRect)rect{
[self setupSectorLayer];
[self setupLoadingLayer];
[self setupSharpLayer:rect];
}
// layer of sector
- (void)setupSectorLayer{
self.sectorLayer= [CAShapeLayer layer];
[self.sectorLayer setFillColor:UIColor.clearColor.CGColor];
[self.sectorLayer setLineWidth:1.f];
[self.sectorLayer setStrokeColor:[UIColor whiteColor].CGColor];
[self.sectorLayer setPath:[[UIBezierPath bezierPathWithOvalInRect:[self bounds]] CGPath]];
[self.sectorLayer setHidden:YES];
[self.layer addSublayer:self.sectorLayer];
}
// loading
- (void)setupLoadingLayer{
self.loadingLayer = [CAShapeLayer layer];
[self.loadingLayer setFrame:[self bounds]];
[self.loadingLayer setAnchorPoint:CGPointMake(0.5f, 0.5f)];
[self.loadingLayer setFillColor:UIColor.clearColor.CGColor];
[self.loadingLayer setLineWidth:1.f];
[self.loadingLayer setStrokeColor:[[UIColor whiteColor] CGColor]];
CGPoint center = CGPointMake(self.bounds.size.width * 0.5, self.bounds.size.height * 0.5);
CGFloat loadRadius = self.bounds.size.width * 0.5;
CGFloat endAngle = (2 * (float)M_PI) - ((float)M_PI / 8);
self.loadingLayer.path = [UIBezierPath bezierPathWithArcCenter:center
radius:loadRadius
startAngle:0
endAngle:endAngle
clockwise:YES].CGPath;
CABasicAnimation *rotationAnimation = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"];
[rotationAnimation setToValue:[NSNumber numberWithFloat:M_PI * 2.0]];
[rotationAnimation setDuration:1.f];
[rotationAnimation setCumulative:YES];
[rotationAnimation setRepeatCount:HUGE_VALF];
[self.loadingLayer addAnimation:rotationAnimation forKey:@"rotationAnimation"];
[self.layer addSublayer:self.loadingLayer];
}
// sector
- (void)setupSharpLayer:(CGRect)rect{
CGFloat minSide = MIN(CGRectGetWidth(rect), CGRectGetHeight(rect));
CGFloat radius = minSide/2 - 3;
self.sharpLayer = [CAShapeLayer layer];
[self.sharpLayer setFrame:[self bounds]];
[self.sharpLayer setAnchorPoint:CGPointMake(0.5f, 0.5f)];
[self.sharpLayer setFillColor:[[UIColor clearColor] CGColor]];
[self.sharpLayer setStrokeColor:[[UIColor whiteColor] CGColor]];
[self.sharpLayer setLineWidth:radius];
[self.sharpLayer setStrokeStart:0];
[self.sharpLayer setStrokeEnd:0];
CGRect pathRect = CGRectMake(CGRectGetWidth(self.bounds)/2 - radius/2, CGRectGetHeight(self.bounds)/2 - radius/2, radius, radius);
[self.sharpLayer setPath:[[UIBezierPath bezierPathWithRoundedRect:pathRect cornerRadius:radius] CGPath]];
[self.layer addSublayer:self.sharpLayer];
}
// progress
- (void)setProgress:(CGFloat)progress{
progress = MAX(0.0f, progress);
progress = MIN(1.0f, progress);
if (progress > 0) {
[self.loadingLayer removeAllAnimations];
self.sectorLayer.hidden = false;
[self.loadingLayer removeFromSuperlayer];
}
if (progress != _progress) {
self.sharpLayer.strokeEnd = progress;
_progress = progress;
}
if (progress >= 1) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self setHidden:true];
});
}
}
@end

View File

@@ -0,0 +1,17 @@
K 25
svn:wc:ra_dav:version-url
V 129
/svn/onlineIOS/!svn/ver/1042/trunk/NewMTWorks/NewMWorks/MaiTian/Vendors%E3%80%90%E7%AC%AC%E4%B8%89%E6%96%B9%E3%80%91/Reachability
END
Reachability.h
K 25
svn:wc:ra_dav:version-url
V 144
/svn/onlineIOS/!svn/ver/1042/trunk/NewMTWorks/NewMWorks/MaiTian/Vendors%E3%80%90%E7%AC%AC%E4%B8%89%E6%96%B9%E3%80%91/Reachability/Reachability.h
END
Reachability.m
K 25
svn:wc:ra_dav:version-url
V 144
/svn/onlineIOS/!svn/ver/1042/trunk/NewMTWorks/NewMWorks/MaiTian/Vendors%E3%80%90%E7%AC%AC%E4%B8%89%E6%96%B9%E3%80%91/Reachability/Reachability.m
END

View File

@@ -0,0 +1,96 @@
10
dir
2234
http://luxikang@172.16.13.104:8080/svn/onlineIOS/trunk/NewMTWorks/NewMWorks/MaiTian/Vendors%E3%80%90%E7%AC%AC%E4%B8%89%E6%96%B9%E3%80%91/Reachability
http://luxikang@172.16.13.104:8080/svn/onlineIOS
2017-01-03T08:48:49.599819Z
1042
shenyinlong
5114eb03-57ec-48d3-8e50-381a887ab7c2
Reachability.h
file
2017-09-01T04:09:07.000000Z
328628dd2d4f9064468e6fc1433364be
2017-01-03T08:48:49.599819Z
1042
shenyinlong
has-props
3855
Reachability.m
file
2017-09-01T04:09:07.000000Z
18fc8ff47ef9d495d5f5a7007c1d9bb3
2017-01-03T08:48:49.599819Z
1042
shenyinlong
has-props
13153

View File

@@ -0,0 +1,5 @@
K 14
svn:executable
V 0
END

View File

@@ -0,0 +1,5 @@
K 14
svn:executable
V 0
END

View File

@@ -0,0 +1,104 @@
/*
Copyright (c) 2011, Tony Million.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
#import <Foundation/Foundation.h>
#import <SystemConfiguration/SystemConfiguration.h>
//! Project version number for MacOSReachability.
FOUNDATION_EXPORT double ReachabilityVersionNumber;
//! Project version string for MacOSReachability.
FOUNDATION_EXPORT const unsigned char ReachabilityVersionString[];
/**
* Create NS_ENUM macro if it does not exist on the targeted version of iOS or OS X.
*
* @see http://nshipster.com/ns_enum-ns_options/
**/
#ifndef NS_ENUM
#define NS_ENUM(_type, _name) enum _name : _type _name; enum _name : _type
#endif
#define MTNetWorkAvailable [[Reachability reachabilityForInternetConnection] isReachable]
extern NSString *const kReachabilityChangedNotification;
typedef NS_ENUM(NSInteger, NetworkStatus) {
// Apple NetworkStatus Compatible Names.
NotReachable = 0,
ReachableViaWiFi = 2,
ReachableViaWWAN = 1
};
@class Reachability;
typedef void (^NetworkReachable)(Reachability * reachability);
typedef void (^NetworkUnreachable)(Reachability * reachability);
typedef void (^NetworkReachability)(Reachability * reachability, SCNetworkConnectionFlags flags);
@interface Reachability : NSObject
@property (nonatomic, copy) NetworkReachable reachableBlock;
@property (nonatomic, copy) NetworkUnreachable unreachableBlock;
@property (nonatomic, copy) NetworkReachability reachabilityBlock;
@property (nonatomic, assign) BOOL reachableOnWWAN;
+(instancetype)reachabilityWithHostname:(NSString*)hostname;
// This is identical to the function above, but is here to maintain
//compatibility with Apples original code. (see .m)
+(instancetype)reachabilityWithHostName:(NSString*)hostname;
+(instancetype)reachabilityForInternetConnection;
+(instancetype)reachabilityWithAddress:(void *)hostAddress;
+(instancetype)reachabilityForLocalWiFi;
-(instancetype)initWithReachabilityRef:(SCNetworkReachabilityRef)ref;
-(BOOL)startNotifier;
-(void)stopNotifier;
-(BOOL)isReachable;
-(BOOL)isReachableViaWWAN;
-(BOOL)isReachableViaWiFi;
// WWAN may be available, but not active until a connection has been established.
// WiFi may require a connection for VPN on Demand.
-(BOOL)isConnectionRequired; // Identical DDG variant.
-(BOOL)connectionRequired; // Apple's routine.
// Dynamic, on demand connection?
-(BOOL)isConnectionOnDemand;
// Is user intervention required?
-(BOOL)isInterventionRequired;
-(NetworkStatus)currentReachabilityStatus;
-(SCNetworkReachabilityFlags)reachabilityFlags;
-(NSString*)currentReachabilityString;
-(NSString*)currentReachabilityFlags;
@end

View File

@@ -0,0 +1,475 @@
/*
Copyright (c) 2011, Tony Million.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
#import "Reachability.h"
#import <sys/socket.h>
#import <netinet/in.h>
#import <netinet6/in6.h>
#import <arpa/inet.h>
#import <ifaddrs.h>
#import <netdb.h>
NSString *const kReachabilityChangedNotification = @"kReachabilityChangedNotification";
@interface Reachability ()
@property (nonatomic, assign) SCNetworkReachabilityRef reachabilityRef;
@property (nonatomic, strong) dispatch_queue_t reachabilitySerialQueue;
@property (nonatomic, strong) id reachabilityObject;
-(void)reachabilityChanged:(SCNetworkReachabilityFlags)flags;
-(BOOL)isReachableWithFlags:(SCNetworkReachabilityFlags)flags;
@end
static NSString *reachabilityFlags(SCNetworkReachabilityFlags flags)
{
return [NSString stringWithFormat:@"%c%c %c%c%c%c%c%c%c",
#if TARGET_OS_IPHONE
(flags & kSCNetworkReachabilityFlagsIsWWAN) ? 'W' : '-',
#else
'X',
#endif
(flags & kSCNetworkReachabilityFlagsReachable) ? 'R' : '-',
(flags & kSCNetworkReachabilityFlagsConnectionRequired) ? 'c' : '-',
(flags & kSCNetworkReachabilityFlagsTransientConnection) ? 't' : '-',
(flags & kSCNetworkReachabilityFlagsInterventionRequired) ? 'i' : '-',
(flags & kSCNetworkReachabilityFlagsConnectionOnTraffic) ? 'C' : '-',
(flags & kSCNetworkReachabilityFlagsConnectionOnDemand) ? 'D' : '-',
(flags & kSCNetworkReachabilityFlagsIsLocalAddress) ? 'l' : '-',
(flags & kSCNetworkReachabilityFlagsIsDirect) ? 'd' : '-'];
}
// Start listening for reachability notifications on the current run loop
static void TMReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkReachabilityFlags flags, void* info)
{
#pragma unused (target)
Reachability *reachability = ((__bridge Reachability*)info);
// We probably don't need an autoreleasepool here, as GCD docs state each queue has its own autorelease pool,
// but what the heck eh?
@autoreleasepool
{
[reachability reachabilityChanged:flags];
}
}
@implementation Reachability
#pragma mark - Class Constructor Methods
+(instancetype)reachabilityWithHostName:(NSString*)hostname
{
return [Reachability reachabilityWithHostname:hostname];
}
+(instancetype)reachabilityWithHostname:(NSString*)hostname
{
SCNetworkReachabilityRef ref = SCNetworkReachabilityCreateWithName(NULL, [hostname UTF8String]);
if (ref)
{
id reachability = [[self alloc] initWithReachabilityRef:ref];
return reachability;
}
return nil;
}
+(instancetype)reachabilityWithAddress:(void *)hostAddress
{
SCNetworkReachabilityRef ref = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, (const struct sockaddr*)hostAddress);
if (ref)
{
id reachability = [[self alloc] initWithReachabilityRef:ref];
return reachability;
}
return nil;
}
+(instancetype)reachabilityForInternetConnection
{
struct sockaddr_in zeroAddress;
bzero(&zeroAddress, sizeof(zeroAddress));
zeroAddress.sin_len = sizeof(zeroAddress);
zeroAddress.sin_family = AF_INET;
return [self reachabilityWithAddress:&zeroAddress];
}
+(instancetype)reachabilityForLocalWiFi
{
struct sockaddr_in localWifiAddress;
bzero(&localWifiAddress, sizeof(localWifiAddress));
localWifiAddress.sin_len = sizeof(localWifiAddress);
localWifiAddress.sin_family = AF_INET;
// IN_LINKLOCALNETNUM is defined in <netinet/in.h> as 169.254.0.0
localWifiAddress.sin_addr.s_addr = htonl(IN_LINKLOCALNETNUM);
return [self reachabilityWithAddress:&localWifiAddress];
}
// Initialization methods
-(instancetype)initWithReachabilityRef:(SCNetworkReachabilityRef)ref
{
self = [super init];
if (self != nil)
{
self.reachableOnWWAN = YES;
self.reachabilityRef = ref;
// We need to create a serial queue.
// We allocate this once for the lifetime of the notifier.
self.reachabilitySerialQueue = dispatch_queue_create("com.tonymillion.reachability", NULL);
}
return self;
}
-(void)dealloc
{
[self stopNotifier];
if(self.reachabilityRef)
{
CFRelease(self.reachabilityRef);
self.reachabilityRef = nil;
}
self.reachableBlock = nil;
self.unreachableBlock = nil;
self.reachabilityBlock = nil;
self.reachabilitySerialQueue = nil;
}
#pragma mark - Notifier Methods
// Notifier
// NOTE: This uses GCD to trigger the blocks - they *WILL NOT* be called on THE MAIN THREAD
// - In other words DO NOT DO ANY UI UPDATES IN THE BLOCKS.
// INSTEAD USE dispatch_async(dispatch_get_main_queue(), ^{UISTUFF}) (or dispatch_sync if you want)
-(BOOL)startNotifier
{
// allow start notifier to be called multiple times
if(self.reachabilityObject && (self.reachabilityObject == self))
{
return YES;
}
SCNetworkReachabilityContext context = { 0, NULL, NULL, NULL, NULL };
context.info = (__bridge void *)self;
if(SCNetworkReachabilitySetCallback(self.reachabilityRef, TMReachabilityCallback, &context))
{
// Set it as our reachability queue, which will retain the queue
if(SCNetworkReachabilitySetDispatchQueue(self.reachabilityRef, self.reachabilitySerialQueue))
{
// this should do a retain on ourself, so as long as we're in notifier mode we shouldn't disappear out from under ourselves
// woah
self.reachabilityObject = self;
return YES;
}
else
{
#ifdef DEBUG
NSLog(@"SCNetworkReachabilitySetDispatchQueue() failed: %s", SCErrorString(SCError()));
#endif
// UH OH - FAILURE - stop any callbacks!
SCNetworkReachabilitySetCallback(self.reachabilityRef, NULL, NULL);
}
}
else
{
#ifdef DEBUG
NSLog(@"SCNetworkReachabilitySetCallback() failed: %s", SCErrorString(SCError()));
#endif
}
// if we get here we fail at the internet
self.reachabilityObject = nil;
return NO;
}
-(void)stopNotifier
{
// First stop, any callbacks!
SCNetworkReachabilitySetCallback(self.reachabilityRef, NULL, NULL);
// Unregister target from the GCD serial dispatch queue.
SCNetworkReachabilitySetDispatchQueue(self.reachabilityRef, NULL);
self.reachabilityObject = nil;
}
#pragma mark - reachability tests
// This is for the case where you flick the airplane mode;
// you end up getting something like this:
//Reachability: WR ct-----
//Reachability: -- -------
//Reachability: WR ct-----
//Reachability: -- -------
// We treat this as 4 UNREACHABLE triggers - really apple should do better than this
#define testcase (kSCNetworkReachabilityFlagsConnectionRequired | kSCNetworkReachabilityFlagsTransientConnection)
-(BOOL)isReachableWithFlags:(SCNetworkReachabilityFlags)flags
{
BOOL connectionUP = YES;
if(!(flags & kSCNetworkReachabilityFlagsReachable))
connectionUP = NO;
if( (flags & testcase) == testcase )
connectionUP = NO;
#if TARGET_OS_IPHONE
if(flags & kSCNetworkReachabilityFlagsIsWWAN)
{
// We're on 3G.
if(!self.reachableOnWWAN)
{
// We don't want to connect when on 3G.
connectionUP = NO;
}
}
#endif
return connectionUP;
}
-(BOOL)isReachable
{
SCNetworkReachabilityFlags flags;
if(!SCNetworkReachabilityGetFlags(self.reachabilityRef, &flags))
return NO;
return [self isReachableWithFlags:flags];
}
-(BOOL)isReachableViaWWAN
{
#if TARGET_OS_IPHONE
SCNetworkReachabilityFlags flags = 0;
if(SCNetworkReachabilityGetFlags(self.reachabilityRef, &flags))
{
// Check we're REACHABLE
if(flags & kSCNetworkReachabilityFlagsReachable)
{
// Now, check we're on WWAN
if(flags & kSCNetworkReachabilityFlagsIsWWAN)
{
return YES;
}
}
}
#endif
return NO;
}
-(BOOL)isReachableViaWiFi
{
SCNetworkReachabilityFlags flags = 0;
if(SCNetworkReachabilityGetFlags(self.reachabilityRef, &flags))
{
// Check we're reachable
if((flags & kSCNetworkReachabilityFlagsReachable))
{
#if TARGET_OS_IPHONE
// Check we're NOT on WWAN
if((flags & kSCNetworkReachabilityFlagsIsWWAN))
{
return NO;
}
#endif
return YES;
}
}
return NO;
}
// WWAN may be available, but not active until a connection has been established.
// WiFi may require a connection for VPN on Demand.
-(BOOL)isConnectionRequired
{
return [self connectionRequired];
}
-(BOOL)connectionRequired
{
SCNetworkReachabilityFlags flags;
if(SCNetworkReachabilityGetFlags(self.reachabilityRef, &flags))
{
return (flags & kSCNetworkReachabilityFlagsConnectionRequired);
}
return NO;
}
// Dynamic, on demand connection?
-(BOOL)isConnectionOnDemand
{
SCNetworkReachabilityFlags flags;
if (SCNetworkReachabilityGetFlags(self.reachabilityRef, &flags))
{
return ((flags & kSCNetworkReachabilityFlagsConnectionRequired) &&
(flags & (kSCNetworkReachabilityFlagsConnectionOnTraffic | kSCNetworkReachabilityFlagsConnectionOnDemand)));
}
return NO;
}
// Is user intervention required?
-(BOOL)isInterventionRequired
{
SCNetworkReachabilityFlags flags;
if (SCNetworkReachabilityGetFlags(self.reachabilityRef, &flags))
{
return ((flags & kSCNetworkReachabilityFlagsConnectionRequired) &&
(flags & kSCNetworkReachabilityFlagsInterventionRequired));
}
return NO;
}
#pragma mark - reachability status stuff
-(NetworkStatus)currentReachabilityStatus
{
if([self isReachable])
{
if([self isReachableViaWiFi])
return ReachableViaWiFi;
#if TARGET_OS_IPHONE
return ReachableViaWWAN;
#endif
}
return NotReachable;
}
-(SCNetworkReachabilityFlags)reachabilityFlags
{
SCNetworkReachabilityFlags flags = 0;
if(SCNetworkReachabilityGetFlags(self.reachabilityRef, &flags))
{
return flags;
}
return 0;
}
-(NSString*)currentReachabilityString
{
NetworkStatus temp = [self currentReachabilityStatus];
if(temp == ReachableViaWWAN)
{
// Updated for the fact that we have CDMA phones now!
return NSLocalizedString(@"Cellular", @"");
}
if (temp == ReachableViaWiFi)
{
return NSLocalizedString(@"WiFi", @"");
}
return NSLocalizedString(@"No Connection", @"");
}
-(NSString*)currentReachabilityFlags
{
return reachabilityFlags([self reachabilityFlags]);
}
#pragma mark - Callback function calls this method
-(void)reachabilityChanged:(SCNetworkReachabilityFlags)flags
{
if([self isReachableWithFlags:flags])
{
if(self.reachableBlock)
{
self.reachableBlock(self);
}
}
else
{
if(self.unreachableBlock)
{
self.unreachableBlock(self);
}
}
if(self.reachabilityBlock)
{
self.reachabilityBlock(self, flags);
}
// this makes sure the change notification happens on the MAIN THREAD
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:kReachabilityChangedNotification
object:self];
});
}
#pragma mark - Debug Description
- (NSString *) description
{
NSString *description = [NSString stringWithFormat:@"<%@: %#x (%@)>",
NSStringFromClass([self class]), (unsigned int) self, [self currentReachabilityFlags]];
return description;
}
@end

View File

@@ -0,0 +1,63 @@
//
// KNReachability.h
// KNPhotoBrowser
//
// Created by LuKane on 2021/3/19.
// Copyright © 2021 LuKane. All rights reserved.
// ***************************************
// **** this class is from the github ****
// ***************************************
#import <Foundation/Foundation.h>
#import <SystemConfiguration/SystemConfiguration.h>
NS_ASSUME_NONNULL_BEGIN
extern NSString *const kKNReachabilityChangedNotification;
typedef NS_ENUM(NSInteger, NetworkStatus) {
NotReachable = 0,
ReachableViaWiFi = 2,
ReachableViaWWAN = 1
};
@class KNReachability;
typedef void (^NetworkReachable)(KNReachability * reachability);
typedef void (^NetworkUnreachable)(KNReachability * reachability);
typedef void (^NetworkReachability)(KNReachability * reachability, SCNetworkConnectionFlags flags);
@interface KNReachability : NSObject
@property (nonatomic, copy, nullable) NetworkReachable reachableBlock;
@property (nonatomic, copy, nullable) NetworkUnreachable unreachableBlock;
@property (nonatomic, copy, nullable) NetworkReachability reachabilityBlock;
@property (nonatomic, assign) BOOL reachableOnWWAN;
+ (instancetype)reachabilityWithHostname:(NSString*)hostname;
+ (instancetype)reachabilityWithHostName:(NSString*)hostname;
+ (instancetype)reachabilityForInternetConnection;
+ (instancetype)reachabilityWithAddress:(void *)hostAddress;
+ (instancetype)reachabilityForLocalWiFi;
- (instancetype)initWithReachabilityRef:(SCNetworkReachabilityRef)ref;
- (BOOL)startNotifier;
- (void)stopNotifier;
- (BOOL)isReachable;
- (BOOL)isReachableViaWWAN;
- (BOOL)isReachableViaWiFi;
- (BOOL)isConnectionRequired;
- (BOOL)connectionRequired;
- (BOOL)isConnectionOnDemand;
- (BOOL)isInterventionRequired;
- (NetworkStatus)currentReachabilityStatus;
- (SCNetworkReachabilityFlags)reachabilityFlags;
- (NSString *)currentReachabilityString;
- (NSString *)currentReachabilityFlags;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,378 @@
//
// KNReachability.m
// KNPhotoBrowser
//
// Created by LuKane on 2021/3/19.
// Copyright © 2021 LuKane. All rights reserved.
//
#import "KNReachability.h"
#import <sys/socket.h>
#import <netinet/in.h>
#import <netinet6/in6.h>
#import <arpa/inet.h>
#import <ifaddrs.h>
#import <netdb.h>
NSString *const kKNReachabilityChangedNotification = @"kKNReachabilityChangedNotification";
@interface KNReachability()
@property (nonatomic, assign) SCNetworkReachabilityRef reachabilityRef;
@property (nonatomic, strong) dispatch_queue_t reachabilitySerialQueue;
@property (nonatomic, strong) id reachabilityObject;
- (void)reachabilityChanged:(SCNetworkReachabilityFlags)flags;
- (BOOL)isReachableWithFlags:(SCNetworkReachabilityFlags)flags;
@end
static NSString *reachabilityFlags(SCNetworkReachabilityFlags flags){
return [NSString stringWithFormat:@"%c%c %c%c%c%c%c%c%c",
#if TARGET_OS_IPHONE
(flags & kSCNetworkReachabilityFlagsIsWWAN) ? 'W' : '-',
#else
'X',
#endif
(flags & kSCNetworkReachabilityFlagsReachable) ? 'R' : '-',
(flags & kSCNetworkReachabilityFlagsConnectionRequired) ? 'c' : '-',
(flags & kSCNetworkReachabilityFlagsTransientConnection) ? 't' : '-',
(flags & kSCNetworkReachabilityFlagsInterventionRequired) ? 'i' : '-',
(flags & kSCNetworkReachabilityFlagsConnectionOnTraffic) ? 'C' : '-',
(flags & kSCNetworkReachabilityFlagsConnectionOnDemand) ? 'D' : '-',
(flags & kSCNetworkReachabilityFlagsIsLocalAddress) ? 'l' : '-',
(flags & kSCNetworkReachabilityFlagsIsDirect) ? 'd' : '-'];
}
static void TMReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkReachabilityFlags flags, void* info){
#pragma unused (target)
KNReachability *reachability = ((__bridge KNReachability*)info);
// We probably don't need an autoreleasepool here, as GCD docs state each queue has its own autorelease pool,
// but what the heck eh?
@autoreleasepool
{
[reachability reachabilityChanged:flags];
}
}
@implementation KNReachability
+ (instancetype)reachabilityWithHostName:(NSString*)hostname{
return [KNReachability reachabilityWithHostname:hostname];
}
+ (instancetype)reachabilityWithHostname:(NSString*)hostname{
SCNetworkReachabilityRef ref = SCNetworkReachabilityCreateWithName(NULL, [hostname UTF8String]);
if (ref)
{
id reachability = [[self alloc] initWithReachabilityRef:ref];
return reachability;
}
return nil;
}
+ (instancetype)reachabilityWithAddress:(void *)hostAddress{
SCNetworkReachabilityRef ref = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, (const struct sockaddr*)hostAddress);
if (ref)
{
id reachability = [[self alloc] initWithReachabilityRef:ref];
return reachability;
}
return nil;
}
+ (instancetype)reachabilityForInternetConnection{
struct sockaddr_in zeroAddress;
bzero(&zeroAddress, sizeof(zeroAddress));
zeroAddress.sin_len = sizeof(zeroAddress);
zeroAddress.sin_family = AF_INET;
return [self reachabilityWithAddress:&zeroAddress];
}
+ (instancetype)reachabilityForLocalWiFi{
struct sockaddr_in localWifiAddress;
bzero(&localWifiAddress, sizeof(localWifiAddress));
localWifiAddress.sin_len = sizeof(localWifiAddress);
localWifiAddress.sin_family = AF_INET;
// IN_LINKLOCALNETNUM is defined in <netinet/in.h> as 169.254.0.0
localWifiAddress.sin_addr.s_addr = htonl(IN_LINKLOCALNETNUM);
return [self reachabilityWithAddress:&localWifiAddress];
}
- (instancetype)initWithReachabilityRef:(SCNetworkReachabilityRef)ref{
self = [super init];
if (self != nil)
{
self.reachableOnWWAN = YES;
self.reachabilityRef = ref;
// We need to create a serial queue.
// We allocate this once for the lifetime of the notifier.
self.reachabilitySerialQueue = dispatch_queue_create("com.tonymillion.reachability", NULL);
}
return self;
}
- (void)dealloc{
[self stopNotifier];
if(self.reachabilityRef){
CFRelease(self.reachabilityRef);
self.reachabilityRef = nil;
}
self.reachableBlock = nil;
self.unreachableBlock = nil;
self.reachabilityBlock = nil;
self.reachabilitySerialQueue = nil;
}
#pragma mark - Notifier Methods
// Notifier
// NOTE: This uses GCD to trigger the blocks - they *WILL NOT* be called on THE MAIN THREAD
// - In other words DO NOT DO ANY UI UPDATES IN THE BLOCKS.
// INSTEAD USE dispatch_async(dispatch_get_main_queue(), ^{UISTUFF}) (or dispatch_sync if you want)
- (BOOL)startNotifier{
// allow start notifier to be called multiple times
if(self.reachabilityObject && (self.reachabilityObject == self)){
return YES;
}
SCNetworkReachabilityContext context = { 0, NULL, NULL, NULL, NULL };
context.info = (__bridge void *)self;
if(SCNetworkReachabilitySetCallback(self.reachabilityRef, TMReachabilityCallback, &context)){
// Set it as our reachability queue, which will retain the queue
if(SCNetworkReachabilitySetDispatchQueue(self.reachabilityRef, self.reachabilitySerialQueue)){
// this should do a retain on ourself, so as long as we're in notifier mode we shouldn't disappear out from under ourselves
// woah
self.reachabilityObject = self;
return YES;
}else {
#ifdef DEBUG
NSLog(@"SCNetworkReachabilitySetDispatchQueue() failed: %s", SCErrorString(SCError()));
#endif
// UH OH - FAILURE - stop any callbacks!
SCNetworkReachabilitySetCallback(self.reachabilityRef, NULL, NULL);
}
} else {
#ifdef DEBUG
NSLog(@"SCNetworkReachabilitySetCallback() failed: %s", SCErrorString(SCError()));
#endif
}
// if we get here we fail at the internet
self.reachabilityObject = nil;
return NO;
}
- (void)stopNotifier{
// First stop, any callbacks!
SCNetworkReachabilitySetCallback(self.reachabilityRef, NULL, NULL);
// Unregister target from the GCD serial dispatch queue.
SCNetworkReachabilitySetDispatchQueue(self.reachabilityRef, NULL);
self.reachabilityObject = nil;
}
#pragma mark - reachability tests
// This is for the case where you flick the airplane mode;
// you end up getting something like this:
//Reachability: WR ct-----
//Reachability: -- -------
//Reachability: WR ct-----
//Reachability: -- -------
// We treat this as 4 UNREACHABLE triggers - really apple should do better than this
#define testcase (kSCNetworkReachabilityFlagsConnectionRequired | kSCNetworkReachabilityFlagsTransientConnection)
- (BOOL)isReachableWithFlags:(SCNetworkReachabilityFlags)flags{
BOOL connectionUP = YES;
if(!(flags & kSCNetworkReachabilityFlagsReachable))
connectionUP = NO;
if( (flags & testcase) == testcase )
connectionUP = NO;
#if TARGET_OS_IPHONE
if(flags & kSCNetworkReachabilityFlagsIsWWAN){
// We're on 3G.
if(!self.reachableOnWWAN)
{
// We don't want to connect when on 3G.
connectionUP = NO;
}
}
#endif
return connectionUP;
}
- (BOOL)isReachable{
SCNetworkReachabilityFlags flags;
if(!SCNetworkReachabilityGetFlags(self.reachabilityRef, &flags))
return NO;
return [self isReachableWithFlags:flags];
}
- (BOOL)isReachableViaWWAN{
#if TARGET_OS_IPHONE
SCNetworkReachabilityFlags flags = 0;
if(SCNetworkReachabilityGetFlags(self.reachabilityRef, &flags)){
// Check we're REACHABLE
if(flags & kSCNetworkReachabilityFlagsReachable){
// Now, check we're on WWAN
if(flags & kSCNetworkReachabilityFlagsIsWWAN){
return YES;
}
}
}
#endif
return NO;
}
- (BOOL)isReachableViaWiFi{
SCNetworkReachabilityFlags flags = 0;
if(SCNetworkReachabilityGetFlags(self.reachabilityRef, &flags)){
// Check we're reachable
if((flags & kSCNetworkReachabilityFlagsReachable)){
#if TARGET_OS_IPHONE
// Check we're NOT on WWAN
if((flags & kSCNetworkReachabilityFlagsIsWWAN)){
return NO;
}
#endif
return YES;
}
}
return NO;
}
// WWAN may be available, but not active until a connection has been established.
// WiFi may require a connection for VPN on Demand.
- (BOOL)isConnectionRequired{
return [self connectionRequired];
}
- (BOOL)connectionRequired{
SCNetworkReachabilityFlags flags;
if(SCNetworkReachabilityGetFlags(self.reachabilityRef, &flags)){
return (flags & kSCNetworkReachabilityFlagsConnectionRequired);
}
return NO;
}
// Dynamic, on demand connection?
- (BOOL)isConnectionOnDemand{
SCNetworkReachabilityFlags flags;
if (SCNetworkReachabilityGetFlags(self.reachabilityRef, &flags)){
return ((flags & kSCNetworkReachabilityFlagsConnectionRequired) &&
(flags & (kSCNetworkReachabilityFlagsConnectionOnTraffic | kSCNetworkReachabilityFlagsConnectionOnDemand)));
}
return NO;
}
// Is user intervention required?
- (BOOL)isInterventionRequired{
SCNetworkReachabilityFlags flags;
if (SCNetworkReachabilityGetFlags(self.reachabilityRef, &flags)){
return ((flags & kSCNetworkReachabilityFlagsConnectionRequired) &&
(flags & kSCNetworkReachabilityFlagsInterventionRequired));
}
return NO;
}
#pragma mark - reachability status stuff
- (NetworkStatus)currentReachabilityStatus{
if([self isReachable]){
if([self isReachableViaWiFi])
return ReachableViaWiFi;
#if TARGET_OS_IPHONE
return ReachableViaWWAN;
#endif
}
return NotReachable;
}
- (SCNetworkReachabilityFlags)reachabilityFlags{
SCNetworkReachabilityFlags flags = 0;
if(SCNetworkReachabilityGetFlags(self.reachabilityRef, &flags)){
return flags;
}
return 0;
}
- (NSString *)currentReachabilityString{
NetworkStatus temp = [self currentReachabilityStatus];
if(temp == ReachableViaWWAN){
// Updated for the fact that we have CDMA phones now!
return NSLocalizedString(@"Cellular", @"");
}
if (temp == ReachableViaWiFi){
return NSLocalizedString(@"WiFi", @"");
}
return NSLocalizedString(@"No Connection", @"");
}
- (NSString *)currentReachabilityFlags{
return reachabilityFlags([self reachabilityFlags]);
}
#pragma mark - Callback function calls this method
- (void)reachabilityChanged:(SCNetworkReachabilityFlags)flags{
if([self isReachableWithFlags:flags]){
if(self.reachableBlock){
self.reachableBlock(self);
}
}else{
if(self.unreachableBlock){
self.unreachableBlock(self);
}
}
if(self.reachabilityBlock){
self.reachabilityBlock(self, flags);
}
// this makes sure the change notification happens on the MAIN THREAD
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:kKNReachabilityChangedNotification
object:self];
});
}
@end