This commit is contained in:
2025-10-27 21:55:05 +08:00
parent d4ebfb9909
commit a6c077a42d
414 changed files with 55606 additions and 21 deletions

View File

@@ -0,0 +1,34 @@
//
// MJRefreshAutoFooter.h
// MJRefresh
//
// Created by MJ Lee on 15/4/24.
// Copyright (c) 2015年 小码哥. All rights reserved.
//
#if __has_include(<MJRefresh/MJRefreshFooter.h>)
#import <MJRefresh/MJRefreshFooter.h>
#else
#import "MJRefreshFooter.h"
#endif
NS_ASSUME_NONNULL_BEGIN
@interface MJRefreshAutoFooter : MJRefreshFooter
/** 是否自动刷新(默认为YES) */
@property (assign, nonatomic, getter=isAutomaticallyRefresh) BOOL automaticallyRefresh;
/** 当底部控件出现多少时就自动刷新(默认为1.0,也就是底部控件完全出现时,才会自动刷新) */
@property (assign, nonatomic) CGFloat appearencePercentTriggerAutoRefresh MJRefreshDeprecated("请使用triggerAutomaticallyRefreshPercent属性");
/** 当底部控件出现多少时就自动刷新(默认为1.0,也就是底部控件完全出现时,才会自动刷新) */
@property (assign, nonatomic) CGFloat triggerAutomaticallyRefreshPercent;
/** 自动触发次数, 默认为 1, 仅在拖拽 ScrollView 时才生效,
如果为 -1, 则为无限触发
*/
@property (nonatomic) NSInteger autoTriggerTimes;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,216 @@
//
// MJRefreshAutoFooter.m
// MJRefresh
//
// Created by MJ Lee on 15/4/24.
// Copyright (c) 2015 . All rights reserved.
//
#import "MJRefreshAutoFooter.h"
#import "NSBundle+MJRefresh.h"
#import "UIView+MJExtension.h"
#import "UIScrollView+MJExtension.h"
#import "UIScrollView+MJRefresh.h"
@interface MJRefreshAutoFooter()
/** */
@property (nonatomic) BOOL triggerByDrag;
@property (nonatomic) NSInteger leftTriggerTimes;
@end
@implementation MJRefreshAutoFooter
#pragma mark -
- (void)willMoveToSuperview:(UIView *)newSuperview
{
[super willMoveToSuperview:newSuperview];
if (newSuperview) { //
if (self.hidden == NO) {
self.scrollView.mj_insetB += self.mj_h;
}
//
self.mj_y = _scrollView.mj_contentH + self.ignoredScrollViewContentInsetBottom;
} else { //
if (self.hidden == NO) {
self.scrollView.mj_insetB -= self.mj_h;
}
}
}
#pragma mark -
- (void)setAppearencePercentTriggerAutoRefresh:(CGFloat)appearencePercentTriggerAutoRefresh
{
self.triggerAutomaticallyRefreshPercent = appearencePercentTriggerAutoRefresh;
}
- (CGFloat)appearencePercentTriggerAutoRefresh
{
return self.triggerAutomaticallyRefreshPercent;
}
#pragma mark -
- (void)prepare
{
[super prepare];
// 100%
self.triggerAutomaticallyRefreshPercent = 1.0;
//
self.automaticallyRefresh = YES;
self.autoTriggerTimes = 1;
}
- (void)scrollViewContentSizeDidChange:(NSDictionary *)change
{
[super scrollViewContentSizeDidChange:change];
CGSize size = [change[NSKeyValueChangeNewKey] CGSizeValue];
CGFloat contentHeight = size.height == 0 ? self.scrollView.mj_contentH : size.height;
//
CGFloat y = contentHeight + self.ignoredScrollViewContentInsetBottom;
if (self.mj_y != y) {
self.mj_y = y;
}
}
- (void)scrollViewContentOffsetDidChange:(NSDictionary *)change
{
[super scrollViewContentOffsetDidChange:change];
if (self.state != MJRefreshStateIdle || !self.automaticallyRefresh || self.mj_y == 0) return;
if (_scrollView.mj_insetT + _scrollView.mj_contentH > _scrollView.mj_h) { //
// _scrollView.mj_contentHself.mj_y
if (_scrollView.mj_offsetY >= _scrollView.mj_contentH - _scrollView.mj_h + self.mj_h * self.triggerAutomaticallyRefreshPercent + _scrollView.mj_insetB - self.mj_h) {
//
CGPoint old = [change[@"old"] CGPointValue];
CGPoint new = [change[@"new"] CGPointValue];
if (new.y <= old.y) return;
if (_scrollView.isDragging) {
self.triggerByDrag = YES;
}
//
[self beginRefreshing];
}
}
}
- (void)scrollViewPanStateDidChange:(NSDictionary *)change
{
[super scrollViewPanStateDidChange:change];
if (self.state != MJRefreshStateIdle) return;
UIGestureRecognizerState panState = _scrollView.panGestureRecognizer.state;
switch (panState) {
//
case UIGestureRecognizerStateEnded: {
if (_scrollView.mj_insetT + _scrollView.mj_contentH <= _scrollView.mj_h) { //
if (_scrollView.mj_offsetY >= - _scrollView.mj_insetT) { //
self.triggerByDrag = YES;
[self beginRefreshing];
}
} else { //
if (_scrollView.mj_offsetY >= _scrollView.mj_contentH + _scrollView.mj_insetB - _scrollView.mj_h) {
self.triggerByDrag = YES;
[self beginRefreshing];
}
}
}
break;
case UIGestureRecognizerStateBegan: {
[self resetTriggerTimes];
}
break;
default:
break;
}
}
- (BOOL)unlimitedTrigger {
return self.leftTriggerTimes == -1;
}
- (void)beginRefreshing
{
if (self.triggerByDrag && self.leftTriggerTimes <= 0 && !self.unlimitedTrigger) {
return;
}
[super beginRefreshing];
}
- (void)setState:(MJRefreshState)state
{
MJRefreshCheckState
if (state == MJRefreshStateRefreshing) {
[self executeRefreshingCallback];
} else if (state == MJRefreshStateNoMoreData || state == MJRefreshStateIdle) {
if (self.triggerByDrag) {
if (!self.unlimitedTrigger) {
self.leftTriggerTimes -= 1;
}
self.triggerByDrag = NO;
}
if (MJRefreshStateRefreshing == oldState) {
if (self.scrollView.pagingEnabled) {
CGPoint offset = self.scrollView.contentOffset;
offset.y -= self.scrollView.mj_insetB;
[UIView animateWithDuration:self.slowAnimationDuration animations:^{
self.scrollView.contentOffset = offset;
if (self.endRefreshingAnimationBeginAction) {
self.endRefreshingAnimationBeginAction();
}
} completion:^(BOOL finished) {
if (self.endRefreshingCompletionBlock) {
self.endRefreshingCompletionBlock();
}
}];
return;
}
if (self.endRefreshingCompletionBlock) {
self.endRefreshingCompletionBlock();
}
}
}
}
- (void)resetTriggerTimes {
self.leftTriggerTimes = self.autoTriggerTimes;
}
- (void)setHidden:(BOOL)hidden
{
BOOL lastHidden = self.isHidden;
[super setHidden:hidden];
if (!lastHidden && hidden) {
self.state = MJRefreshStateIdle;
self.scrollView.mj_insetB -= self.mj_h;
} else if (lastHidden && !hidden) {
self.scrollView.mj_insetB += self.mj_h;
//
self.mj_y = _scrollView.mj_contentH + self.ignoredScrollViewContentInsetBottom;
}
}
- (void)setAutoTriggerTimes:(NSInteger)autoTriggerTimes {
_autoTriggerTimes = autoTriggerTimes;
self.leftTriggerTimes = autoTriggerTimes;
}
@end

View File

@@ -0,0 +1,21 @@
//
// MJRefreshBackFooter.h
// MJRefresh
//
// Created by MJ Lee on 15/4/24.
// Copyright (c) 2015年 小码哥. All rights reserved.
//
#if __has_include(<MJRefresh/MJRefreshFooter.h>)
#import <MJRefresh/MJRefreshFooter.h>
#else
#import "MJRefreshFooter.h"
#endif
NS_ASSUME_NONNULL_BEGIN
@interface MJRefreshBackFooter : MJRefreshFooter
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,158 @@
//
// MJRefreshBackFooter.m
// MJRefresh
//
// Created by MJ Lee on 15/4/24.
// Copyright (c) 2015 . All rights reserved.
//
#import "MJRefreshBackFooter.h"
#import "NSBundle+MJRefresh.h"
#import "UIView+MJExtension.h"
#import "UIScrollView+MJExtension.h"
#import "UIScrollView+MJRefresh.h"
@interface MJRefreshBackFooter()
@property (assign, nonatomic) NSInteger lastRefreshCount;
@property (assign, nonatomic) CGFloat lastBottomDelta;
@end
@implementation MJRefreshBackFooter
#pragma mark -
- (void)willMoveToSuperview:(UIView *)newSuperview
{
[super willMoveToSuperview:newSuperview];
[self scrollViewContentSizeDidChange:nil];
}
#pragma mark -
- (void)scrollViewContentOffsetDidChange:(NSDictionary *)change
{
[super scrollViewContentOffsetDidChange:change];
//
if (self.state == MJRefreshStateRefreshing) return;
_scrollViewOriginalInset = self.scrollView.mj_inset;
// contentOffset
CGFloat currentOffsetY = self.scrollView.mj_offsetY;
// offsetY
CGFloat happenOffsetY = [self happenOffsetY];
//
if (currentOffsetY <= happenOffsetY) return;
CGFloat pullingPercent = (currentOffsetY - happenOffsetY) / self.mj_h;
// pullingPercent
if (self.state == MJRefreshStateNoMoreData) {
self.pullingPercent = pullingPercent;
return;
}
if (self.scrollView.isDragging) {
self.pullingPercent = pullingPercent;
//
CGFloat normal2pullingOffsetY = happenOffsetY + self.mj_h;
if (self.state == MJRefreshStateIdle && currentOffsetY > normal2pullingOffsetY) {
//
self.state = MJRefreshStatePulling;
} else if (self.state == MJRefreshStatePulling && currentOffsetY <= normal2pullingOffsetY) {
//
self.state = MJRefreshStateIdle;
}
} else if (self.state == MJRefreshStatePulling) {// &&
//
[self beginRefreshing];
} else if (pullingPercent < 1) {
self.pullingPercent = pullingPercent;
}
}
- (void)scrollViewContentSizeDidChange:(NSDictionary *)change
{
[super scrollViewContentSizeDidChange:change];
CGSize size = [change[NSKeyValueChangeNewKey] CGSizeValue];
CGFloat contentHeight = size.height == 0 ? self.scrollView.mj_contentH : size.height;
//
contentHeight += self.ignoredScrollViewContentInsetBottom;
//
CGFloat scrollHeight = self.scrollView.mj_h - self.scrollViewOriginalInset.top - self.scrollViewOriginalInset.bottom + self.ignoredScrollViewContentInsetBottom;
//
CGFloat y = MAX(contentHeight, scrollHeight);
if (self.mj_y != y) {
self.mj_y = y;
}
}
- (void)setState:(MJRefreshState)state
{
MJRefreshCheckState
//
if (state == MJRefreshStateNoMoreData || state == MJRefreshStateIdle) {
//
if (MJRefreshStateRefreshing == oldState) {
[UIView animateWithDuration:self.slowAnimationDuration animations:^{
if (self.endRefreshingAnimationBeginAction) {
self.endRefreshingAnimationBeginAction();
}
self.scrollView.mj_insetB -= self.lastBottomDelta;
//
if (self.isAutomaticallyChangeAlpha) self.alpha = 0.0;
} completion:^(BOOL finished) {
self.pullingPercent = 0.0;
if (self.endRefreshingCompletionBlock) {
self.endRefreshingCompletionBlock();
}
}];
}
CGFloat deltaH = [self heightForContentBreakView];
//
if (MJRefreshStateRefreshing == oldState && deltaH > 0 && self.scrollView.mj_totalDataCount != self.lastRefreshCount) {
self.scrollView.mj_offsetY = self.scrollView.mj_offsetY;
}
} else if (state == MJRefreshStateRefreshing) {
//
self.lastRefreshCount = self.scrollView.mj_totalDataCount;
[UIView animateWithDuration:self.fastAnimationDuration animations:^{
CGFloat bottom = self.mj_h + self.scrollViewOriginalInset.bottom;
CGFloat deltaH = [self heightForContentBreakView];
if (deltaH < 0) { // view
bottom -= deltaH;
}
self.lastBottomDelta = bottom - self.scrollView.mj_insetB;
self.scrollView.mj_insetB = bottom;
self.scrollView.mj_offsetY = [self happenOffsetY] + self.mj_h;
} completion:^(BOOL finished) {
[self executeRefreshingCallback];
}];
}
}
#pragma mark -
#pragma mark scrollView view
- (CGFloat)heightForContentBreakView
{
CGFloat h = self.scrollView.frame.size.height - self.scrollViewOriginalInset.bottom - self.scrollViewOriginalInset.top;
return self.scrollView.contentSize.height - h;
}
#pragma mark contentOffset.y
- (CGFloat)happenOffsetY
{
CGFloat deltaH = [self heightForContentBreakView];
if (deltaH > 0) {
return deltaH - self.scrollViewOriginalInset.top;
} else {
return - self.scrollViewOriginalInset.top;
}
}
@end

View File

@@ -0,0 +1,151 @@
// 代码地址: https://github.com/CoderMJLee/MJRefresh
// MJRefreshComponent.h
// MJRefresh
//
// Created by MJ Lee on 15/3/4.
// Copyright (c) 2015年 小码哥. All rights reserved.
// 刷新控件的基类
#import <UIKit/UIKit.h>
#if __has_include(<MJRefresh/MJRefreshConst.h>)
#import <MJRefresh/MJRefreshConst.h>
#else
#import "MJRefreshConst.h"
#endif
NS_ASSUME_NONNULL_BEGIN
/** 刷新控件的状态 */
typedef NS_ENUM(NSInteger, MJRefreshState) {
/** 普通闲置状态 */
MJRefreshStateIdle = 1,
/** 松开就可以进行刷新的状态 */
MJRefreshStatePulling,
/** 正在刷新中的状态 */
MJRefreshStateRefreshing,
/** 即将刷新的状态 */
MJRefreshStateWillRefresh,
/** 所有数据加载完毕,没有更多的数据了 */
MJRefreshStateNoMoreData
};
/** 进入刷新状态的回调 */
typedef void (^MJRefreshComponentRefreshingBlock)(void) MJRefreshDeprecated("first deprecated in 3.3.0 - Use `MJRefreshComponentAction` instead");
/** 开始刷新后的回调(进入刷新状态后的回调) */
typedef void (^MJRefreshComponentBeginRefreshingCompletionBlock)(void) MJRefreshDeprecated("first deprecated in 3.3.0 - Use `MJRefreshComponentAction` instead");
/** 结束刷新后的回调 */
typedef void (^MJRefreshComponentEndRefreshingCompletionBlock)(void) MJRefreshDeprecated("first deprecated in 3.3.0 - Use `MJRefreshComponentAction` instead");
/** 刷新用到的回调类型 */
typedef void (^MJRefreshComponentAction)(void);
/** 刷新控件的基类 */
@interface MJRefreshComponent : UIView
{
/** 记录scrollView刚开始的inset */
UIEdgeInsets _scrollViewOriginalInset;
/** 父控件 */
__weak UIScrollView *_scrollView;
}
#pragma mark - 刷新动画时间控制
/** 快速动画时间(一般用在刷新开始的回弹动画), 默认 0.25 */
@property (nonatomic) NSTimeInterval fastAnimationDuration;
/** 慢速动画时间(一般用在刷新结束后的回弹动画), 默认 0.4*/
@property (nonatomic) NSTimeInterval slowAnimationDuration;
/** 关闭全部默认动画效果, 可以简单粗暴地解决 CollectionView 的回弹动画 bug */
- (instancetype)setAnimationDisabled;
#pragma mark - 刷新回调
/** 正在刷新的回调 */
@property (copy, nonatomic, nullable) MJRefreshComponentAction refreshingBlock;
/** 设置回调对象和回调方法 */
- (void)setRefreshingTarget:(id)target refreshingAction:(SEL)action;
/** 回调对象 */
@property (weak, nonatomic) id refreshingTarget;
/** 回调方法 */
@property (assign, nonatomic) SEL refreshingAction;
/** 触发回调(交给子类去调用) */
- (void)executeRefreshingCallback;
#pragma mark - 刷新状态控制
/** 进入刷新状态 */
- (void)beginRefreshing;
- (void)beginRefreshingWithCompletionBlock:(void (^)(void))completionBlock;
/** 开始刷新后的回调(进入刷新状态后的回调) */
@property (copy, nonatomic, nullable) MJRefreshComponentAction beginRefreshingCompletionBlock;
/** 带动画的结束刷新的回调 */
@property (copy, nonatomic, nullable) MJRefreshComponentAction endRefreshingAnimateCompletionBlock MJRefreshDeprecated("first deprecated in 3.3.0 - Use `endRefreshingAnimationBeginAction` instead");
@property (copy, nonatomic, nullable) MJRefreshComponentAction endRefreshingAnimationBeginAction;
/** 结束刷新的回调 */
@property (copy, nonatomic, nullable) MJRefreshComponentAction endRefreshingCompletionBlock;
/** 结束刷新状态 */
- (void)endRefreshing;
- (void)endRefreshingWithCompletionBlock:(void (^)(void))completionBlock;
/** 是否正在刷新 */
@property (assign, nonatomic, readonly, getter=isRefreshing) BOOL refreshing;
/** 刷新状态 一般交给子类内部实现 */
@property (assign, nonatomic) MJRefreshState state;
#pragma mark - 交给子类去访问
/** 记录scrollView刚开始的inset */
@property (assign, nonatomic, readonly) UIEdgeInsets scrollViewOriginalInset;
/** 父控件 */
@property (weak, nonatomic, readonly) UIScrollView *scrollView;
#pragma mark - 交给子类们去实现
/** 初始化 */
- (void)prepare NS_REQUIRES_SUPER;
/** 摆放子控件frame */
- (void)placeSubviews NS_REQUIRES_SUPER;
/** 当scrollView的contentOffset发生改变的时候调用 */
- (void)scrollViewContentOffsetDidChange:(nullable NSDictionary *)change NS_REQUIRES_SUPER;
/** 当scrollView的contentSize发生改变的时候调用 */
- (void)scrollViewContentSizeDidChange:(nullable NSDictionary *)change NS_REQUIRES_SUPER;
/** 当scrollView的拖拽状态发生改变的时候调用 */
- (void)scrollViewPanStateDidChange:(nullable NSDictionary *)change NS_REQUIRES_SUPER;
/** 多语言配置 language 发生变化时调用
`MJRefreshConfig.defaultConfig.language` 发生改变时调用.
⚠️ 父类会调用 `placeSubviews` 方法, 请勿在 placeSubviews 中调用本方法, 造成死循环. 子类在需要重新布局时, 在配置完修改后, 最后再调用 super 方法, 否则可能导致配置修改后, 定位先于修改执行.
*/
- (void)i18nDidChange NS_REQUIRES_SUPER;
#pragma mark - 其他
/** 拉拽的百分比(交给子类重写) */
@property (assign, nonatomic) CGFloat pullingPercent;
/** 根据拖拽比例自动切换透明度 */
@property (assign, nonatomic, getter=isAutoChangeAlpha) BOOL autoChangeAlpha MJRefreshDeprecated("请使用automaticallyChangeAlpha属性");
/** 根据拖拽比例自动切换透明度 */
@property (assign, nonatomic, getter=isAutomaticallyChangeAlpha) BOOL automaticallyChangeAlpha;
@end
@interface UILabel(MJRefresh)
+ (instancetype)mj_label;
- (CGFloat)mj_textWidth;
@end
@interface MJRefreshComponent (ChainingGrammar)
#pragma mark - <<< 为 Swift 扩展链式语法 >>> -
/// 自动变化透明度
- (instancetype)autoChangeTransparency:(BOOL)isAutoChange;
/// 刷新开始后立即调用的回调
- (instancetype)afterBeginningAction:(MJRefreshComponentAction)action;
/// 刷新动画开始后立即调用的回调
- (instancetype)endingAnimationBeginningAction:(MJRefreshComponentAction)action;
/// 刷新结束后立即调用的回调
- (instancetype)afterEndingAction:(MJRefreshComponentAction)action;
/// 需要子类必须实现
/// @param scrollView 赋值给的 ScrollView 的 Header/Footer/Trailer
- (instancetype)linkTo:(UIScrollView *)scrollView;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,323 @@
// : https://github.com/CoderMJLee/MJRefresh
// MJRefreshComponent.m
// MJRefresh
//
// Created by MJ Lee on 15/3/4.
// Copyright (c) 2015 . All rights reserved.
//
#import "MJRefreshComponent.h"
#import "MJRefreshConst.h"
#import "MJRefreshConfig.h"
#import "UIView+MJExtension.h"
#import "UIScrollView+MJExtension.h"
#import "UIScrollView+MJRefresh.h"
#import "NSBundle+MJRefresh.h"
@interface MJRefreshComponent()
@property (strong, nonatomic) UIPanGestureRecognizer *pan;
@end
@implementation MJRefreshComponent
#pragma mark -
- (instancetype)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame]) {
//
[self prepare];
//
self.state = MJRefreshStateIdle;
self.fastAnimationDuration = 0.25;
self.slowAnimationDuration = 0.4;
}
return self;
}
- (void)prepare
{
//
self.autoresizingMask = UIViewAutoresizingFlexibleWidth;
self.backgroundColor = [UIColor clearColor];
}
- (void)layoutSubviews
{
[self placeSubviews];
[super layoutSubviews];
}
- (void)placeSubviews{}
- (void)willMoveToSuperview:(UIView *)newSuperview
{
[super willMoveToSuperview:newSuperview];
// UIScrollView
if (newSuperview && ![newSuperview isKindOfClass:[UIScrollView class]]) return;
//
[self removeObservers];
if (newSuperview) { //
// UIScrollView
_scrollView = (UIScrollView *)newSuperview;
//
self.mj_w = _scrollView.mj_w;
//
self.mj_x = -_scrollView.mj_insetL;
//
_scrollView.alwaysBounceVertical = YES;
// UIScrollViewcontentInset
_scrollViewOriginalInset = _scrollView.mj_inset;
//
[self addObservers];
}
}
- (void)drawRect:(CGRect)rect
{
[super drawRect:rect];
if (self.state == MJRefreshStateWillRefresh) {
// viewbeginRefreshing
self.state = MJRefreshStateRefreshing;
}
}
#pragma mark - KVO
- (void)addObservers
{
NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
[self.scrollView addObserver:self forKeyPath:MJRefreshKeyPathContentOffset options:options context:nil];
[self.scrollView addObserver:self forKeyPath:MJRefreshKeyPathContentSize options:options context:nil];
self.pan = self.scrollView.panGestureRecognizer;
[self.pan addObserver:self forKeyPath:MJRefreshKeyPathPanState options:options context:nil];
[NSNotificationCenter.defaultCenter addObserver:self selector:@selector(i18nDidChange) name:MJRefreshDidChangeLanguageNotification object:MJRefreshConfig.defaultConfig];
}
- (void)removeObservers
{
[self.superview removeObserver:self forKeyPath:MJRefreshKeyPathContentOffset];
[self.superview removeObserver:self forKeyPath:MJRefreshKeyPathContentSize];
[self.pan removeObserver:self forKeyPath:MJRefreshKeyPathPanState];
self.pan = nil;
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
//
if (!self.userInteractionEnabled) return;
//
if ([keyPath isEqualToString:MJRefreshKeyPathContentSize]) {
[self scrollViewContentSizeDidChange:change];
}
//
if (self.hidden) return;
if ([keyPath isEqualToString:MJRefreshKeyPathContentOffset]) {
[self scrollViewContentOffsetDidChange:change];
} else if ([keyPath isEqualToString:MJRefreshKeyPathPanState]) {
[self scrollViewPanStateDidChange:change];
}
}
- (void)scrollViewContentOffsetDidChange:(NSDictionary *)change{}
- (void)scrollViewContentSizeDidChange:(NSDictionary *)change{}
- (void)scrollViewPanStateDidChange:(NSDictionary *)change{}
- (void)i18nDidChange {
[self placeSubviews];
}
#pragma mark -
#pragma mark
- (void)setRefreshingTarget:(id)target refreshingAction:(SEL)action
{
self.refreshingTarget = target;
self.refreshingAction = action;
}
- (void)setState:(MJRefreshState)state
{
_state = state;
// setState:
MJRefreshDispatchAsyncOnMainQueue([self setNeedsLayout];)
}
#pragma mark
- (void)beginRefreshing
{
[UIView animateWithDuration:self.fastAnimationDuration animations:^{
self.alpha = 1.0;
}];
self.pullingPercent = 1.0;
//
if (self.window) {
self.state = MJRefreshStateRefreshing;
} else {
// 使header inset
if (self.state != MJRefreshStateRefreshing) {
self.state = MJRefreshStateWillRefresh;
// ()
[self setNeedsDisplay];
}
}
}
- (void)beginRefreshingWithCompletionBlock:(void (^)(void))completionBlock
{
self.beginRefreshingCompletionBlock = completionBlock;
[self beginRefreshing];
}
#pragma mark
- (void)endRefreshing
{
MJRefreshDispatchAsyncOnMainQueue(self.state = MJRefreshStateIdle;)
}
- (void)endRefreshingWithCompletionBlock:(void (^)(void))completionBlock
{
self.endRefreshingCompletionBlock = completionBlock;
[self endRefreshing];
}
#pragma mark
- (BOOL)isRefreshing
{
return self.state == MJRefreshStateRefreshing || self.state == MJRefreshStateWillRefresh;
}
#pragma mark
- (void)setAutoChangeAlpha:(BOOL)autoChangeAlpha
{
self.automaticallyChangeAlpha = autoChangeAlpha;
}
- (BOOL)isAutoChangeAlpha
{
return self.isAutomaticallyChangeAlpha;
}
- (void)setAutomaticallyChangeAlpha:(BOOL)automaticallyChangeAlpha
{
_automaticallyChangeAlpha = automaticallyChangeAlpha;
if (self.isRefreshing) return;
if (automaticallyChangeAlpha) {
self.alpha = self.pullingPercent;
} else {
self.alpha = 1.0;
}
}
#pragma mark
- (void)setPullingPercent:(CGFloat)pullingPercent
{
_pullingPercent = pullingPercent;
if (self.isRefreshing) return;
if (self.isAutomaticallyChangeAlpha) {
self.alpha = pullingPercent;
}
}
#pragma mark -
- (void)executeRefreshingCallback
{
if (self.refreshingBlock) {
self.refreshingBlock();
}
if ([self.refreshingTarget respondsToSelector:self.refreshingAction]) {
MJRefreshMsgSend(MJRefreshMsgTarget(self.refreshingTarget), self.refreshingAction, self);
}
if (self.beginRefreshingCompletionBlock) {
self.beginRefreshingCompletionBlock();
}
}
#pragma mark -
- (instancetype)setAnimationDisabled {
self.fastAnimationDuration = 0;
self.slowAnimationDuration = 0;
return self;
}
#pragma mark - <<< Deprecation compatible function >>> -
- (void)setEndRefreshingAnimateCompletionBlock:(MJRefreshComponentEndRefreshingCompletionBlock)endRefreshingAnimateCompletionBlock {
_endRefreshingAnimationBeginAction = endRefreshingAnimateCompletionBlock;
}
@end
@implementation UILabel(MJRefresh)
+ (instancetype)mj_label
{
UILabel *label = [[self alloc] init];
label.font = MJRefreshLabelFont;
label.textColor = MJRefreshLabelTextColor;
label.autoresizingMask = UIViewAutoresizingFlexibleWidth;
label.textAlignment = NSTextAlignmentCenter;
label.backgroundColor = [UIColor clearColor];
return label;
}
- (CGFloat)mj_textWidth {
CGFloat stringWidth = 0;
CGSize size = CGSizeMake(MAXFLOAT, MAXFLOAT);
if (self.attributedText) {
if (self.attributedText.length == 0) { return 0; }
stringWidth = [self.attributedText boundingRectWithSize:size
options:NSStringDrawingUsesLineFragmentOrigin
context:nil].size.width;
} else {
if (self.text.length == 0) { return 0; }
NSAssert(self.font != nil, @"请检查 mj_label's `font` 是否设置正确");
stringWidth = [self.text boundingRectWithSize:size
options:NSStringDrawingUsesLineFragmentOrigin
attributes:@{NSFontAttributeName:self.font}
context:nil].size.width;
}
return stringWidth;
}
@end
#pragma mark - <<< Swift >>> -
@implementation MJRefreshComponent (ChainingGrammar)
- (instancetype)autoChangeTransparency:(BOOL)isAutoChange {
self.automaticallyChangeAlpha = isAutoChange;
return self;
}
- (instancetype)afterBeginningAction:(MJRefreshComponentAction)action {
self.beginRefreshingCompletionBlock = action;
return self;
}
- (instancetype)endingAnimationBeginningAction:(MJRefreshComponentAction)action {
self.endRefreshingAnimationBeginAction = action;
return self;
}
- (instancetype)afterEndingAction:(MJRefreshComponentAction)action {
self.endRefreshingCompletionBlock = action;
return self;
}
- (instancetype)linkTo:(UIScrollView *)scrollView {
return self;
}
@end

View File

@@ -0,0 +1,37 @@
// 代码地址: https://github.com/CoderMJLee/MJRefresh
// MJRefreshFooter.h
// MJRefresh
//
// Created by MJ Lee on 15/3/5.
// Copyright (c) 2015年 小码哥. All rights reserved.
// 上拉刷新控件
#if __has_include(<MJRefresh/MJRefreshComponent.h>)
#import <MJRefresh/MJRefreshComponent.h>
#else
#import "MJRefreshComponent.h"
#endif
NS_ASSUME_NONNULL_BEGIN
@interface MJRefreshFooter : MJRefreshComponent
/** 创建footer */
+ (instancetype)footerWithRefreshingBlock:(MJRefreshComponentAction)refreshingBlock;
/** 创建footer */
+ (instancetype)footerWithRefreshingTarget:(id)target refreshingAction:(SEL)action;
/** 提示没有更多的数据 */
- (void)endRefreshingWithNoMoreData;
- (void)noticeNoMoreData MJRefreshDeprecated("使用endRefreshingWithNoMoreData");
/** 重置没有更多的数据(消除没有更多数据的状态) */
- (void)resetNoMoreData;
/** 忽略多少scrollView的contentInset的bottom */
@property (assign, nonatomic) CGFloat ignoredScrollViewContentInsetBottom;
/** 自动根据有无数据来显示和隐藏有数据就显示没有数据隐藏。默认是NO */
@property (assign, nonatomic, getter=isAutomaticallyHidden) BOOL automaticallyHidden MJRefreshDeprecated("已废弃此属性开发者请自行控制footer的显示和隐藏");
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,71 @@
// : https://github.com/CoderMJLee/MJRefresh
// MJRefreshFooter.m
// MJRefresh
//
// Created by MJ Lee on 15/3/5.
// Copyright (c) 2015 . All rights reserved.
//
#import "MJRefreshFooter.h"
#import "UIScrollView+MJRefresh.h"
#import "UIView+MJExtension.h"
@interface MJRefreshFooter()
@end
@implementation MJRefreshFooter
#pragma mark -
+ (instancetype)footerWithRefreshingBlock:(MJRefreshComponentAction)refreshingBlock
{
MJRefreshFooter *cmp = [[self alloc] init];
cmp.refreshingBlock = refreshingBlock;
return cmp;
}
+ (instancetype)footerWithRefreshingTarget:(id)target refreshingAction:(SEL)action
{
MJRefreshFooter *cmp = [[self alloc] init];
[cmp setRefreshingTarget:target refreshingAction:action];
return cmp;
}
#pragma mark -
- (void)prepare
{
[super prepare];
//
self.mj_h = MJRefreshFooterHeight;
//
// self.automaticallyHidden = NO;
}
#pragma mark . .
- (instancetype)linkTo:(UIScrollView *)scrollView {
scrollView.mj_footer = self;
return self;
}
#pragma mark -
- (void)endRefreshingWithNoMoreData
{
MJRefreshDispatchAsyncOnMainQueue(self.state = MJRefreshStateNoMoreData;)
}
- (void)noticeNoMoreData
{
[self endRefreshingWithNoMoreData];
}
- (void)resetNoMoreData
{
MJRefreshDispatchAsyncOnMainQueue(self.state = MJRefreshStateIdle;)
}
- (void)setAutomaticallyHidden:(BOOL)automaticallyHidden
{
_automaticallyHidden = automaticallyHidden;
}
@end

View File

@@ -0,0 +1,35 @@
// 代码地址: https://github.com/CoderMJLee/MJRefresh
// MJRefreshHeader.h
// MJRefresh
//
// Created by MJ Lee on 15/3/4.
// Copyright (c) 2015年 小码哥. All rights reserved.
// 下拉刷新控件:负责监控用户下拉的状态
#if __has_include(<MJRefresh/MJRefreshComponent.h>)
#import <MJRefresh/MJRefreshComponent.h>
#else
#import "MJRefreshComponent.h"
#endif
NS_ASSUME_NONNULL_BEGIN
@interface MJRefreshHeader : MJRefreshComponent
/** 创建header */
+ (instancetype)headerWithRefreshingBlock:(MJRefreshComponentAction)refreshingBlock;
/** 创建header */
+ (instancetype)headerWithRefreshingTarget:(id)target refreshingAction:(SEL)action;
/** 这个key用来存储上一次下拉刷新成功的时间 */
@property (copy, nonatomic) NSString *lastUpdatedTimeKey;
/** 上一次下拉刷新成功的时间 */
@property (strong, nonatomic, readonly, nullable) NSDate *lastUpdatedTime;
/** 忽略多少scrollView的contentInset的top */
@property (assign, nonatomic) CGFloat ignoredScrollViewContentInsetTop;
/** 默认是关闭状态, 如果遇到 CollectionView 的动画异常问题可以尝试打开 */
@property (nonatomic) BOOL isCollectionViewAnimationBug;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,297 @@
// : https://github.com/CoderMJLee/MJRefresh
// MJRefreshHeader.m
// MJRefresh
//
// Created by MJ Lee on 15/3/4.
// Copyright (c) 2015 . All rights reserved.
//
#import "MJRefreshHeader.h"
#import "UIView+MJExtension.h"
#import "UIScrollView+MJExtension.h"
#import "UIScrollView+MJRefresh.h"
NSString * const MJRefreshHeaderRefreshing2IdleBoundsKey = @"MJRefreshHeaderRefreshing2IdleBounds";
NSString * const MJRefreshHeaderRefreshingBoundsKey = @"MJRefreshHeaderRefreshingBounds";
@interface MJRefreshHeader() <CAAnimationDelegate>
@property (assign, nonatomic) CGFloat insetTDelta;
@end
@implementation MJRefreshHeader
#pragma mark -
+ (instancetype)headerWithRefreshingBlock:(MJRefreshComponentAction)refreshingBlock
{
MJRefreshHeader *cmp = [[self alloc] init];
cmp.refreshingBlock = refreshingBlock;
return cmp;
}
+ (instancetype)headerWithRefreshingTarget:(id)target refreshingAction:(SEL)action
{
MJRefreshHeader *cmp = [[self alloc] init];
[cmp setRefreshingTarget:target refreshingAction:action];
return cmp;
}
#pragma mark -
- (void)prepare
{
[super prepare];
// key
self.lastUpdatedTimeKey = MJRefreshHeaderLastUpdatedTimeKey;
//
self.mj_h = MJRefreshHeaderHeight;
}
- (void)placeSubviews
{
[super placeSubviews];
// y(YplaceSubviewsy)
self.mj_y = - self.mj_h - self.ignoredScrollViewContentInsetTop;
}
- (void)resetInset {
if (@available(iOS 11.0, *)) {
} else {
// iOS 10 , push VC, , Insets.top , resetInset,
if (!self.window) { return; }
}
// sectionheader
CGFloat insetT = - self.scrollView.mj_offsetY > _scrollViewOriginalInset.top ? - self.scrollView.mj_offsetY : _scrollViewOriginalInset.top;
insetT = insetT > self.mj_h + _scrollViewOriginalInset.top ? self.mj_h + _scrollViewOriginalInset.top : insetT;
self.insetTDelta = _scrollViewOriginalInset.top - insetT;
// CollectionView 使 Autolayout Cell, Layout
if (fabs(self.scrollView.mj_insetT - insetT) > FLT_EPSILON) {
self.scrollView.mj_insetT = insetT;
}
}
- (void)scrollViewContentOffsetDidChange:(NSDictionary *)change
{
[super scrollViewContentOffsetDidChange:change];
// refreshing
if (self.state == MJRefreshStateRefreshing) {
[self resetInset];
return;
}
// contentInset
_scrollViewOriginalInset = self.scrollView.mj_inset;
// contentOffset
CGFloat offsetY = self.scrollView.mj_offsetY;
// offsetY
CGFloat happenOffsetY = - self.scrollViewOriginalInset.top;
//
// >= -> >
if (offsetY > happenOffsetY) return;
//
CGFloat normal2pullingOffsetY = happenOffsetY - self.mj_h;
CGFloat pullingPercent = (happenOffsetY - offsetY) / self.mj_h;
if (self.scrollView.isDragging) { //
self.pullingPercent = pullingPercent;
if (self.state == MJRefreshStateIdle && offsetY < normal2pullingOffsetY) {
//
self.state = MJRefreshStatePulling;
} else if (self.state == MJRefreshStatePulling && offsetY >= normal2pullingOffsetY) {
//
self.state = MJRefreshStateIdle;
}
} else if (self.state == MJRefreshStatePulling) {// &&
//
[self beginRefreshing];
} else if (pullingPercent < 1) {
self.pullingPercent = pullingPercent;
}
}
- (void)setState:(MJRefreshState)state
{
MJRefreshCheckState
//
if (state == MJRefreshStateIdle) {
if (oldState != MJRefreshStateRefreshing) return;
[self headerEndingAction];
} else if (state == MJRefreshStateRefreshing) {
[self headerRefreshingAction];
}
}
- (void)headerEndingAction {
//
[[NSUserDefaults standardUserDefaults] setObject:[NSDate date] forKey:self.lastUpdatedTimeKey];
[[NSUserDefaults standardUserDefaults] synchronize];
// 使 UIViewAnimation
if (!self.isCollectionViewAnimationBug) {
// insetoffset
[UIView animateWithDuration:self.slowAnimationDuration animations:^{
self.scrollView.mj_insetT += self.insetTDelta;
if (self.endRefreshingAnimationBeginAction) {
self.endRefreshingAnimationBeginAction();
}
//
if (self.isAutomaticallyChangeAlpha) self.alpha = 0.0;
} completion:^(BOOL finished) {
self.pullingPercent = 0.0;
if (self.endRefreshingCompletionBlock) {
self.endRefreshingCompletionBlock();
}
}];
return;
}
/**
https://github.com/CoderMJLee/MJRefresh/pull/844
+ [UIView animateWithDuration: animations:]contentInset
fix issue#225 https://github.com/CoderMJLee/MJRefresh/issues/225
pull#737 https://github.com/CoderMJLee/MJRefresh/pull/737
, Refreshing .
*/
// Inset self.pullingPercent self.alpha, alpha , alpha
CGFloat viewAlpha = self.alpha;
self.scrollView.mj_insetT += self.insetTDelta;
// , .
self.scrollView.userInteractionEnabled = NO;
//CAAnimation keyPath contentInset Bounds
CABasicAnimation *boundsAnimation = [CABasicAnimation animationWithKeyPath:@"bounds"];
boundsAnimation.fromValue = [NSValue valueWithCGRect:CGRectOffset(self.scrollView.bounds, 0, self.insetTDelta)];
boundsAnimation.duration = self.slowAnimationDuration;
//delegate
boundsAnimation.removedOnCompletion = NO;
boundsAnimation.fillMode = kCAFillModeBoth;
boundsAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
boundsAnimation.delegate = self;
[boundsAnimation setValue:MJRefreshHeaderRefreshing2IdleBoundsKey forKey:@"identity"];
[self.scrollView.layer addAnimation:boundsAnimation forKey:MJRefreshHeaderRefreshing2IdleBoundsKey];
if (self.endRefreshingAnimationBeginAction) {
self.endRefreshingAnimationBeginAction();
}
//
if (self.isAutomaticallyChangeAlpha) {
CABasicAnimation *opacityAnimation = [CABasicAnimation animationWithKeyPath:@"opacity"];
opacityAnimation.fromValue = @(viewAlpha);
opacityAnimation.toValue = @(0.0);
opacityAnimation.duration = self.slowAnimationDuration;
opacityAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
[self.layer addAnimation:opacityAnimation forKey:@"MJRefreshHeaderRefreshing2IdleOpacity"];
// inset , pullingPercent , alpha 0 . 0, , , 0.
self.alpha = 0;
}
}
- (void)headerRefreshingAction {
// 使 UIViewAnimation
if (!self.isCollectionViewAnimationBug) {
[UIView animateWithDuration:self.fastAnimationDuration animations:^{
if (self.scrollView.panGestureRecognizer.state != UIGestureRecognizerStateCancelled) {
CGFloat top = self.scrollViewOriginalInset.top + self.mj_h;
// top
self.scrollView.mj_insetT = top;
//
CGPoint offset = self.scrollView.contentOffset;
offset.y = -top;
[self.scrollView setContentOffset:offset animated:NO];
}
} completion:^(BOOL finished) {
[self executeRefreshingCallback];
}];
return;
}
if (self.scrollView.panGestureRecognizer.state != UIGestureRecognizerStateCancelled) {
CGFloat top = self.scrollViewOriginalInset.top + self.mj_h;
// , .
self.scrollView.userInteractionEnabled = NO;
// CAAnimation keyPath contentOffset Bounds
CABasicAnimation *boundsAnimation = [CABasicAnimation animationWithKeyPath:@"bounds"];
CGRect bounds = self.scrollView.bounds;
bounds.origin.y = -top;
boundsAnimation.fromValue = [NSValue valueWithCGRect:self.scrollView.bounds];
boundsAnimation.toValue = [NSValue valueWithCGRect:bounds];
boundsAnimation.duration = self.fastAnimationDuration;
//delegate
boundsAnimation.removedOnCompletion = NO;
boundsAnimation.fillMode = kCAFillModeBoth;
boundsAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
boundsAnimation.delegate = self;
[boundsAnimation setValue:MJRefreshHeaderRefreshingBoundsKey forKey:@"identity"];
[self.scrollView.layer addAnimation:boundsAnimation forKey:MJRefreshHeaderRefreshingBoundsKey];
} else {
[self executeRefreshingCallback];
}
}
#pragma mark . .
- (instancetype)linkTo:(UIScrollView *)scrollView {
scrollView.mj_header = self;
return self;
}
#pragma mark - CAAnimationDelegate
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag {
NSString *identity = [anim valueForKey:@"identity"];
if ([identity isEqualToString:MJRefreshHeaderRefreshing2IdleBoundsKey]) {
self.pullingPercent = 0.0;
self.scrollView.userInteractionEnabled = YES;
if (self.endRefreshingCompletionBlock) {
self.endRefreshingCompletionBlock();
}
} else if ([identity isEqualToString:MJRefreshHeaderRefreshingBoundsKey]) {
// end Refreshing
if (self.state != MJRefreshStateIdle) {
CGFloat top = self.scrollViewOriginalInset.top + self.mj_h;
self.scrollView.mj_insetT = top;
//
CGPoint offset = self.scrollView.contentOffset;
offset.y = -top;
[self.scrollView setContentOffset:offset animated:NO];
}
self.scrollView.userInteractionEnabled = YES;
[self executeRefreshingCallback];
}
if ([self.scrollView.layer animationForKey:MJRefreshHeaderRefreshing2IdleBoundsKey]) {
[self.scrollView.layer removeAnimationForKey:MJRefreshHeaderRefreshing2IdleBoundsKey];
}
if ([self.scrollView.layer animationForKey:MJRefreshHeaderRefreshingBoundsKey]) {
[self.scrollView.layer removeAnimationForKey:MJRefreshHeaderRefreshingBoundsKey];
}
}
#pragma mark -
- (NSDate *)lastUpdatedTime
{
return [[NSUserDefaults standardUserDefaults] objectForKey:self.lastUpdatedTimeKey];
}
- (void)setIgnoredScrollViewContentInsetTop:(CGFloat)ignoredScrollViewContentInsetTop {
_ignoredScrollViewContentInsetTop = ignoredScrollViewContentInsetTop;
self.mj_y = - self.mj_h - _ignoredScrollViewContentInsetTop;
}
@end

View File

@@ -0,0 +1,30 @@
//
// MJRefreshTrailer.h
// MJRefresh
//
// Created by kinarobin on 2020/5/3.
// Copyright © 2020 小码哥. All rights reserved.
//
#if __has_include(<MJRefresh/MJRefreshComponent.h>)
#import <MJRefresh/MJRefreshComponent.h>
#else
#import "MJRefreshComponent.h"
#endif
NS_ASSUME_NONNULL_BEGIN
@interface MJRefreshTrailer : MJRefreshComponent
/** 创建trailer*/
+ (instancetype)trailerWithRefreshingBlock:(MJRefreshComponentAction)refreshingBlock;
/** 创建trailer */
+ (instancetype)trailerWithRefreshingTarget:(id)target refreshingAction:(SEL)action;
/** 忽略多少scrollView的contentInset的right */
@property (assign, nonatomic) CGFloat ignoredScrollViewContentInsetRight;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,179 @@
//
// MJRefreshTrailer.m
// MJRefresh
//
// Created by kinarobin on 2020/5/3.
// Copyright © 2020 . All rights reserved.
//
#import "MJRefreshTrailer.h"
#import "UIView+MJExtension.h"
#import "UIScrollView+MJRefresh.h"
#import "UIScrollView+MJExtension.h"
@interface MJRefreshTrailer()
@property (assign, nonatomic) NSInteger lastRefreshCount;
@property (assign, nonatomic) CGFloat lastRightDelta;
@end
@implementation MJRefreshTrailer
#pragma mark -
+ (instancetype)trailerWithRefreshingBlock:(MJRefreshComponentAction)refreshingBlock {
MJRefreshTrailer *cmp = [[self alloc] init];
cmp.refreshingBlock = refreshingBlock;
return cmp;
}
+ (instancetype)trailerWithRefreshingTarget:(id)target refreshingAction:(SEL)action {
MJRefreshTrailer *cmp = [[self alloc] init];
[cmp setRefreshingTarget:target refreshingAction:action];
return cmp;
}
- (void)scrollViewContentOffsetDidChange:(NSDictionary *)change {
[super scrollViewContentOffsetDidChange:change];
//
if (self.state == MJRefreshStateRefreshing) return;
_scrollViewOriginalInset = self.scrollView.mj_inset;
// contentOffset
CGFloat currentOffsetX = self.scrollView.mj_offsetX;
// offsetX
CGFloat happenOffsetX = [self happenOffsetX];
//
if (currentOffsetX <= happenOffsetX) return;
CGFloat pullingPercent = (currentOffsetX - happenOffsetX) / self.mj_w;
// pullingPercent
if (self.state == MJRefreshStateNoMoreData) {
self.pullingPercent = pullingPercent;
return;
}
if (self.scrollView.isDragging) {
self.pullingPercent = pullingPercent;
//
CGFloat normal2pullingOffsetX = happenOffsetX + self.mj_w;
if (self.state == MJRefreshStateIdle && currentOffsetX > normal2pullingOffsetX) {
self.state = MJRefreshStatePulling;
} else if (self.state == MJRefreshStatePulling && currentOffsetX <= normal2pullingOffsetX) {
//
self.state = MJRefreshStateIdle;
}
} else if (self.state == MJRefreshStatePulling) {// &&
//
[self beginRefreshing];
} else if (pullingPercent < 1) {
self.pullingPercent = pullingPercent;
}
}
- (void)setState:(MJRefreshState)state {
MJRefreshCheckState
//
if (state == MJRefreshStateNoMoreData || state == MJRefreshStateIdle) {
//
if (MJRefreshStateRefreshing == oldState) {
[UIView animateWithDuration:self.slowAnimationDuration animations:^{
if (self.endRefreshingAnimationBeginAction) {
self.endRefreshingAnimationBeginAction();
}
self.scrollView.mj_insetR -= self.lastRightDelta;
//
if (self.isAutomaticallyChangeAlpha) self.alpha = 0.0;
} completion:^(BOOL finished) {
self.pullingPercent = 0.0;
if (self.endRefreshingCompletionBlock) {
self.endRefreshingCompletionBlock();
}
}];
}
CGFloat deltaW = [self widthForContentBreakView];
//
if (MJRefreshStateRefreshing == oldState && deltaW > 0 && self.scrollView.mj_totalDataCount != self.lastRefreshCount) {
self.scrollView.mj_offsetX = self.scrollView.mj_offsetX;
}
} else if (state == MJRefreshStateRefreshing) {
//
self.lastRefreshCount = self.scrollView.mj_totalDataCount;
[UIView animateWithDuration:self.fastAnimationDuration animations:^{
CGFloat right = self.mj_w + self.scrollViewOriginalInset.right;
CGFloat deltaW = [self widthForContentBreakView];
if (deltaW < 0) { // view
right -= deltaW;
}
self.lastRightDelta = right - self.scrollView.mj_insetR;
self.scrollView.mj_insetR = right;
//
CGPoint offset = self.scrollView.contentOffset;
offset.x = [self happenOffsetX] + self.mj_w;
[self.scrollView setContentOffset:offset animated:NO];
} completion:^(BOOL finished) {
[self executeRefreshingCallback];
}];
}
}
- (void)scrollViewContentSizeDidChange:(NSDictionary *)change {
[super scrollViewContentSizeDidChange:change];
//
CGFloat contentWidth = self.scrollView.mj_contentW + self.ignoredScrollViewContentInsetRight;
//
CGFloat scrollWidth = self.scrollView.mj_w - self.scrollViewOriginalInset.left - self.scrollViewOriginalInset.right + self.ignoredScrollViewContentInsetRight;
//
self.mj_x = MAX(contentWidth, scrollWidth);
}
- (void)placeSubviews {
[super placeSubviews];
self.mj_h = _scrollView.mj_h;
//
self.mj_w = MJRefreshTrailWidth;
}
- (void)willMoveToSuperview:(UIView *)newSuperview {
[super willMoveToSuperview:newSuperview];
if (newSuperview) {
//
_scrollView.alwaysBounceHorizontal = YES;
_scrollView.alwaysBounceVertical = NO;
}
}
#pragma mark . .
- (instancetype)linkTo:(UIScrollView *)scrollView {
scrollView.mj_trailer = self;
return self;
}
#pragma mark - contentOffset.x
- (CGFloat)happenOffsetX {
CGFloat deltaW = [self widthForContentBreakView];
if (deltaW > 0) {
return deltaW - self.scrollViewOriginalInset.left;
} else {
return - self.scrollViewOriginalInset.left;
}
}
#pragma mark scrollView view
- (CGFloat)widthForContentBreakView {
CGFloat w = self.scrollView.frame.size.width - self.scrollViewOriginalInset.right - self.scrollViewOriginalInset.left;
return self.scrollView.contentSize.width - w;
}
@end

View File

@@ -0,0 +1,25 @@
//
// MJRefreshAutoGifFooter.h
// MJRefresh
//
// Created by MJ Lee on 15/4/24.
// Copyright (c) 2015年 小码哥. All rights reserved.
//
#if __has_include(<MJRefresh/MJRefreshAutoStateFooter.h>)
#import <MJRefresh/MJRefreshAutoStateFooter.h>
#else
#import "MJRefreshAutoStateFooter.h"
#endif
NS_ASSUME_NONNULL_BEGIN
@interface MJRefreshAutoGifFooter : MJRefreshAutoStateFooter
@property (weak, nonatomic, readonly) UIImageView *gifView;
/** 设置state状态下的动画图片images 动画持续时间duration*/
- (instancetype)setImages:(NSArray *)images duration:(NSTimeInterval)duration forState:(MJRefreshState)state;
- (instancetype)setImages:(NSArray *)images forState:(MJRefreshState)state;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,121 @@
//
// MJRefreshAutoGifFooter.m
// MJRefresh
//
// Created by MJ Lee on 15/4/24.
// Copyright (c) 2015 . All rights reserved.
//
#import "MJRefreshAutoGifFooter.h"
#import "NSBundle+MJRefresh.h"
#import "UIView+MJExtension.h"
#import "UIScrollView+MJExtension.h"
#import "UIScrollView+MJRefresh.h"
@interface MJRefreshAutoGifFooter()
{
__unsafe_unretained UIImageView *_gifView;
}
/** */
@property (strong, nonatomic) NSMutableDictionary *stateImages;
/** */
@property (strong, nonatomic) NSMutableDictionary *stateDurations;
@end
@implementation MJRefreshAutoGifFooter
#pragma mark -
- (UIImageView *)gifView
{
if (!_gifView) {
UIImageView *gifView = [[UIImageView alloc] init];
[self addSubview:_gifView = gifView];
}
return _gifView;
}
- (NSMutableDictionary *)stateImages
{
if (!_stateImages) {
self.stateImages = [NSMutableDictionary dictionary];
}
return _stateImages;
}
- (NSMutableDictionary *)stateDurations
{
if (!_stateDurations) {
self.stateDurations = [NSMutableDictionary dictionary];
}
return _stateDurations;
}
#pragma mark -
- (instancetype)setImages:(NSArray *)images duration:(NSTimeInterval)duration forState:(MJRefreshState)state
{
if (images == nil) return self;
self.stateImages[@(state)] = images;
self.stateDurations[@(state)] = @(duration);
/* */
UIImage *image = [images firstObject];
if (image.size.height > self.mj_h) {
self.mj_h = image.size.height;
}
return self;
}
- (instancetype)setImages:(NSArray *)images forState:(MJRefreshState)state
{
return [self setImages:images duration:images.count * 0.1 forState:state];
}
#pragma mark -
- (void)prepare
{
[super prepare];
//
self.labelLeftInset = 20;
}
- (void)placeSubviews
{
[super placeSubviews];
if (self.gifView.constraints.count) return;
self.gifView.frame = self.bounds;
if (self.isRefreshingTitleHidden) {
self.gifView.contentMode = UIViewContentModeCenter;
} else {
self.gifView.contentMode = UIViewContentModeRight;
self.gifView.mj_w = self.mj_w * 0.5 - self.labelLeftInset - self.stateLabel.mj_textWidth * 0.5;
}
}
- (void)setState:(MJRefreshState)state
{
MJRefreshCheckState
//
if (state == MJRefreshStateRefreshing) {
NSArray *images = self.stateImages[@(state)];
if (images.count == 0) return;
[self.gifView stopAnimating];
self.gifView.hidden = NO;
if (images.count == 1) { //
self.gifView.image = [images lastObject];
} else { //
self.gifView.animationImages = images;
self.gifView.animationDuration = [self.stateDurations[@(state)] doubleValue];
[self.gifView startAnimating];
}
} else if (state == MJRefreshStateNoMoreData || state == MJRefreshStateIdle) {
[self.gifView stopAnimating];
self.gifView.hidden = YES;
}
}
@end

View File

@@ -0,0 +1,25 @@
//
// MJRefreshAutoNormalFooter.h
// MJRefresh
//
// Created by MJ Lee on 15/4/24.
// Copyright (c) 2015年 小码哥. All rights reserved.
//
#if __has_include(<MJRefresh/MJRefreshAutoStateFooter.h>)
#import <MJRefresh/MJRefreshAutoStateFooter.h>
#else
#import "MJRefreshAutoStateFooter.h"
#endif
NS_ASSUME_NONNULL_BEGIN
@interface MJRefreshAutoNormalFooter : MJRefreshAutoStateFooter
@property (weak, nonatomic, readonly) UIActivityIndicatorView *loadingView;
/** 菊花的样式 */
@property (assign, nonatomic) UIActivityIndicatorViewStyle activityIndicatorViewStyle MJRefreshDeprecated("first deprecated in 3.2.2 - Use `loadingView` property");
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,81 @@
//
// MJRefreshAutoNormalFooter.m
// MJRefresh
//
// Created by MJ Lee on 15/4/24.
// Copyright (c) 2015 . All rights reserved.
//
#import "MJRefreshAutoNormalFooter.h"
#import "NSBundle+MJRefresh.h"
#import "UIView+MJExtension.h"
#import "UIScrollView+MJExtension.h"
#import "UIScrollView+MJRefresh.h"
@interface MJRefreshAutoNormalFooter()
@property (weak, nonatomic) UIActivityIndicatorView *loadingView;
@end
@implementation MJRefreshAutoNormalFooter
#pragma mark -
- (UIActivityIndicatorView *)loadingView
{
if (!_loadingView) {
UIActivityIndicatorView *loadingView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:_activityIndicatorViewStyle];
loadingView.hidesWhenStopped = YES;
[self addSubview:_loadingView = loadingView];
}
return _loadingView;
}
- (void)setActivityIndicatorViewStyle:(UIActivityIndicatorViewStyle)activityIndicatorViewStyle
{
_activityIndicatorViewStyle = activityIndicatorViewStyle;
[self.loadingView removeFromSuperview];
self.loadingView = nil;
[self setNeedsLayout];
}
#pragma mark -
- (void)prepare
{
[super prepare];
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000
if (@available(iOS 13.0, *)) {
_activityIndicatorViewStyle = UIActivityIndicatorViewStyleMedium;
return;
}
#endif
_activityIndicatorViewStyle = UIActivityIndicatorViewStyleGray;
}
- (void)placeSubviews
{
[super placeSubviews];
if (self.loadingView.constraints.count) return;
//
CGFloat loadingCenterX = self.mj_w * 0.5;
if (!self.isRefreshingTitleHidden) {
loadingCenterX -= self.stateLabel.mj_textWidth * 0.5 + self.labelLeftInset;
}
CGFloat loadingCenterY = self.mj_h * 0.5;
self.loadingView.center = CGPointMake(loadingCenterX, loadingCenterY);
}
- (void)setState:(MJRefreshState)state
{
MJRefreshCheckState
//
if (state == MJRefreshStateNoMoreData || state == MJRefreshStateIdle) {
[self.loadingView stopAnimating];
} else if (state == MJRefreshStateRefreshing) {
[self.loadingView startAnimating];
}
}
@end

View File

@@ -0,0 +1,30 @@
//
// MJRefreshAutoStateFooter.h
// MJRefresh
//
// Created by MJ Lee on 15/6/13.
// Copyright © 2015年 小码哥. All rights reserved.
//
#if __has_include(<MJRefresh/MJRefreshAutoFooter.h>)
#import <MJRefresh/MJRefreshAutoFooter.h>
#else
#import "MJRefreshAutoFooter.h"
#endif
NS_ASSUME_NONNULL_BEGIN
@interface MJRefreshAutoStateFooter : MJRefreshAutoFooter
/** 文字距离圈圈、箭头的距离 */
@property (assign, nonatomic) CGFloat labelLeftInset;
/** 显示刷新状态的label */
@property (weak, nonatomic, readonly) UILabel *stateLabel;
/** 设置state状态下的文字 */
- (instancetype)setTitle:(NSString *)title forState:(MJRefreshState)state;
/** 隐藏刷新状态的文字 */
@property (assign, nonatomic, getter=isRefreshingTitleHidden) BOOL refreshingTitleHidden;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,119 @@
//
// MJRefreshAutoStateFooter.m
// MJRefresh
//
// Created by MJ Lee on 15/6/13.
// Copyright © 2015 . All rights reserved.
//
#import "MJRefreshAutoStateFooter.h"
#import "NSBundle+MJRefresh.h"
@interface MJRefreshAutoFooter (TapTriggerFix)
- (void)beginRefreshingWithoutValidation;
@end
@implementation MJRefreshAutoFooter (TapTriggerFix)
- (void)beginRefreshingWithoutValidation {
[super beginRefreshing];
}
@end
@interface MJRefreshAutoStateFooter()
{
/** label */
__unsafe_unretained UILabel *_stateLabel;
}
/** */
@property (strong, nonatomic) NSMutableDictionary *stateTitles;
@end
@implementation MJRefreshAutoStateFooter
#pragma mark -
- (NSMutableDictionary *)stateTitles
{
if (!_stateTitles) {
self.stateTitles = [NSMutableDictionary dictionary];
}
return _stateTitles;
}
- (UILabel *)stateLabel
{
if (!_stateLabel) {
[self addSubview:_stateLabel = [UILabel mj_label]];
}
return _stateLabel;
}
#pragma mark -
- (instancetype)setTitle:(NSString *)title forState:(MJRefreshState)state
{
if (title == nil) return self;
self.stateTitles[@(state)] = title;
self.stateLabel.text = self.stateTitles[@(self.state)];
return self;
}
#pragma mark -
- (void)stateLabelClick
{
if (self.state == MJRefreshStateIdle) {
[super beginRefreshingWithoutValidation];
}
}
- (void)textConfiguration {
//
[self setTitle:[NSBundle mj_localizedStringForKey:MJRefreshAutoFooterIdleText] forState:MJRefreshStateIdle];
[self setTitle:[NSBundle mj_localizedStringForKey:MJRefreshAutoFooterRefreshingText] forState:MJRefreshStateRefreshing];
[self setTitle:[NSBundle mj_localizedStringForKey:MJRefreshAutoFooterNoMoreDataText] forState:MJRefreshStateNoMoreData];
}
#pragma mark -
- (void)prepare
{
[super prepare];
//
self.labelLeftInset = MJRefreshLabelLeftInset;
[self textConfiguration];
// label
self.stateLabel.userInteractionEnabled = YES;
[self.stateLabel addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(stateLabelClick)]];
}
- (void)i18nDidChange {
[self textConfiguration];
[super i18nDidChange];
}
- (void)placeSubviews
{
[super placeSubviews];
if (self.stateLabel.constraints.count) return;
//
self.stateLabel.frame = self.bounds;
}
- (void)setState:(MJRefreshState)state
{
MJRefreshCheckState
if (self.isRefreshingTitleHidden && state == MJRefreshStateRefreshing) {
self.stateLabel.text = nil;
} else {
self.stateLabel.text = self.stateTitles[@(state)];
}
}
@end

View File

@@ -0,0 +1,25 @@
//
// MJRefreshBackGifFooter.h
// MJRefresh
//
// Created by MJ Lee on 15/4/24.
// Copyright (c) 2015年 小码哥. All rights reserved.
//
#if __has_include(<MJRefresh/MJRefreshBackStateFooter.h>)
#import <MJRefresh/MJRefreshBackStateFooter.h>
#else
#import "MJRefreshBackStateFooter.h"
#endif
NS_ASSUME_NONNULL_BEGIN
@interface MJRefreshBackGifFooter : MJRefreshBackStateFooter
@property (weak, nonatomic, readonly) UIImageView *gifView;
/** 设置state状态下的动画图片images 动画持续时间duration*/
- (instancetype)setImages:(NSArray *)images duration:(NSTimeInterval)duration forState:(MJRefreshState)state;
- (instancetype)setImages:(NSArray *)images forState:(MJRefreshState)state;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,132 @@
//
// MJRefreshBackGifFooter.m
// MJRefresh
//
// Created by MJ Lee on 15/4/24.
// Copyright (c) 2015 . All rights reserved.
//
#import "MJRefreshBackGifFooter.h"
#import "NSBundle+MJRefresh.h"
#import "UIView+MJExtension.h"
#import "UIScrollView+MJExtension.h"
#import "UIScrollView+MJRefresh.h"
@interface MJRefreshBackGifFooter()
{
__unsafe_unretained UIImageView *_gifView;
}
/** */
@property (strong, nonatomic) NSMutableDictionary *stateImages;
/** */
@property (strong, nonatomic) NSMutableDictionary *stateDurations;
@end
@implementation MJRefreshBackGifFooter
#pragma mark -
- (UIImageView *)gifView
{
if (!_gifView) {
UIImageView *gifView = [[UIImageView alloc] init];
[self addSubview:_gifView = gifView];
}
return _gifView;
}
- (NSMutableDictionary *)stateImages
{
if (!_stateImages) {
self.stateImages = [NSMutableDictionary dictionary];
}
return _stateImages;
}
- (NSMutableDictionary *)stateDurations
{
if (!_stateDurations) {
self.stateDurations = [NSMutableDictionary dictionary];
}
return _stateDurations;
}
#pragma mark -
- (instancetype)setImages:(NSArray *)images duration:(NSTimeInterval)duration forState:(MJRefreshState)state
{
if (images == nil) return self;
self.stateImages[@(state)] = images;
self.stateDurations[@(state)] = @(duration);
/* */
UIImage *image = [images firstObject];
if (image.size.height > self.mj_h) {
self.mj_h = image.size.height;
}
return self;
}
- (instancetype)setImages:(NSArray *)images forState:(MJRefreshState)state
{
return [self setImages:images duration:images.count * 0.1 forState:state];
}
#pragma mark -
- (void)prepare
{
[super prepare];
//
self.labelLeftInset = 20;
}
- (void)setPullingPercent:(CGFloat)pullingPercent
{
[super setPullingPercent:pullingPercent];
NSArray *images = self.stateImages[@(MJRefreshStateIdle)];
if (self.state != MJRefreshStateIdle || images.count == 0) return;
[self.gifView stopAnimating];
NSUInteger index = images.count * pullingPercent;
if (index >= images.count) index = images.count - 1;
self.gifView.image = images[index];
}
- (void)placeSubviews
{
[super placeSubviews];
if (self.gifView.constraints.count) return;
self.gifView.frame = self.bounds;
if (self.stateLabel.hidden) {
self.gifView.contentMode = UIViewContentModeCenter;
} else {
self.gifView.contentMode = UIViewContentModeRight;
self.gifView.mj_w = self.mj_w * 0.5 - self.labelLeftInset - self.stateLabel.mj_textWidth * 0.5;
}
}
- (void)setState:(MJRefreshState)state
{
MJRefreshCheckState
//
if (state == MJRefreshStatePulling || state == MJRefreshStateRefreshing) {
NSArray *images = self.stateImages[@(state)];
if (images.count == 0) return;
self.gifView.hidden = NO;
[self.gifView stopAnimating];
if (images.count == 1) { //
self.gifView.image = [images lastObject];
} else { //
self.gifView.animationImages = images;
self.gifView.animationDuration = [self.stateDurations[@(state)] doubleValue];
[self.gifView startAnimating];
}
} else if (state == MJRefreshStateIdle) {
self.gifView.hidden = NO;
} else if (state == MJRefreshStateNoMoreData) {
self.gifView.hidden = YES;
}
}
@end

View File

@@ -0,0 +1,25 @@
//
// MJRefreshBackNormalFooter.h
// MJRefresh
//
// Created by MJ Lee on 15/4/24.
// Copyright (c) 2015年 小码哥. All rights reserved.
//
#if __has_include(<MJRefresh/MJRefreshBackStateFooter.h>)
#import <MJRefresh/MJRefreshBackStateFooter.h>
#else
#import "MJRefreshBackStateFooter.h"
#endif
NS_ASSUME_NONNULL_BEGIN
@interface MJRefreshBackNormalFooter : MJRefreshBackStateFooter
@property (weak, nonatomic, readonly) UIImageView *arrowView;
@property (weak, nonatomic, readonly) UIActivityIndicatorView *loadingView;
/** 菊花的样式 */
@property (assign, nonatomic) UIActivityIndicatorViewStyle activityIndicatorViewStyle MJRefreshDeprecated("first deprecated in 3.2.2 - Use `loadingView` property");
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,132 @@
//
// MJRefreshBackNormalFooter.m
// MJRefresh
//
// Created by MJ Lee on 15/4/24.
// Copyright (c) 2015 . All rights reserved.
//
#import "MJRefreshBackNormalFooter.h"
#import "NSBundle+MJRefresh.h"
#import "UIView+MJExtension.h"
@interface MJRefreshBackNormalFooter()
{
__unsafe_unretained UIImageView *_arrowView;
}
@property (weak, nonatomic) UIActivityIndicatorView *loadingView;
@end
@implementation MJRefreshBackNormalFooter
#pragma mark -
- (UIImageView *)arrowView
{
if (!_arrowView) {
UIImageView *arrowView = [[UIImageView alloc] initWithImage:[NSBundle mj_arrowImage]];
[self addSubview:_arrowView = arrowView];
}
return _arrowView;
}
- (UIActivityIndicatorView *)loadingView
{
if (!_loadingView) {
UIActivityIndicatorView *loadingView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:_activityIndicatorViewStyle];
loadingView.hidesWhenStopped = YES;
[self addSubview:_loadingView = loadingView];
}
return _loadingView;
}
- (void)setActivityIndicatorViewStyle:(UIActivityIndicatorViewStyle)activityIndicatorViewStyle
{
_activityIndicatorViewStyle = activityIndicatorViewStyle;
[self.loadingView removeFromSuperview];
self.loadingView = nil;
[self setNeedsLayout];
}
#pragma mark -
- (void)prepare
{
[super prepare];
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000
if (@available(iOS 13.0, *)) {
_activityIndicatorViewStyle = UIActivityIndicatorViewStyleMedium;
return;
}
#endif
_activityIndicatorViewStyle = UIActivityIndicatorViewStyleGray;
}
- (void)placeSubviews
{
[super placeSubviews];
//
CGFloat arrowCenterX = self.mj_w * 0.5;
if (!self.stateLabel.hidden) {
arrowCenterX -= self.labelLeftInset + self.stateLabel.mj_textWidth * 0.5;
}
CGFloat arrowCenterY = self.mj_h * 0.5;
CGPoint arrowCenter = CGPointMake(arrowCenterX, arrowCenterY);
//
if (self.arrowView.constraints.count == 0) {
self.arrowView.mj_size = self.arrowView.image.size;
self.arrowView.center = arrowCenter;
}
//
if (self.loadingView.constraints.count == 0) {
self.loadingView.center = arrowCenter;
}
self.arrowView.tintColor = self.stateLabel.textColor;
}
- (void)setState:(MJRefreshState)state
{
MJRefreshCheckState
//
if (state == MJRefreshStateIdle) {
if (oldState == MJRefreshStateRefreshing) {
self.arrowView.transform = CGAffineTransformMakeRotation(0.000001 - M_PI);
[UIView animateWithDuration:self.slowAnimationDuration animations:^{
self.loadingView.alpha = 0.0;
} completion:^(BOOL finished) {
// MJRefreshStateIdle
if (self.state != MJRefreshStateIdle) return;
self.loadingView.alpha = 1.0;
[self.loadingView stopAnimating];
self.arrowView.hidden = NO;
}];
} else {
self.arrowView.hidden = NO;
[self.loadingView stopAnimating];
[UIView animateWithDuration:self.fastAnimationDuration animations:^{
self.arrowView.transform = CGAffineTransformMakeRotation(0.000001 - M_PI);
}];
}
} else if (state == MJRefreshStatePulling) {
self.arrowView.hidden = NO;
[self.loadingView stopAnimating];
[UIView animateWithDuration:self.fastAnimationDuration animations:^{
self.arrowView.transform = CGAffineTransformIdentity;
}];
} else if (state == MJRefreshStateRefreshing) {
self.arrowView.hidden = YES;
[self.loadingView startAnimating];
} else if (state == MJRefreshStateNoMoreData) {
self.arrowView.hidden = YES;
[self.loadingView stopAnimating];
}
}
@end

View File

@@ -0,0 +1,29 @@
//
// MJRefreshBackStateFooter.h
// MJRefresh
//
// Created by MJ Lee on 15/6/13.
// Copyright © 2015年 小码哥. All rights reserved.
//
#if __has_include(<MJRefresh/MJRefreshBackFooter.h>)
#import <MJRefresh/MJRefreshBackFooter.h>
#else
#import "MJRefreshBackFooter.h"
#endif
NS_ASSUME_NONNULL_BEGIN
@interface MJRefreshBackStateFooter : MJRefreshBackFooter
/** 文字距离圈圈、箭头的距离 */
@property (assign, nonatomic) CGFloat labelLeftInset;
/** 显示刷新状态的label */
@property (weak, nonatomic, readonly) UILabel *stateLabel;
/** 设置state状态下的文字 */
- (instancetype)setTitle:(NSString *)title forState:(MJRefreshState)state;
/** 获取state状态下的title */
- (NSString *)titleForState:(MJRefreshState)state;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,93 @@
//
// MJRefreshBackStateFooter.m
// MJRefresh
//
// Created by MJ Lee on 15/6/13.
// Copyright © 2015 . All rights reserved.
//
#import "MJRefreshBackStateFooter.h"
#import "NSBundle+MJRefresh.h"
@interface MJRefreshBackStateFooter()
{
/** label */
__unsafe_unretained UILabel *_stateLabel;
}
/** */
@property (strong, nonatomic) NSMutableDictionary *stateTitles;
@end
@implementation MJRefreshBackStateFooter
#pragma mark -
- (NSMutableDictionary *)stateTitles
{
if (!_stateTitles) {
self.stateTitles = [NSMutableDictionary dictionary];
}
return _stateTitles;
}
- (UILabel *)stateLabel
{
if (!_stateLabel) {
[self addSubview:_stateLabel = [UILabel mj_label]];
}
return _stateLabel;
}
#pragma mark -
- (instancetype)setTitle:(NSString *)title forState:(MJRefreshState)state
{
if (title == nil) return self;
self.stateTitles[@(state)] = title;
self.stateLabel.text = self.stateTitles[@(self.state)];
return self;
}
- (NSString *)titleForState:(MJRefreshState)state {
return self.stateTitles[@(state)];
}
- (void)textConfiguration {
//
[self setTitle:[NSBundle mj_localizedStringForKey:MJRefreshBackFooterIdleText] forState:MJRefreshStateIdle];
[self setTitle:[NSBundle mj_localizedStringForKey:MJRefreshBackFooterPullingText] forState:MJRefreshStatePulling];
[self setTitle:[NSBundle mj_localizedStringForKey:MJRefreshBackFooterRefreshingText] forState:MJRefreshStateRefreshing];
[self setTitle:[NSBundle mj_localizedStringForKey:MJRefreshBackFooterNoMoreDataText] forState:MJRefreshStateNoMoreData];
}
#pragma mark -
- (void)prepare
{
[super prepare];
//
self.labelLeftInset = MJRefreshLabelLeftInset;
[self textConfiguration];
}
- (void)i18nDidChange {
[self textConfiguration];
[super i18nDidChange];
}
- (void)placeSubviews
{
[super placeSubviews];
if (self.stateLabel.constraints.count) return;
//
self.stateLabel.frame = self.bounds;
}
- (void)setState:(MJRefreshState)state
{
MJRefreshCheckState
//
self.stateLabel.text = self.stateTitles[@(state)];
}
@end

View File

@@ -0,0 +1,25 @@
//
// MJRefreshGifHeader.h
// MJRefresh
//
// Created by MJ Lee on 15/4/24.
// Copyright (c) 2015年 小码哥. All rights reserved.
//
#if __has_include(<MJRefresh/MJRefreshStateHeader.h>)
#import <MJRefresh/MJRefreshStateHeader.h>
#else
#import "MJRefreshStateHeader.h"
#endif
NS_ASSUME_NONNULL_BEGIN
@interface MJRefreshGifHeader : MJRefreshStateHeader
@property (weak, nonatomic, readonly) UIImageView *gifView;
/** 设置state状态下的动画图片images 动画持续时间duration*/
- (instancetype)setImages:(NSArray *)images duration:(NSTimeInterval)duration forState:(MJRefreshState)state;
- (instancetype)setImages:(NSArray *)images forState:(MJRefreshState)state;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,135 @@
//
// MJRefreshGifHeader.m
// MJRefresh
//
// Created by MJ Lee on 15/4/24.
// Copyright (c) 2015 . All rights reserved.
//
#import "MJRefreshGifHeader.h"
#import "UIView+MJExtension.h"
#import "UIScrollView+MJExtension.h"
@interface MJRefreshGifHeader()
{
__unsafe_unretained UIImageView *_gifView;
}
/** */
@property (strong, nonatomic) NSMutableDictionary *stateImages;
/** */
@property (strong, nonatomic) NSMutableDictionary *stateDurations;
@end
@implementation MJRefreshGifHeader
#pragma mark -
- (UIImageView *)gifView
{
if (!_gifView) {
UIImageView *gifView = [[UIImageView alloc] init];
[self addSubview:_gifView = gifView];
}
return _gifView;
}
- (NSMutableDictionary *)stateImages
{
if (!_stateImages) {
self.stateImages = [NSMutableDictionary dictionary];
}
return _stateImages;
}
- (NSMutableDictionary *)stateDurations
{
if (!_stateDurations) {
self.stateDurations = [NSMutableDictionary dictionary];
}
return _stateDurations;
}
#pragma mark -
- (instancetype)setImages:(NSArray *)images duration:(NSTimeInterval)duration forState:(MJRefreshState)state {
if (images == nil) return self;
self.stateImages[@(state)] = images;
self.stateDurations[@(state)] = @(duration);
/* */
UIImage *image = [images firstObject];
if (image.size.height > self.mj_h) {
self.mj_h = image.size.height;
}
return self;
}
- (instancetype)setImages:(NSArray *)images forState:(MJRefreshState)state
{
return [self setImages:images duration:images.count * 0.1 forState:state];
}
#pragma mark -
- (void)prepare
{
[super prepare];
//
self.labelLeftInset = 20;
}
- (void)setPullingPercent:(CGFloat)pullingPercent
{
[super setPullingPercent:pullingPercent];
NSArray *images = self.stateImages[@(MJRefreshStateIdle)];
if (self.state != MJRefreshStateIdle || images.count == 0) return;
//
[self.gifView stopAnimating];
//
NSUInteger index = images.count * pullingPercent;
if (index >= images.count) index = images.count - 1;
self.gifView.image = images[index];
}
- (void)placeSubviews
{
[super placeSubviews];
if (self.gifView.constraints.count) return;
self.gifView.frame = self.bounds;
if (self.stateLabel.hidden && self.lastUpdatedTimeLabel.hidden) {
self.gifView.contentMode = UIViewContentModeCenter;
} else {
self.gifView.contentMode = UIViewContentModeRight;
CGFloat stateWidth = self.stateLabel.mj_textWidth;
CGFloat timeWidth = 0.0;
if (!self.lastUpdatedTimeLabel.hidden) {
timeWidth = self.lastUpdatedTimeLabel.mj_textWidth;
}
CGFloat textWidth = MAX(stateWidth, timeWidth);
self.gifView.mj_w = self.mj_w * 0.5 - textWidth * 0.5 - self.labelLeftInset;
}
}
- (void)setState:(MJRefreshState)state
{
MJRefreshCheckState
//
if (state == MJRefreshStatePulling || state == MJRefreshStateRefreshing) {
NSArray *images = self.stateImages[@(state)];
if (images.count == 0) return;
[self.gifView stopAnimating];
if (images.count == 1) { //
self.gifView.image = [images lastObject];
} else { //
self.gifView.animationImages = images;
self.gifView.animationDuration = [self.stateDurations[@(state)] doubleValue];
[self.gifView startAnimating];
}
} else if (state == MJRefreshStateIdle) {
[self.gifView stopAnimating];
}
}
@end

View File

@@ -0,0 +1,26 @@
//
// MJRefreshNormalHeader.h
// MJRefresh
//
// Created by MJ Lee on 15/4/24.
// Copyright (c) 2015年 小码哥. All rights reserved.
//
#if __has_include(<MJRefresh/MJRefreshStateHeader.h>)
#import <MJRefresh/MJRefreshStateHeader.h>
#else
#import "MJRefreshStateHeader.h"
#endif
NS_ASSUME_NONNULL_BEGIN
@interface MJRefreshNormalHeader : MJRefreshStateHeader
@property (weak, nonatomic, readonly) UIImageView *arrowView;
@property (weak, nonatomic, readonly) UIActivityIndicatorView *loadingView;
/** 菊花的样式 */
@property (assign, nonatomic) UIActivityIndicatorViewStyle activityIndicatorViewStyle MJRefreshDeprecated("first deprecated in 3.2.2 - Use `loadingView` property");
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,137 @@
//
// MJRefreshNormalHeader.m
// MJRefresh
//
// Created by MJ Lee on 15/4/24.
// Copyright (c) 2015 . All rights reserved.
//
#import "MJRefreshNormalHeader.h"
#import "NSBundle+MJRefresh.h"
#import "UIScrollView+MJRefresh.h"
#import "UIView+MJExtension.h"
@interface MJRefreshNormalHeader()
{
__unsafe_unretained UIImageView *_arrowView;
}
@property (weak, nonatomic) UIActivityIndicatorView *loadingView;
@end
@implementation MJRefreshNormalHeader
#pragma mark -
- (UIImageView *)arrowView
{
if (!_arrowView) {
UIImageView *arrowView = [[UIImageView alloc] initWithImage:[NSBundle mj_arrowImage]];
[self addSubview:_arrowView = arrowView];
}
return _arrowView;
}
- (UIActivityIndicatorView *)loadingView
{
if (!_loadingView) {
UIActivityIndicatorView *loadingView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:_activityIndicatorViewStyle];
loadingView.hidesWhenStopped = YES;
[self addSubview:_loadingView = loadingView];
}
return _loadingView;
}
#pragma mark -
- (void)setActivityIndicatorViewStyle:(UIActivityIndicatorViewStyle)activityIndicatorViewStyle
{
_activityIndicatorViewStyle = activityIndicatorViewStyle;
[self.loadingView removeFromSuperview];
self.loadingView = nil;
[self setNeedsLayout];
}
#pragma mark -
- (void)prepare
{
[super prepare];
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000
if (@available(iOS 13.0, *)) {
_activityIndicatorViewStyle = UIActivityIndicatorViewStyleMedium;
return;
}
#endif
_activityIndicatorViewStyle = UIActivityIndicatorViewStyleGray;
}
- (void)placeSubviews
{
[super placeSubviews];
//
CGFloat arrowCenterX = self.mj_w * 0.5;
if (!self.stateLabel.hidden) {
CGFloat stateWidth = self.stateLabel.mj_textWidth;
CGFloat timeWidth = 0.0;
if (!self.lastUpdatedTimeLabel.hidden) {
timeWidth = self.lastUpdatedTimeLabel.mj_textWidth;
}
CGFloat textWidth = MAX(stateWidth, timeWidth);
arrowCenterX -= textWidth / 2 + self.labelLeftInset;
}
CGFloat arrowCenterY = self.mj_h * 0.5;
CGPoint arrowCenter = CGPointMake(arrowCenterX, arrowCenterY);
//
if (self.arrowView.constraints.count == 0) {
self.arrowView.mj_size = self.arrowView.image.size;
self.arrowView.center = arrowCenter;
}
//
if (self.loadingView.constraints.count == 0) {
self.loadingView.center = arrowCenter;
}
self.arrowView.tintColor = self.stateLabel.textColor;
}
- (void)setState:(MJRefreshState)state
{
MJRefreshCheckState
//
if (state == MJRefreshStateIdle) {
if (oldState == MJRefreshStateRefreshing) {
self.arrowView.transform = CGAffineTransformIdentity;
[UIView animateWithDuration:self.slowAnimationDuration animations:^{
self.loadingView.alpha = 0.0;
} completion:^(BOOL finished) {
// idle
if (self.state != MJRefreshStateIdle) return;
self.loadingView.alpha = 1.0;
[self.loadingView stopAnimating];
self.arrowView.hidden = NO;
}];
} else {
[self.loadingView stopAnimating];
self.arrowView.hidden = NO;
[UIView animateWithDuration:self.fastAnimationDuration animations:^{
self.arrowView.transform = CGAffineTransformIdentity;
}];
}
} else if (state == MJRefreshStatePulling) {
[self.loadingView stopAnimating];
self.arrowView.hidden = NO;
[UIView animateWithDuration:self.fastAnimationDuration animations:^{
self.arrowView.transform = CGAffineTransformMakeRotation(0.000001 - M_PI);
}];
} else if (state == MJRefreshStateRefreshing) {
self.loadingView.alpha = 1.0; // refreshing -> idle
[self.loadingView startAnimating];
self.arrowView.hidden = YES;
}
}
@end

View File

@@ -0,0 +1,39 @@
//
// MJRefreshStateHeader.h
// MJRefresh
//
// Created by MJ Lee on 15/4/24.
// Copyright (c) 2015年 小码哥. All rights reserved.
//
#if __has_include(<MJRefresh/MJRefreshHeader.h>)
#import <MJRefresh/MJRefreshHeader.h>
#else
#import "MJRefreshHeader.h"
#endif
NS_ASSUME_NONNULL_BEGIN
@interface MJRefreshStateHeader : MJRefreshHeader
#pragma mark - 刷新时间相关
/** 利用这个block来决定显示的更新时间文字 */
@property (copy, nonatomic, nullable) NSString *(^lastUpdatedTimeText)(NSDate * _Nullable lastUpdatedTime);
/** 显示上一次刷新时间的label */
@property (weak, nonatomic, readonly) UILabel *lastUpdatedTimeLabel;
#pragma mark - 状态相关
/** 文字距离圈圈、箭头的距离 */
@property (assign, nonatomic) CGFloat labelLeftInset;
/** 显示刷新状态的label */
@property (weak, nonatomic, readonly) UILabel *stateLabel;
/** 设置state状态下的文字 */
- (instancetype)setTitle:(NSString *)title forState:(MJRefreshState)state;
@end
@interface MJRefreshStateHeader (ChainingGrammar)
- (instancetype)modifyLastUpdatedTimeText:(NSString * (^)(NSDate * _Nullable lastUpdatedTime))handler;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,191 @@
//
// MJRefreshStateHeader.m
// MJRefresh
//
// Created by MJ Lee on 15/4/24.
// Copyright (c) 2015 . All rights reserved.
//
#import "MJRefreshStateHeader.h"
#import "MJRefreshConst.h"
#import "NSBundle+MJRefresh.h"
#import "UIView+MJExtension.h"
#import "UIScrollView+MJExtension.h"
@interface MJRefreshStateHeader()
{
/** label */
__unsafe_unretained UILabel *_lastUpdatedTimeLabel;
/** label */
__unsafe_unretained UILabel *_stateLabel;
}
/** */
@property (strong, nonatomic) NSMutableDictionary *stateTitles;
@end
@implementation MJRefreshStateHeader
#pragma mark -
- (NSMutableDictionary *)stateTitles
{
if (!_stateTitles) {
self.stateTitles = [NSMutableDictionary dictionary];
}
return _stateTitles;
}
- (UILabel *)stateLabel
{
if (!_stateLabel) {
[self addSubview:_stateLabel = [UILabel mj_label]];
}
return _stateLabel;
}
- (UILabel *)lastUpdatedTimeLabel
{
if (!_lastUpdatedTimeLabel) {
[self addSubview:_lastUpdatedTimeLabel = [UILabel mj_label]];
}
return _lastUpdatedTimeLabel;
}
- (void)setLastUpdatedTimeText:(NSString * _Nonnull (^)(NSDate * _Nullable))lastUpdatedTimeText{
_lastUpdatedTimeText = lastUpdatedTimeText;
// key
self.lastUpdatedTimeKey = self.lastUpdatedTimeKey;
}
#pragma mark -
- (instancetype)setTitle:(NSString *)title forState:(MJRefreshState)state
{
if (title == nil) return self;
self.stateTitles[@(state)] = title;
self.stateLabel.text = self.stateTitles[@(self.state)];
return self;
}
#pragma mark key
- (void)setLastUpdatedTimeKey:(NSString *)lastUpdatedTimeKey
{
[super setLastUpdatedTimeKey:lastUpdatedTimeKey];
// label
if (self.lastUpdatedTimeLabel.hidden) return;
NSDate *lastUpdatedTime = [[NSUserDefaults standardUserDefaults] objectForKey:lastUpdatedTimeKey];
// block
if (self.lastUpdatedTimeText) {
self.lastUpdatedTimeLabel.text = self.lastUpdatedTimeText(lastUpdatedTime);
return;
}
if (lastUpdatedTime) {
// 1.
NSCalendar *calendar = [NSCalendar calendarWithIdentifier:NSCalendarIdentifierGregorian];
NSUInteger unitFlags = NSCalendarUnitYear| NSCalendarUnitMonth | NSCalendarUnitDay | NSCalendarUnitHour | NSCalendarUnitMinute;
NSDateComponents *cmp1 = [calendar components:unitFlags fromDate:lastUpdatedTime];
NSDateComponents *cmp2 = [calendar components:unitFlags fromDate:[NSDate date]];
// 2.
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
BOOL isToday = NO;
if ([cmp1 day] == [cmp2 day]) { //
formatter.dateFormat = @" HH:mm";
isToday = YES;
} else if ([cmp1 year] == [cmp2 year]) { //
formatter.dateFormat = @"MM-dd HH:mm";
} else {
formatter.dateFormat = @"yyyy-MM-dd HH:mm";
}
NSString *time = [formatter stringFromDate:lastUpdatedTime];
// 3.
self.lastUpdatedTimeLabel.text = [NSString stringWithFormat:@"%@%@%@",
[NSBundle mj_localizedStringForKey:MJRefreshHeaderLastTimeText],
isToday ? [NSBundle mj_localizedStringForKey:MJRefreshHeaderDateTodayText] : @"",
time];
} else {
self.lastUpdatedTimeLabel.text = [NSString stringWithFormat:@"%@%@",
[NSBundle mj_localizedStringForKey:MJRefreshHeaderLastTimeText],
[NSBundle mj_localizedStringForKey:MJRefreshHeaderNoneLastDateText]];
}
}
- (void)textConfiguration {
//
[self setTitle:[NSBundle mj_localizedStringForKey:MJRefreshHeaderIdleText] forState:MJRefreshStateIdle];
[self setTitle:[NSBundle mj_localizedStringForKey:MJRefreshHeaderPullingText] forState:MJRefreshStatePulling];
[self setTitle:[NSBundle mj_localizedStringForKey:MJRefreshHeaderRefreshingText] forState:MJRefreshStateRefreshing];
self.lastUpdatedTimeKey = MJRefreshHeaderLastUpdatedTimeKey;
}
#pragma mark -
- (void)prepare
{
[super prepare];
//
self.labelLeftInset = MJRefreshLabelLeftInset;
[self textConfiguration];
}
- (void)i18nDidChange {
[self textConfiguration];
[super i18nDidChange];
}
- (void)placeSubviews
{
[super placeSubviews];
if (self.stateLabel.hidden) return;
BOOL noConstrainsOnStatusLabel = self.stateLabel.constraints.count == 0;
if (self.lastUpdatedTimeLabel.hidden) {
//
if (noConstrainsOnStatusLabel) self.stateLabel.frame = self.bounds;
} else {
CGFloat stateLabelH = self.mj_h * 0.5;
//
if (noConstrainsOnStatusLabel) {
self.stateLabel.mj_x = 0;
self.stateLabel.mj_y = 0;
self.stateLabel.mj_w = self.mj_w;
self.stateLabel.mj_h = stateLabelH;
}
//
if (self.lastUpdatedTimeLabel.constraints.count == 0) {
self.lastUpdatedTimeLabel.mj_x = 0;
self.lastUpdatedTimeLabel.mj_y = stateLabelH;
self.lastUpdatedTimeLabel.mj_w = self.mj_w;
self.lastUpdatedTimeLabel.mj_h = self.mj_h - self.lastUpdatedTimeLabel.mj_y;
}
}
}
- (void)setState:(MJRefreshState)state
{
MJRefreshCheckState
//
self.stateLabel.text = self.stateTitles[@(state)];
// key
self.lastUpdatedTimeKey = self.lastUpdatedTimeKey;
}
@end
#pragma mark - <<< Swift >>> -
@implementation MJRefreshStateHeader (ChainingGrammar)
- (instancetype)modifyLastUpdatedTimeText:(NSString * _Nonnull (^)(NSDate * _Nullable))handler {
self.lastUpdatedTimeText = handler;
return self;
}
@end

View File

@@ -0,0 +1,23 @@
//
// MJRefreshNormalTrailer.h
// MJRefresh
//
// Created by kinarobin on 2020/5/3.
// Copyright © 2020 小码哥. All rights reserved.
//
#if __has_include(<MJRefresh/MJRefreshStateTrailer.h>)
#import <MJRefresh/MJRefreshStateTrailer.h>
#else
#import "MJRefreshStateTrailer.h"
#endif
NS_ASSUME_NONNULL_BEGIN
@interface MJRefreshNormalTrailer : MJRefreshStateTrailer
@property (weak, nonatomic, readonly) UIImageView *arrowView;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,80 @@
//
// MJRefreshNormalTrailer.m
// MJRefresh
//
// Created by kinarobin on 2020/5/3.
// Copyright © 2020 . All rights reserved.
//
#import "MJRefreshNormalTrailer.h"
#import "NSBundle+MJRefresh.h"
#import "UIView+MJExtension.h"
@interface MJRefreshNormalTrailer() {
__unsafe_unretained UIImageView *_arrowView;
}
@end
@implementation MJRefreshNormalTrailer
#pragma mark -
- (UIImageView *)arrowView {
if (!_arrowView) {
UIImageView *arrowView = [[UIImageView alloc] initWithImage:[NSBundle mj_trailArrowImage]];
[self addSubview:_arrowView = arrowView];
}
return _arrowView;
}
- (void)placeSubviews {
[super placeSubviews];
CGSize arrowSize = self.arrowView.image.size;
//
CGPoint selfCenter = CGPointMake(self.mj_w * 0.5, self.mj_h * 0.5);
CGPoint arrowCenter = CGPointMake(arrowSize.width * 0.5 + 5, self.mj_h * 0.5);
BOOL stateHidden = self.stateLabel.isHidden;
if (self.arrowView.constraints.count == 0) {
self.arrowView.mj_size = self.arrowView.image.size;
self.arrowView.center = stateHidden ? selfCenter : arrowCenter ;
}
self.arrowView.tintColor = self.stateLabel.textColor;
if (stateHidden) return;
BOOL noConstrainsOnStatusLabel = self.stateLabel.constraints.count == 0;
CGFloat stateLabelW = ceil(self.stateLabel.font.pointSize);
//
if (noConstrainsOnStatusLabel) {
BOOL arrowHidden = self.arrowView.isHidden;
CGFloat stateCenterX = (self.mj_w + arrowSize.width) * 0.5;
self.stateLabel.center = arrowHidden ? selfCenter : CGPointMake(stateCenterX, self.mj_h * 0.5);
self.stateLabel.mj_size = CGSizeMake(stateLabelW, self.mj_h) ;
}
}
- (void)setState:(MJRefreshState)state {
MJRefreshCheckState
//
if (state == MJRefreshStateIdle) {
if (oldState == MJRefreshStateRefreshing) {
[UIView animateWithDuration:self.fastAnimationDuration animations:^{
self.arrowView.transform = CGAffineTransformMakeRotation(M_PI);
} completion:^(BOOL finished) {
self.arrowView.transform = CGAffineTransformIdentity;
}];
} else {
[UIView animateWithDuration:self.fastAnimationDuration animations:^{
self.arrowView.transform = CGAffineTransformIdentity;
}];
}
} else if (state == MJRefreshStatePulling) {
[UIView animateWithDuration:self.fastAnimationDuration animations:^{
self.arrowView.transform = CGAffineTransformMakeRotation(M_PI);
}];
}
}
@end

View File

@@ -0,0 +1,28 @@
//
// MJRefreshStateTrailer.h
// MJRefresh
//
// Created by kinarobin on 2020/5/3.
// Copyright © 2020 小码哥. All rights reserved.
//
#if __has_include(<MJRefresh/MJRefreshTrailer.h>)
#import <MJRefresh/MJRefreshTrailer.h>
#else
#import "MJRefreshTrailer.h"
#endif
NS_ASSUME_NONNULL_BEGIN
@interface MJRefreshStateTrailer : MJRefreshTrailer
#pragma mark - 状态相关
/** 显示刷新状态的label */
@property (weak, nonatomic, readonly) UILabel *stateLabel;
/** 设置state状态下的文字 */
- (instancetype)setTitle:(NSString *)title forState:(MJRefreshState)state;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,87 @@
//
// MJRefreshStateTrailer.m
// MJRefresh
//
// Created by kinarobin on 2020/5/3.
// Copyright © 2020 . All rights reserved.
//
#import "MJRefreshStateTrailer.h"
#import "NSBundle+MJRefresh.h"
#import "UIView+MJExtension.h"
@interface MJRefreshStateTrailer() {
/** label */
__unsafe_unretained UILabel *_stateLabel;
}
/** */
@property (strong, nonatomic) NSMutableDictionary *stateTitles;
@end
@implementation MJRefreshStateTrailer
#pragma mark -
- (NSMutableDictionary *)stateTitles {
if (!_stateTitles) {
self.stateTitles = [NSMutableDictionary dictionary];
}
return _stateTitles;
}
- (UILabel *)stateLabel {
if (!_stateLabel) {
UILabel *stateLabel = [UILabel mj_label];
stateLabel.numberOfLines = 0;
[self addSubview:_stateLabel = stateLabel];
}
return _stateLabel;
}
#pragma mark -
- (instancetype)setTitle:(NSString *)title forState:(MJRefreshState)state {
if (title == nil) return self;
self.stateTitles[@(state)] = title;
self.stateLabel.text = self.stateTitles[@(self.state)];
return self;
}
- (void)textConfiguration {
//
[self setTitle:[NSBundle mj_localizedStringForKey:MJRefreshTrailerIdleText] forState:MJRefreshStateIdle];
[self setTitle:[NSBundle mj_localizedStringForKey:MJRefreshTrailerPullingText] forState:MJRefreshStatePulling];
[self setTitle:[NSBundle mj_localizedStringForKey:MJRefreshTrailerPullingText] forState:MJRefreshStateRefreshing];
}
#pragma mark -
- (void)prepare {
[super prepare];
[self textConfiguration];
}
- (void)i18nDidChange {
[self textConfiguration];
[super i18nDidChange];
}
- (void)setState:(MJRefreshState)state {
MJRefreshCheckState
//
self.stateLabel.text = self.stateTitles[@(state)];
}
- (void)placeSubviews {
[super placeSubviews];
if (self.stateLabel.hidden) return;
BOOL noConstrainsOnStatusLabel = self.stateLabel.constraints.count == 0;
CGFloat stateLabelW = ceil(self.stateLabel.font.pointSize);
//
if (noConstrainsOnStatusLabel) {
self.stateLabel.center = CGPointMake(self.mj_w * 0.5, self.mj_h * 0.5);
self.stateLabel.mj_size = CGSizeMake(stateLabelW, self.mj_h) ;
}
}
@end

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -0,0 +1,16 @@
"MJRefreshHeaderIdleText" = "아래로 당겨 새로고침";
"MJRefreshHeaderPullingText" = "놓으면 새로고침";
"MJRefreshHeaderRefreshingText" = "로딩중...";
"MJRefreshAutoFooterIdleText" = "탭 또는 위로 당겨 로드함";
"MJRefreshAutoFooterRefreshingText" = "로딩중...";
"MJRefreshAutoFooterNoMoreDataText" = "더이상 데이터 없음";
"MJRefreshBackFooterIdleText" = "위로 당겨 더 로드 가능";
"MJRefreshBackFooterPullingText" = "놓으면 더 로드됨.";
"MJRefreshBackFooterRefreshingText" = "로딩중...";
"MJRefreshBackFooterNoMoreDataText" = "더이상 데이터 없음";
"MJRefreshHeaderLastTimeText" = "마지막 업데이트: ";
"MJRefreshHeaderDateTodayText" = "오늘";
"MJRefreshHeaderNoneLastDateText" = "기록 없음";

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@@ -0,0 +1,19 @@
"MJRefreshHeaderIdleText" = "下拉可以刷新";
"MJRefreshHeaderPullingText" = "鬆開立即刷新";
"MJRefreshHeaderRefreshingText" = "正在刷新數據中...";
"MJRefreshTrailerIdleText" = "滑動查看圖文詳情";
"MJRefreshTrailerPullingText" = "釋放查看圖文詳情";
"MJRefreshAutoFooterIdleText" = "點擊或上拉加載更多";
"MJRefreshAutoFooterRefreshingText" = "正在加載更多的數據...";
"MJRefreshAutoFooterNoMoreDataText" = "已經全部加載完畢";
"MJRefreshBackFooterIdleText" = "上拉可以加載更多";
"MJRefreshBackFooterPullingText" = "鬆開立即加載更多";
"MJRefreshBackFooterRefreshingText" = "正在加載更多的數據...";
"MJRefreshBackFooterNoMoreDataText" = "已經全部加載完畢";
"MJRefreshHeaderLastTimeText" = "最後更新:";
"MJRefreshHeaderDateTodayText" = "今天";
"MJRefreshHeaderNoneLastDateText" = "無記錄";

42
Pods/MJRefresh/MJRefresh/MJRefresh.h generated Normal file
View File

@@ -0,0 +1,42 @@
// 代码地址: https://github.com/CoderMJLee/MJRefresh
#import <Foundation/Foundation.h>
#if __has_include(<MJRefresh/MJRefresh.h>)
FOUNDATION_EXPORT double MJRefreshVersionNumber;
FOUNDATION_EXPORT const unsigned char MJRefreshVersionString[];
#import <MJRefresh/UIScrollView+MJRefresh.h>
#import <MJRefresh/UIScrollView+MJExtension.h>
#import <MJRefresh/UIView+MJExtension.h>
#import <MJRefresh/MJRefreshNormalHeader.h>
#import <MJRefresh/MJRefreshGifHeader.h>
#import <MJRefresh/MJRefreshBackNormalFooter.h>
#import <MJRefresh/MJRefreshBackGifFooter.h>
#import <MJRefresh/MJRefreshAutoNormalFooter.h>
#import <MJRefresh/MJRefreshAutoGifFooter.h>
#import <MJRefresh/MJRefreshNormalTrailer.h>
#import <MJRefresh/MJRefreshConfig.h>
#import <MJRefresh/NSBundle+MJRefresh.h>
#import <MJRefresh/MJRefreshConst.h>
#else
#import "UIScrollView+MJRefresh.h"
#import "UIScrollView+MJExtension.h"
#import "UIView+MJExtension.h"
#import "MJRefreshNormalHeader.h"
#import "MJRefreshGifHeader.h"
#import "MJRefreshBackNormalFooter.h"
#import "MJRefreshBackGifFooter.h"
#import "MJRefreshAutoNormalFooter.h"
#import "MJRefreshAutoGifFooter.h"
#import "MJRefreshNormalTrailer.h"
#import "MJRefreshConfig.h"
#import "NSBundle+MJRefresh.h"
#import "MJRefreshConst.h"
#endif

View File

@@ -0,0 +1,36 @@
//
// MJRefreshConfig.h
//
// Created by Frank on 2018/11/27.
// Copyright © 2018 小码哥. All rights reserved.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface MJRefreshConfig : NSObject
/** 默认使用的语言版本, 默认为 nil. 将随系统的语言自动改变 */
@property (copy, nonatomic, nullable) NSString *languageCode;
/** 默认使用的语言资源文件名, 默认为 nil, 即默认的 Localizable.strings.
- Attention: 文件名不包含后缀.strings
*/
@property (copy, nonatomic, nullable) NSString *i18nFilename;
/** i18n 多语言资源加载自定义 Bundle.
- Attention: 默认为 nil 采用内置逻辑. 这里设置后将忽略内置逻辑的多语言模式, 采用自定义的多语言 bundle
*/
@property (nonatomic, nullable) NSBundle *i18nBundle;
/** Singleton Config instance */
@property (class, nonatomic, readonly) MJRefreshConfig *defaultConfig;
- (instancetype)init NS_UNAVAILABLE;
+ (instancetype)new NS_UNAVAILABLE;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,42 @@
//
// MJRefreshConfig.m
//
// Created by Frank on 2018/11/27.
// Copyright © 2018 . All rights reserved.
//
#import "MJRefreshConfig.h"
#import "MJRefreshConst.h"
#import "NSBundle+MJRefresh.h"
@interface MJRefreshConfig (Bundle)
+ (void)resetLanguageResourceCache;
@end
@implementation MJRefreshConfig
static MJRefreshConfig *mj_RefreshConfig = nil;
+ (instancetype)defaultConfig {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
mj_RefreshConfig = [[self alloc] init];
});
return mj_RefreshConfig;
}
- (void)setLanguageCode:(NSString *)languageCode {
if ([languageCode isEqualToString:_languageCode]) {
return;
}
_languageCode = languageCode;
//
[MJRefreshConfig resetLanguageResourceCache];
[NSNotificationCenter.defaultCenter
postNotificationName:MJRefreshDidChangeLanguageNotification object:self];
}
@end

115
Pods/MJRefresh/MJRefresh/MJRefreshConst.h generated Normal file
View File

@@ -0,0 +1,115 @@
// 代码地址: https://github.com/CoderMJLee/MJRefresh
#import <UIKit/UIKit.h>
#import <objc/message.h>
#import <objc/runtime.h>
// 弱引用
#define MJWeakSelf __weak typeof(self) weakSelf = self;
// 日志输出
#ifdef DEBUG
#define MJRefreshLog(...) NSLog(__VA_ARGS__)
#else
#define MJRefreshLog(...)
#endif
// 过期提醒
#define MJRefreshDeprecated(DESCRIPTION) __attribute__((deprecated(DESCRIPTION)))
// 运行时objc_msgSend
#define MJRefreshMsgSend(...) ((void (*)(void *, SEL, UIView *))objc_msgSend)(__VA_ARGS__)
#define MJRefreshMsgTarget(target) (__bridge void *)(target)
// RGB颜色
#define MJRefreshColor(r, g, b) [UIColor colorWithRed:(r)/255.0 green:(g)/255.0 blue:(b)/255.0 alpha:1.0]
// 文字颜色
#define MJRefreshLabelTextColor MJRefreshColor(90, 90, 90)
// 字体大小
#define MJRefreshLabelFont [UIFont boldSystemFontOfSize:14]
// 常量
UIKIT_EXTERN const CGFloat MJRefreshLabelLeftInset;
UIKIT_EXTERN const CGFloat MJRefreshHeaderHeight;
UIKIT_EXTERN const CGFloat MJRefreshFooterHeight;
UIKIT_EXTERN const CGFloat MJRefreshTrailWidth;
UIKIT_EXTERN const CGFloat MJRefreshFastAnimationDuration;
UIKIT_EXTERN const CGFloat MJRefreshSlowAnimationDuration;
UIKIT_EXTERN NSString *const MJRefreshKeyPathContentOffset;
UIKIT_EXTERN NSString *const MJRefreshKeyPathContentSize;
UIKIT_EXTERN NSString *const MJRefreshKeyPathContentInset;
UIKIT_EXTERN NSString *const MJRefreshKeyPathPanState;
UIKIT_EXTERN NSString *const MJRefreshHeaderLastUpdatedTimeKey;
UIKIT_EXTERN NSString *const MJRefreshHeaderIdleText;
UIKIT_EXTERN NSString *const MJRefreshHeaderPullingText;
UIKIT_EXTERN NSString *const MJRefreshHeaderRefreshingText;
UIKIT_EXTERN NSString *const MJRefreshTrailerIdleText;
UIKIT_EXTERN NSString *const MJRefreshTrailerPullingText;
UIKIT_EXTERN NSString *const MJRefreshAutoFooterIdleText;
UIKIT_EXTERN NSString *const MJRefreshAutoFooterRefreshingText;
UIKIT_EXTERN NSString *const MJRefreshAutoFooterNoMoreDataText;
UIKIT_EXTERN NSString *const MJRefreshBackFooterIdleText;
UIKIT_EXTERN NSString *const MJRefreshBackFooterPullingText;
UIKIT_EXTERN NSString *const MJRefreshBackFooterRefreshingText;
UIKIT_EXTERN NSString *const MJRefreshBackFooterNoMoreDataText;
UIKIT_EXTERN NSString *const MJRefreshHeaderLastTimeText;
UIKIT_EXTERN NSString *const MJRefreshHeaderDateTodayText;
UIKIT_EXTERN NSString *const MJRefreshHeaderNoneLastDateText;
UIKIT_EXTERN NSString *const MJRefreshDidChangeLanguageNotification;
// 状态检查
#define MJRefreshCheckState \
MJRefreshState oldState = self.state; \
if (state == oldState) return; \
[super setState:state];
// 异步主线程执行不强持有Self
#define MJRefreshDispatchAsyncOnMainQueue(x) \
__weak typeof(self) weakSelf = self; \
dispatch_async(dispatch_get_main_queue(), ^{ \
typeof(weakSelf) self = weakSelf; \
{x} \
});
/// 替换方法实现
/// @param _fromClass 源类
/// @param _originSelector 源类的 Selector
/// @param _toClass 目标类
/// @param _newSelector 目标类的 Selector
CG_INLINE BOOL MJRefreshExchangeImplementations(
Class _fromClass, SEL _originSelector,
Class _toClass, SEL _newSelector) {
if (!_fromClass || !_toClass) {
return NO;
}
Method oriMethod = class_getInstanceMethod(_fromClass, _originSelector);
Method newMethod = class_getInstanceMethod(_toClass, _newSelector);
if (!newMethod) {
return NO;
}
BOOL isAddedMethod = class_addMethod(_fromClass, _originSelector,
method_getImplementation(newMethod),
method_getTypeEncoding(newMethod));
if (isAddedMethod) {
// 如果 class_addMethod 成功了,说明之前 fromClass 里并不存在 originSelector所以要用一个空的方法代替它以避免 class_replaceMethod 后,后续 toClass 的这个方法被调用时可能会 crash
IMP emptyIMP = imp_implementationWithBlock(^(id selfObject) {});
IMP oriMethodIMP = method_getImplementation(oriMethod) ?: emptyIMP;
const char *oriMethodTypeEncoding = method_getTypeEncoding(oriMethod) ?: "v@:";
class_replaceMethod(_toClass, _newSelector, oriMethodIMP, oriMethodTypeEncoding);
} else {
method_exchangeImplementations(oriMethod, newMethod);
}
return YES;
}

View File

@@ -0,0 +1,39 @@
// : https://github.com/CoderMJLee/MJRefresh
#import <UIKit/UIKit.h>
const CGFloat MJRefreshLabelLeftInset = 25;
const CGFloat MJRefreshHeaderHeight = 54.0;
const CGFloat MJRefreshFooterHeight = 44.0;
const CGFloat MJRefreshTrailWidth = 60.0;
const CGFloat MJRefreshFastAnimationDuration = 0.25;
const CGFloat MJRefreshSlowAnimationDuration = 0.4;
NSString *const MJRefreshKeyPathContentOffset = @"contentOffset";
NSString *const MJRefreshKeyPathContentInset = @"contentInset";
NSString *const MJRefreshKeyPathContentSize = @"contentSize";
NSString *const MJRefreshKeyPathPanState = @"state";
NSString *const MJRefreshHeaderLastUpdatedTimeKey = @"MJRefreshHeaderLastUpdatedTimeKey";
NSString *const MJRefreshHeaderIdleText = @"MJRefreshHeaderIdleText";
NSString *const MJRefreshHeaderPullingText = @"MJRefreshHeaderPullingText";
NSString *const MJRefreshHeaderRefreshingText = @"MJRefreshHeaderRefreshingText";
NSString *const MJRefreshTrailerIdleText = @"MJRefreshTrailerIdleText";
NSString *const MJRefreshTrailerPullingText = @"MJRefreshTrailerPullingText";
NSString *const MJRefreshAutoFooterIdleText = @"MJRefreshAutoFooterIdleText";
NSString *const MJRefreshAutoFooterRefreshingText = @"MJRefreshAutoFooterRefreshingText";
NSString *const MJRefreshAutoFooterNoMoreDataText = @"MJRefreshAutoFooterNoMoreDataText";
NSString *const MJRefreshBackFooterIdleText = @"MJRefreshBackFooterIdleText";
NSString *const MJRefreshBackFooterPullingText = @"MJRefreshBackFooterPullingText";
NSString *const MJRefreshBackFooterRefreshingText = @"MJRefreshBackFooterRefreshingText";
NSString *const MJRefreshBackFooterNoMoreDataText = @"MJRefreshBackFooterNoMoreDataText";
NSString *const MJRefreshHeaderLastTimeText = @"MJRefreshHeaderLastTimeText";
NSString *const MJRefreshHeaderDateTodayText = @"MJRefreshHeaderDateTodayText";
NSString *const MJRefreshHeaderNoneLastDateText = @"MJRefreshHeaderNoneLastDateText";
NSString *const MJRefreshDidChangeLanguageNotification = @"MJRefreshDidChangeLanguageNotification";

View File

@@ -0,0 +1,21 @@
//
// NSBundle+MJRefresh.h
// MJRefresh
//
// Created by MJ Lee on 16/6/13.
// Copyright © 2016年 小码哥. All rights reserved.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface NSBundle (MJRefresh)
+ (instancetype)mj_refreshBundle;
+ (UIImage *)mj_arrowImage;
+ (UIImage *)mj_trailArrowImage;
+ (NSString *)mj_localizedStringForKey:(NSString *)key value:(nullable NSString *)value;
+ (NSString *)mj_localizedStringForKey:(NSString *)key;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,116 @@
//
// NSBundle+MJRefresh.m
// MJRefresh
//
// Created by MJ Lee on 16/6/13.
// Copyright © 2016 . All rights reserved.
//
#import "NSBundle+MJRefresh.h"
#import "MJRefreshComponent.h"
#import "MJRefreshConfig.h"
static NSBundle *mj_defaultI18nBundle = nil;
static NSBundle *mj_systemI18nBundle = nil;
@implementation NSBundle (MJRefresh)
+ (instancetype)mj_refreshBundle
{
static NSBundle *refreshBundle = nil;
if (refreshBundle == nil) {
#ifdef SWIFT_PACKAGE
NSBundle *containnerBundle = SWIFTPM_MODULE_BUNDLE;
#else
NSBundle *containnerBundle = [NSBundle bundleForClass:[MJRefreshComponent class]];
#endif
refreshBundle = [NSBundle bundleWithPath:[containnerBundle pathForResource:@"MJRefresh" ofType:@"bundle"]];
}
return refreshBundle;
}
+ (UIImage *)mj_arrowImage
{
static UIImage *arrowImage = nil;
if (arrowImage == nil) {
arrowImage = [[UIImage imageWithContentsOfFile:[[self mj_refreshBundle] pathForResource:@"arrow@2x" ofType:@"png"]] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
}
return arrowImage;
}
+ (UIImage *)mj_trailArrowImage {
static UIImage *arrowImage = nil;
if (arrowImage == nil) {
arrowImage = [[UIImage imageWithContentsOfFile:[[self mj_refreshBundle] pathForResource:@"trail_arrow@2x" ofType:@"png"]] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
}
return arrowImage;
}
+ (NSString *)mj_localizedStringForKey:(NSString *)key
{
return [self mj_localizedStringForKey:key value:nil];
}
+ (NSString *)mj_localizedStringForKey:(NSString *)key value:(NSString *)value
{
NSString *table = MJRefreshConfig.defaultConfig.i18nFilename;
// ,
if (mj_defaultI18nBundle == nil) {
NSString *language = MJRefreshConfig.defaultConfig.languageCode;
//
if (!language) {
language = [NSLocale preferredLanguages].firstObject;
}
NSBundle *bundle = MJRefreshConfig.defaultConfig.i18nBundle;
// 使 i18nBundle, 使 mainBundle
bundle = bundle ? bundle : NSBundle.mainBundle;
//
NSString *i18nFolderPath = [bundle pathForResource:language ofType:@"lproj"];
mj_defaultI18nBundle = [NSBundle bundleWithPath:i18nFolderPath];
// , , 使 mainBundle
mj_defaultI18nBundle = mj_defaultI18nBundle ? mj_defaultI18nBundle : NSBundle.mainBundle;
// MJRefresh
if (mj_systemI18nBundle == nil) {
mj_systemI18nBundle = [self mj_defaultI18nBundleWithLanguage:language];
}
}
// MJRefresh
value = [mj_systemI18nBundle localizedStringForKey:key value:value table:nil];
// MainBundle
value = [mj_defaultI18nBundle localizedStringForKey:key value:value table:table];
return value;
}
+ (NSBundle *)mj_defaultI18nBundleWithLanguage:(NSString *)language {
if ([language hasPrefix:@"en"]) {
language = @"en";
} else if ([language hasPrefix:@"zh"]) {
if ([language rangeOfString:@"Hans"].location != NSNotFound) {
language = @"zh-Hans"; //
} else { // zh-Hant\zh-HK\zh-TW
language = @"zh-Hant"; //
}
} else if ([language hasPrefix:@"ko"]) {
language = @"ko";
} else if ([language hasPrefix:@"ru"]) {
language = @"ru";
} else if ([language hasPrefix:@"uk"]) {
language = @"uk";
} else {
language = @"en";
}
// MJRefresh.bundle
return [NSBundle bundleWithPath:[[NSBundle mj_refreshBundle] pathForResource:language ofType:@"lproj"]];
}
@end
@implementation MJRefreshConfig (Bundle)
+ (void)resetLanguageResourceCache {
mj_defaultI18nBundle = nil;
mj_systemI18nBundle = nil;
}
@end

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrivacyTracking</key>
<false/>
<key>NSPrivacyTrackingDomains</key>
<array/>
<key>NSPrivacyAccessedAPITypes</key>
<array>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>CA92.1</string>
</array>
</dict>
</array>
<key>NSPrivacyCollectedDataTypes</key>
<array/>
</dict>
</plist>

View File

@@ -0,0 +1,20 @@
//
// UICollectionViewLayout+MJRefresh.h
//
// 该类是用来解决 Footer 在底端加载完成后, 仍停留在原处的 bug.
// 此问题出现在 iOS 14 及以下系统上.
// Reference: https://github.com/CoderMJLee/MJRefresh/issues/1552
//
// Created by jiasong on 2021/11/15.
// Copyright © 2021 小码哥. All rights reserved.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface UICollectionViewLayout (MJRefresh)
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,45 @@
//
// UICollectionViewLayout+MJRefresh.m
//
// Footer , bug.
// iOS 14 .
// Reference: https://github.com/CoderMJLee/MJRefresh/issues/1552
//
// Created by jiasong on 2021/11/15.
// Copyright © 2021 . All rights reserved.
//
#import "UICollectionViewLayout+MJRefresh.h"
#import "MJRefreshConst.h"
#import "MJRefreshFooter.h"
#import "UIScrollView+MJRefresh.h"
@implementation UICollectionViewLayout (MJRefresh)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
MJRefreshExchangeImplementations(self.class, @selector(finalizeCollectionViewUpdates),
self.class, @selector(mj_finalizeCollectionViewUpdates));
});
}
- (void)mj_finalizeCollectionViewUpdates {
[self mj_finalizeCollectionViewUpdates];
__kindof MJRefreshFooter *footer = self.collectionView.mj_footer;
CGSize newSize = self.collectionViewContentSize;
CGSize oldSize = self.collectionView.contentSize;
if (footer != nil && !CGSizeEqualToSize(newSize, oldSize)) {
NSDictionary *changed = @{
NSKeyValueChangeNewKey: [NSValue valueWithCGSize:newSize],
NSKeyValueChangeOldKey: [NSValue valueWithCGSize:oldSize],
};
[CATransaction begin];
[CATransaction setDisableActions:YES];
[footer scrollViewContentSizeDidChange:changed];
[CATransaction commit];
}
}
@end

View File

@@ -0,0 +1,28 @@
// 代码地址: https://github.com/CoderMJLee/MJRefresh
// UIScrollView+Extension.h
// MJRefresh
//
// Created by MJ Lee on 14-5-28.
// Copyright (c) 2014年 小码哥. All rights reserved.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface UIScrollView (MJExtension)
@property (readonly, nonatomic) UIEdgeInsets mj_inset;
@property (assign, nonatomic) CGFloat mj_insetT;
@property (assign, nonatomic) CGFloat mj_insetB;
@property (assign, nonatomic) CGFloat mj_insetL;
@property (assign, nonatomic) CGFloat mj_insetR;
@property (assign, nonatomic) CGFloat mj_offsetX;
@property (assign, nonatomic) CGFloat mj_offsetY;
@property (assign, nonatomic) CGFloat mj_contentW;
@property (assign, nonatomic) CGFloat mj_contentH;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,153 @@
// : https://github.com/CoderMJLee/MJRefresh
// UIScrollView+Extension.m
// MJRefresh
//
// Created by MJ Lee on 14-5-28.
// Copyright (c) 2014 . All rights reserved.
//
#import "UIScrollView+MJExtension.h"
#import <objc/runtime.h>
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunguarded-availability-new"
@implementation UIScrollView (MJExtension)
static BOOL respondsToAdjustedContentInset_;
+ (void)load
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
respondsToAdjustedContentInset_ = [self instancesRespondToSelector:@selector(adjustedContentInset)];
});
}
- (UIEdgeInsets)mj_inset
{
#ifdef __IPHONE_11_0
if (respondsToAdjustedContentInset_) {
return self.adjustedContentInset;
}
#endif
return self.contentInset;
}
- (void)setMj_insetT:(CGFloat)mj_insetT
{
UIEdgeInsets inset = self.contentInset;
inset.top = mj_insetT;
#ifdef __IPHONE_11_0
if (respondsToAdjustedContentInset_) {
inset.top -= (self.adjustedContentInset.top - self.contentInset.top);
}
#endif
self.contentInset = inset;
}
- (CGFloat)mj_insetT
{
return self.mj_inset.top;
}
- (void)setMj_insetB:(CGFloat)mj_insetB
{
UIEdgeInsets inset = self.contentInset;
inset.bottom = mj_insetB;
#ifdef __IPHONE_11_0
if (respondsToAdjustedContentInset_) {
inset.bottom -= (self.adjustedContentInset.bottom - self.contentInset.bottom);
}
#endif
self.contentInset = inset;
}
- (CGFloat)mj_insetB
{
return self.mj_inset.bottom;
}
- (void)setMj_insetL:(CGFloat)mj_insetL
{
UIEdgeInsets inset = self.contentInset;
inset.left = mj_insetL;
#ifdef __IPHONE_11_0
if (respondsToAdjustedContentInset_) {
inset.left -= (self.adjustedContentInset.left - self.contentInset.left);
}
#endif
self.contentInset = inset;
}
- (CGFloat)mj_insetL
{
return self.mj_inset.left;
}
- (void)setMj_insetR:(CGFloat)mj_insetR
{
UIEdgeInsets inset = self.contentInset;
inset.right = mj_insetR;
#ifdef __IPHONE_11_0
if (respondsToAdjustedContentInset_) {
inset.right -= (self.adjustedContentInset.right - self.contentInset.right);
}
#endif
self.contentInset = inset;
}
- (CGFloat)mj_insetR
{
return self.mj_inset.right;
}
- (void)setMj_offsetX:(CGFloat)mj_offsetX
{
CGPoint offset = self.contentOffset;
offset.x = mj_offsetX;
self.contentOffset = offset;
}
- (CGFloat)mj_offsetX
{
return self.contentOffset.x;
}
- (void)setMj_offsetY:(CGFloat)mj_offsetY
{
CGPoint offset = self.contentOffset;
offset.y = mj_offsetY;
self.contentOffset = offset;
}
- (CGFloat)mj_offsetY
{
return self.contentOffset.y;
}
- (void)setMj_contentW:(CGFloat)mj_contentW
{
CGSize size = self.contentSize;
size.width = mj_contentW;
self.contentSize = size;
}
- (CGFloat)mj_contentW
{
return self.contentSize.width;
}
- (void)setMj_contentH:(CGFloat)mj_contentH
{
CGSize size = self.contentSize;
size.height = mj_contentH;
self.contentSize = size;
}
- (CGFloat)mj_contentH
{
return self.contentSize.height;
}
@end
#pragma clang diagnostic pop

View File

@@ -0,0 +1,36 @@
// 代码地址: https://github.com/CoderMJLee/MJRefresh
// UIScrollView+MJRefresh.h
// MJRefresh
//
// Created by MJ Lee on 15/3/4.
// Copyright (c) 2015年 小码哥. All rights reserved.
// 给ScrollView增加下拉刷新、上拉刷新、 左滑刷新的功能
#import <UIKit/UIKit.h>
#if __has_include(<MJRefresh/MJRefreshConst.h>)
#import <MJRefresh/MJRefreshConst.h>
#else
#import "MJRefreshConst.h"
#endif
@class MJRefreshHeader, MJRefreshFooter, MJRefreshTrailer;
NS_ASSUME_NONNULL_BEGIN
@interface UIScrollView (MJRefresh)
/** 下拉刷新控件 */
@property (strong, nonatomic, nullable) MJRefreshHeader *mj_header;
@property (strong, nonatomic, nullable) MJRefreshHeader *header MJRefreshDeprecated("使用mj_header");
/** 上拉刷新控件 */
@property (strong, nonatomic, nullable) MJRefreshFooter *mj_footer;
@property (strong, nonatomic, nullable) MJRefreshFooter *footer MJRefreshDeprecated("使用mj_footer");
/** 左滑刷新控件 */
@property (strong, nonatomic, nullable) MJRefreshTrailer *mj_trailer;
#pragma mark - other
- (NSInteger)mj_totalDataCount;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,120 @@
// : https://github.com/CoderMJLee/MJRefresh
// UIScrollView+MJRefresh.m
// MJRefresh
//
// Created by MJ Lee on 15/3/4.
// Copyright (c) 2015 . All rights reserved.
//
#import "UIScrollView+MJRefresh.h"
#import "MJRefreshHeader.h"
#import "MJRefreshFooter.h"
#import "MJRefreshTrailer.h"
#import <objc/runtime.h>
@implementation UIScrollView (MJRefresh)
#pragma mark - header
static const char MJRefreshHeaderKey = '\0';
- (void)setMj_header:(MJRefreshHeader *)mj_header
{
if (mj_header != self.mj_header) {
//
[self.mj_header removeFromSuperview];
if (mj_header) {
[self insertSubview:mj_header atIndex:0];
}
//
objc_setAssociatedObject(self, &MJRefreshHeaderKey,
mj_header, OBJC_ASSOCIATION_RETAIN);
}
}
- (MJRefreshHeader *)mj_header
{
return objc_getAssociatedObject(self, &MJRefreshHeaderKey);
}
#pragma mark - footer
static const char MJRefreshFooterKey = '\0';
- (void)setMj_footer:(MJRefreshFooter *)mj_footer
{
if (mj_footer != self.mj_footer) {
//
[self.mj_footer removeFromSuperview];
if (mj_footer) {
[self insertSubview:mj_footer atIndex:0];
}
//
objc_setAssociatedObject(self, &MJRefreshFooterKey,
mj_footer, OBJC_ASSOCIATION_RETAIN);
}
}
- (MJRefreshFooter *)mj_footer
{
return objc_getAssociatedObject(self, &MJRefreshFooterKey);
}
#pragma mark - footer
static const char MJRefreshTrailerKey = '\0';
- (void)setMj_trailer:(MJRefreshTrailer *)mj_trailer {
if (mj_trailer != self.mj_trailer) {
//
[self.mj_trailer removeFromSuperview];
if (mj_trailer) {
[self insertSubview:mj_trailer atIndex:0];
}
//
objc_setAssociatedObject(self, &MJRefreshTrailerKey,
mj_trailer, OBJC_ASSOCIATION_RETAIN);
}
}
- (MJRefreshTrailer *)mj_trailer {
return objc_getAssociatedObject(self, &MJRefreshTrailerKey);
}
#pragma mark -
- (void)setFooter:(MJRefreshFooter *)footer
{
self.mj_footer = footer;
}
- (MJRefreshFooter *)footer
{
return self.mj_footer;
}
- (void)setHeader:(MJRefreshHeader *)header
{
self.mj_header = header;
}
- (MJRefreshHeader *)header
{
return self.mj_header;
}
#pragma mark - other
- (NSInteger)mj_totalDataCount
{
NSInteger totalCount = 0;
if ([self isKindOfClass:[UITableView class]]) {
UITableView *tableView = (UITableView *)self;
for (NSInteger section = 0; section < tableView.numberOfSections; section++) {
totalCount += [tableView numberOfRowsInSection:section];
}
} else if ([self isKindOfClass:[UICollectionView class]]) {
UICollectionView *collectionView = (UICollectionView *)self;
for (NSInteger section = 0; section < collectionView.numberOfSections; section++) {
totalCount += [collectionView numberOfItemsInSection:section];
}
}
return totalCount;
}
@end

View File

@@ -0,0 +1,22 @@
// 代码地址: https://github.com/CoderMJLee/MJRefresh
// UIView+Extension.h
// MJRefresh
//
// Created by MJ Lee on 14-5-28.
// Copyright (c) 2014年 小码哥. All rights reserved.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface UIView (MJExtension)
@property (assign, nonatomic) CGFloat mj_x;
@property (assign, nonatomic) CGFloat mj_y;
@property (assign, nonatomic) CGFloat mj_w;
@property (assign, nonatomic) CGFloat mj_h;
@property (assign, nonatomic) CGSize mj_size;
@property (assign, nonatomic) CGPoint mj_origin;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,83 @@
// : https://github.com/CoderMJLee/MJRefresh
// UIView+Extension.m
// MJRefresh
//
// Created by MJ Lee on 14-5-28.
// Copyright (c) 2014 . All rights reserved.
//
#import "UIView+MJExtension.h"
@implementation UIView (MJExtension)
- (void)setMj_x:(CGFloat)mj_x
{
CGRect frame = self.frame;
frame.origin.x = mj_x;
self.frame = frame;
}
- (CGFloat)mj_x
{
return self.frame.origin.x;
}
- (void)setMj_y:(CGFloat)mj_y
{
CGRect frame = self.frame;
frame.origin.y = mj_y;
self.frame = frame;
}
- (CGFloat)mj_y
{
return self.frame.origin.y;
}
- (void)setMj_w:(CGFloat)mj_w
{
CGRect frame = self.frame;
frame.size.width = mj_w;
self.frame = frame;
}
- (CGFloat)mj_w
{
return self.frame.size.width;
}
- (void)setMj_h:(CGFloat)mj_h
{
CGRect frame = self.frame;
frame.size.height = mj_h;
self.frame = frame;
}
- (CGFloat)mj_h
{
return self.frame.size.height;
}
- (void)setMj_size:(CGSize)mj_size
{
CGRect frame = self.frame;
frame.size = mj_size;
self.frame = frame;
}
- (CGSize)mj_size
{
return self.frame.size;
}
- (void)setMj_origin:(CGPoint)mj_origin
{
CGRect frame = self.frame;
frame.origin = mj_origin;
self.frame = frame;
}
- (CGPoint)mj_origin
{
return self.frame.origin;
}
@end