小说绘上架版本
This commit is contained in:
@@ -0,0 +1,254 @@
|
||||
//
|
||||
// WXYZ_Player.h
|
||||
// WXYZ_Player
|
||||
//
|
||||
// Created by ihoudf on 2017/7/18.
|
||||
// Copyright © 2017年 ihoudf. All rights reserved.
|
||||
//
|
||||
//
|
||||
// WXYZ_Player当前版本:2.0.2
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
#import "WXYZ_PlayerModel.h"
|
||||
#import "TFBasicVoiceHeaderView.h"
|
||||
|
||||
//播放模式
|
||||
typedef NS_ENUM(NSInteger, WXYZ_PlayerMode){
|
||||
WXYZ_PlayerModeOnlyOnce, //单曲只播放一次,默认
|
||||
WXYZ_PlayerModeSingleCycle, //单曲循环
|
||||
WXYZ_PlayerModeOrderCycle, //顺序循环
|
||||
WXYZ_PlayerModeShuffleCycle //随机循环
|
||||
};
|
||||
|
||||
@class WXYZ_Player;
|
||||
|
||||
@protocol WXYZ_PlayerDataSource <NSObject>
|
||||
|
||||
@required
|
||||
|
||||
/**
|
||||
数据源1:音频数组
|
||||
|
||||
@param player WXYZ_Player
|
||||
*/
|
||||
- (NSArray<WXYZ_PlayerModel *> *)audioDataForPlayer:(WXYZ_Player *)player;
|
||||
|
||||
@optional
|
||||
|
||||
/**
|
||||
数据源2:音频信息
|
||||
调用playWithAudioId时,WXYZ_Player会调用此方法请求当前音频的信息
|
||||
根据player.currentAudioModel.audioId获取音频在数组中的位置,传入对应的音频信息model
|
||||
|
||||
@param player WXYZ_Player
|
||||
*/
|
||||
- (WXYZ_PlayerInfoModel *)audioInfoForPlayer:(WXYZ_Player *)player;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@protocol WXYZ_PlayerDelegate <NSObject>
|
||||
|
||||
@optional
|
||||
/**
|
||||
代理1:音频已经加入播放队列
|
||||
|
||||
@param player WXYZ_Player
|
||||
*/
|
||||
- (void)playerAudioAddToPlayQueue:(WXYZ_Player *)player;
|
||||
|
||||
/**
|
||||
代理2:准备播放
|
||||
|
||||
@param player WXYZ_Player
|
||||
*/
|
||||
- (void)playerReadyToPlay:(WXYZ_Player *)player;
|
||||
|
||||
/**
|
||||
代理3:缓冲进度代理 (属性isObserveBufferProgress(默认YES)为YES时有效)
|
||||
|
||||
@param player WXYZ_Player
|
||||
@param bufferProgress 缓冲进度
|
||||
*/
|
||||
- (void)player:(WXYZ_Player *)player bufferProgress:(CGFloat)bufferProgress;
|
||||
|
||||
/**
|
||||
代理4:播放进度代理 (属性isObserveProgress(默认YES)为YES时有效)
|
||||
|
||||
@param player WXYZ_Player
|
||||
@param progress 播放进度
|
||||
@param currentTime 当前播放到的时间
|
||||
*/
|
||||
- (void)player:(WXYZ_Player *)player progress:(CGFloat)progress currentTime:(CGFloat)currentTime totalTime:(CGFloat)totalTime;
|
||||
|
||||
/**
|
||||
代理5:播放结束代理
|
||||
(默认播放结束后调用next,如果实现此代理,播放结束逻辑由您处理)
|
||||
|
||||
@param player FPlayer
|
||||
*/
|
||||
- (void)playerDidPlayToEndTime:(WXYZ_Player *)player;
|
||||
|
||||
/**
|
||||
代理7:播放器被系统打断代理
|
||||
(默认被系统打断暂停播放,打断结束检测能够播放则恢复播放,如果实现此代理,打断逻辑由您处理)
|
||||
|
||||
@param player WXYZ_Player
|
||||
@param isInterrupted YES:被系统打断开始 NO:被系统打断结束
|
||||
*/
|
||||
- (void)player:(WXYZ_Player *)player isInterrupted:(BOOL)isInterrupted;
|
||||
|
||||
/**
|
||||
代理8:监听耳机插入拔出代理
|
||||
|
||||
@param player WXYZ_Player
|
||||
@param isHeadphone YES:插入 NO:拔出
|
||||
*/
|
||||
- (void)player:(WXYZ_Player *)player isHeadphone:(BOOL)isHeadphone;
|
||||
|
||||
/**
|
||||
代理9:播放状态改变
|
||||
*/
|
||||
- (void)playerStateChange:(TFBasicVoicePlayerState)playerState;
|
||||
|
||||
// 远程控制切换上一首作品
|
||||
- (void)audioPlayerRemoteCenterSwitchToPrevious;
|
||||
|
||||
// 远程控制切换下一首作品
|
||||
- (void)audioPlayerRemoteCenterSwitchToNext;
|
||||
|
||||
@end
|
||||
|
||||
/**
|
||||
WXYZ_Player播放管理器
|
||||
*/
|
||||
@interface WXYZ_Player : NSObject
|
||||
|
||||
#pragma mark - 初始化和操作
|
||||
|
||||
@property (nonatomic, weak) id<WXYZ_PlayerDataSource> dataSource;
|
||||
|
||||
@property (nonatomic, weak) id<WXYZ_PlayerDelegate> delegate;
|
||||
|
||||
// 是否禁止上一首远程控制键可用
|
||||
@property (nonatomic, assign) BOOL remoteCenterPreviousEnable;
|
||||
|
||||
// 是否禁止下一首远程控制键可用
|
||||
@property (nonatomic, assign) BOOL remoteCenterNextEnable;
|
||||
|
||||
/**
|
||||
播放模式,默认WXYZ_PlayerModeOnlyOnce。
|
||||
*/
|
||||
@property (nonatomic, assign) WXYZ_PlayerMode playMode;
|
||||
|
||||
/**
|
||||
播放倍速
|
||||
*/
|
||||
@property (nonatomic, assign) CGFloat playRate;
|
||||
|
||||
/**
|
||||
单例
|
||||
*/
|
||||
+ (WXYZ_Player *)sharedPlayer;
|
||||
|
||||
/**
|
||||
初始化播放器
|
||||
*/
|
||||
- (void)initPlayerWithUserId:(NSString *)userId;
|
||||
|
||||
/**
|
||||
刷新数据源数据
|
||||
*/
|
||||
- (void)reloadData;
|
||||
|
||||
/**
|
||||
选择audioId对应的音频开始播放。
|
||||
说明:WXYZ_Player通过数据源方法提前获取数据,通过playWithAudioId选择对应音频播放。
|
||||
而在删除、增加音频后需要调用[[WXYZ_Player shareInstance] reloadData];刷新数据。
|
||||
*/
|
||||
- (void)playWithAudioId:(NSUInteger)audioId;
|
||||
|
||||
/**
|
||||
播放
|
||||
*/
|
||||
- (void)play;
|
||||
|
||||
/**
|
||||
暂停
|
||||
*/
|
||||
- (void)pause;
|
||||
|
||||
/**
|
||||
下一首
|
||||
*/
|
||||
- (void)next;
|
||||
|
||||
/**
|
||||
上一首
|
||||
*/
|
||||
- (void)last;
|
||||
|
||||
/**
|
||||
音频跳转
|
||||
|
||||
@param value 时间百分比
|
||||
@param completionBlock seek结束
|
||||
*/
|
||||
- (void)seekToTime:(CGFloat)value completionBlock:(void(^)(void))completionBlock;
|
||||
|
||||
/**
|
||||
倍速播放(iOS10之后系统支持的倍速常数有0.50, 0.67, 0.80, 1.0, 1.25, 1.50和2.0)
|
||||
@param rate 倍速
|
||||
*/
|
||||
- (void)setRate:(CGFloat)rate;
|
||||
|
||||
/**
|
||||
释放播放器,还原其他播放器
|
||||
*/
|
||||
- (void)deallocPlayer;
|
||||
|
||||
|
||||
#pragma mark - 状态类
|
||||
|
||||
/**
|
||||
播放器状态
|
||||
*/
|
||||
@property (nonatomic, readonly, assign) TFBasicVoicePlayerState state;
|
||||
|
||||
/**
|
||||
当前正在播放的音频model
|
||||
*/
|
||||
@property (nonatomic, readonly, strong) WXYZ_PlayerModel *currentAudioModel;
|
||||
|
||||
/**
|
||||
当前正在播放的音频信息model
|
||||
*/
|
||||
@property (nonatomic, readonly, strong) WXYZ_PlayerInfoModel *currentAudioInfoModel;
|
||||
|
||||
/**
|
||||
当前音频缓冲进度
|
||||
*/
|
||||
@property (nonatomic, readonly, assign) CGFloat bufferProgress;
|
||||
|
||||
/**
|
||||
当前音频播放进度
|
||||
*/
|
||||
@property (nonatomic, readonly, assign) CGFloat progress;
|
||||
|
||||
/**
|
||||
当前音频当前时间
|
||||
*/
|
||||
@property (nonatomic, readonly, assign) CGFloat currentTime;
|
||||
|
||||
/**
|
||||
当前音频总时长
|
||||
*/
|
||||
@property (nonatomic, readonly, assign) CGFloat totalTime;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,628 @@
|
||||
//
|
||||
// WXYZ_Player.m
|
||||
// WXYZ_Player
|
||||
//
|
||||
// Created by ihoudf on 2017/7/18.
|
||||
// Copyright © 2017年 ihoudf. All rights reserved.
|
||||
//
|
||||
|
||||
#import "WXYZ_Player.h"
|
||||
#import <MediaPlayer/MediaPlayer.h>
|
||||
#import "WXYZ_TouchAssistantView.h"
|
||||
|
||||
#define WXYZ_PlayerHighGlobalQueue dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)
|
||||
#define WXYZ_PlayerDefaultGlobalQueue dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
|
||||
|
||||
/**Asset KEY*/
|
||||
NSString * const WXYZPlayableKey = @"playable";
|
||||
/**PlayerItem KEY*/
|
||||
NSString * const WXYZStatusKey = @"status";
|
||||
NSString * const WXYZLoadedTimeRangesKey = @"loadedTimeRanges";
|
||||
NSString * const WXYZPlaybackBufferEmptyKey = @"playbackBufferEmpty";
|
||||
NSString * const WXYZPlaybackLikelyToKeepUpKey = @"playbackLikelyToKeepUp";
|
||||
|
||||
@interface WXYZ_Player()
|
||||
{
|
||||
BOOL _isOtherPlaying; // 其他应用是否正在播放
|
||||
BOOL _isBackground; // 是否进入后台
|
||||
BOOL _isSeek; // 正在seek
|
||||
BOOL _isNaturalToEndTime; // 是否是自然结束
|
||||
dispatch_group_t _netGroupQueue; // 组队列-网络
|
||||
dispatch_group_t _dataGroupQueue; // 组队列-数据
|
||||
CGFloat _seekValue; // seek value
|
||||
NSMutableDictionary *_remoteInfoDictionary;//控制中心信息
|
||||
}
|
||||
/** player */
|
||||
@property (nonatomic, strong) AVPlayer *player;
|
||||
/** playerItem */
|
||||
@property (nonatomic, strong) AVPlayerItem *playerItem;
|
||||
/** 播放进度监测 */
|
||||
@property (nonatomic, strong) id timeObserver;
|
||||
/** model数据数组 */
|
||||
@property (nonatomic, strong) NSMutableArray<WXYZ_PlayerModel *> *playerModelArray;
|
||||
|
||||
@property (nonatomic, copy) void(^seekCompletionBlock)(void);
|
||||
|
||||
@property (nonatomic, assign) UIBackgroundTaskIdentifier bgTaskId;
|
||||
|
||||
@property (nonatomic, readwrite, strong) WXYZ_PlayerModel *currentAudioModel;
|
||||
@property (nonatomic, readwrite, strong) WXYZ_PlayerInfoModel *currentAudioInfoModel;
|
||||
@property (nonatomic, readwrite, assign) TFBasicVoicePlayerState state;
|
||||
@property (nonatomic, readwrite, assign) CGFloat bufferProgress;
|
||||
@property (nonatomic, readwrite, assign) CGFloat progress;
|
||||
@property (nonatomic, readwrite, assign) CGFloat currentTime;
|
||||
@property (nonatomic, readwrite, assign) CGFloat totalTime;
|
||||
|
||||
@end
|
||||
|
||||
@implementation WXYZ_Player
|
||||
|
||||
#pragma mark - 初始化
|
||||
+ (WXYZ_Player *)sharedPlayer {
|
||||
static WXYZ_Player *player = nil;
|
||||
static dispatch_once_t predicate;
|
||||
dispatch_once(&predicate, ^{
|
||||
player = [[[self class] alloc] init];
|
||||
});
|
||||
return player;
|
||||
}
|
||||
|
||||
- (void)dealloc{
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
||||
UIBackgroundTaskIdentifier taskID = [[UIApplication sharedExtensionApplication] beginBackgroundTaskWithExpirationHandler:^{}];
|
||||
if (taskID != UIBackgroundTaskInvalid) {
|
||||
[[UIApplication sharedExtensionApplication] endBackgroundTask:taskID];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - 初始化播放器
|
||||
- (void)initPlayerWithUserId:(NSString *)userId{
|
||||
|
||||
[[AVAudioSession sharedInstance] setActive:YES error:nil];
|
||||
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil];
|
||||
|
||||
_isOtherPlaying = [AVAudioSession sharedInstance].otherAudioPlaying;
|
||||
|
||||
self.playMode = WXYZ_PlayerModeOnlyOnce;
|
||||
self.state = TFBasicVoicePlayerStateStoped;
|
||||
_isBackground = NO;
|
||||
|
||||
_netGroupQueue = dispatch_group_create();
|
||||
_dataGroupQueue = dispatch_group_create();
|
||||
|
||||
[self addPlayerObserver];
|
||||
[self addRemoteControlHandler];
|
||||
}
|
||||
|
||||
- (void)addPlayerObserver{
|
||||
//将要进入后台
|
||||
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
|
||||
[notificationCenter addObserver:self
|
||||
selector:@selector(playerWillResignActive)
|
||||
name:UIApplicationWillResignActiveNotification
|
||||
object:nil];
|
||||
//已经进入前台
|
||||
[notificationCenter addObserver:self
|
||||
selector:@selector(playerDidEnterForeground)
|
||||
name:UIApplicationDidBecomeActiveNotification
|
||||
object:nil];
|
||||
//监测耳机
|
||||
[notificationCenter addObserver:self
|
||||
selector:@selector(playerAudioRouteChange:)
|
||||
name:AVAudioSessionRouteChangeNotification
|
||||
object:nil];
|
||||
//监听播放器被打断(别的软件播放音乐,来电话)
|
||||
[notificationCenter addObserver:self
|
||||
selector:@selector(playerAudioBeInterrupted:)
|
||||
name:AVAudioSessionInterruptionNotification
|
||||
object:[AVAudioSession sharedInstance]];
|
||||
}
|
||||
|
||||
- (void)playerWillResignActive{
|
||||
_isBackground = YES;
|
||||
_bgTaskId = [self backgroundPlayerID:_bgTaskId];
|
||||
}
|
||||
|
||||
- (UIBackgroundTaskIdentifier)backgroundPlayerID:(UIBackgroundTaskIdentifier)backTaskId {
|
||||
// 设置并激活音频会话类别
|
||||
AVAudioSession *session = [AVAudioSession sharedInstance];
|
||||
[session setCategory:AVAudioSessionCategoryPlayback error:nil];
|
||||
[session setActive:YES error:nil];
|
||||
// 允许应用程序接收远程控制
|
||||
// 设置后台任务ID
|
||||
UIBackgroundTaskIdentifier taskId = UIBackgroundTaskInvalid;
|
||||
taskId = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:nil];
|
||||
if(taskId != UIBackgroundTaskInvalid &&
|
||||
backTaskId != UIBackgroundTaskInvalid) {
|
||||
[[UIApplication sharedApplication] endBackgroundTask:backTaskId];
|
||||
}
|
||||
return taskId;
|
||||
}
|
||||
|
||||
- (void)playerDidEnterForeground{
|
||||
_isBackground = NO;
|
||||
}
|
||||
|
||||
- (void)playerAudioRouteChange:(NSNotification *)notification{
|
||||
NSInteger routeChangeReason = [notification.userInfo[AVAudioSessionRouteChangeReasonKey] integerValue];
|
||||
switch (routeChangeReason) {
|
||||
case AVAudioSessionRouteChangeReasonNewDeviceAvailable://耳机插入
|
||||
if (self.delegate && [self.delegate respondsToSelector:@selector(player:isHeadphone:)]) {
|
||||
[self.delegate player:self isHeadphone:YES];
|
||||
}
|
||||
break;
|
||||
case AVAudioSessionRouteChangeReasonOldDeviceUnavailable://耳机拔出,停止播放操作
|
||||
if (self.delegate && [self.delegate respondsToSelector:@selector(player:isHeadphone:)]) {
|
||||
[self.delegate player:self isHeadphone:NO];
|
||||
}else{
|
||||
[self pause];
|
||||
}
|
||||
break;
|
||||
case AVAudioSessionRouteChangeReasonCategoryChange:
|
||||
//
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)playerAudioBeInterrupted:(NSNotification *)notification{
|
||||
NSDictionary *dic = notification.userInfo;
|
||||
if ([dic[AVAudioSessionInterruptionTypeKey] integerValue] == 1) {//打断开始
|
||||
if (self.delegate && [self.delegate respondsToSelector:@selector(player:isInterrupted:)]) {
|
||||
[self.delegate player:self isInterrupted:YES];
|
||||
}else{
|
||||
[self pause];
|
||||
}
|
||||
}else {//打断结束
|
||||
if (self.delegate && [self.delegate respondsToSelector:@selector(player:isInterrupted:)]) {
|
||||
[self.delegate player:self isInterrupted:NO];
|
||||
}else{
|
||||
if ([notification.userInfo[AVAudioSessionInterruptionOptionKey] unsignedIntegerValue] == 1) {
|
||||
[self play];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
-(void)playerDidPlayToEndTime:(NSNotification *)notification{
|
||||
// 其他音视频播放结束也会触发回调,例如穿山甲的激励视频。
|
||||
if (notification.object != self.playerItem) return;
|
||||
if (self.delegate && [self.delegate respondsToSelector:@selector(playerDidPlayToEndTime:)]) {
|
||||
[self.delegate playerDidPlayToEndTime:self];
|
||||
}else{
|
||||
_isNaturalToEndTime = YES;
|
||||
[self next];
|
||||
}
|
||||
}
|
||||
|
||||
/**远程线控*/
|
||||
- (void)addRemoteControlHandler
|
||||
{
|
||||
if (@available (iOS 7.1, *)) {
|
||||
kCodeSync([[UIApplication sharedApplication] endReceivingRemoteControlEvents]);
|
||||
kCodeSync([[UIApplication sharedApplication] beginReceivingRemoteControlEvents]);
|
||||
MPRemoteCommandCenter *center = [MPRemoteCommandCenter sharedCommandCenter];
|
||||
[self addRemoteCommand:center.playCommand selector:@selector(play)];
|
||||
[self addRemoteCommand:center.pauseCommand selector:@selector(pause)];
|
||||
[self addRemoteCommand:center.previousTrackCommand selector:@selector(last)];
|
||||
[self addRemoteCommand:center.nextTrackCommand selector:@selector(next)];
|
||||
[center.togglePlayPauseCommand addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull event) {
|
||||
if ([WXYZ_Player sharedPlayer].state == TFBasicVoicePlayerStatePlaying) {
|
||||
[[WXYZ_Player sharedPlayer] pause];
|
||||
}else{
|
||||
[[WXYZ_Player sharedPlayer] play];
|
||||
}
|
||||
return MPRemoteCommandHandlerStatusSuccess;
|
||||
}];
|
||||
|
||||
if (@available (iOS 9.1,*)) {
|
||||
[center.changePlaybackPositionCommand addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull event) {
|
||||
MPChangePlaybackPositionCommandEvent *positionEvent = (MPChangePlaybackPositionCommandEvent *)event;
|
||||
if (self.totalTime > 0) {
|
||||
[self seekToTime:positionEvent.positionTime / self.totalTime completionBlock:nil];
|
||||
}
|
||||
return MPRemoteCommandHandlerStatusSuccess;
|
||||
}];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)addRemoteCommand:(MPRemoteCommand *)command selector:(SEL)selector{
|
||||
[command addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull event) {
|
||||
if ([self respondsToSelector:selector]) {
|
||||
IMP imp = [self methodForSelector:selector];
|
||||
void (*func)(id, SEL) = (void *)imp;
|
||||
func(self, selector);
|
||||
}
|
||||
return MPRemoteCommandHandlerStatusSuccess;
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)setRemoteCenterPreviousEnable:(BOOL)remoteCenterPreviousEnable
|
||||
{
|
||||
_remoteCenterPreviousEnable = remoteCenterPreviousEnable;
|
||||
[MPRemoteCommandCenter sharedCommandCenter].previousTrackCommand.enabled = remoteCenterPreviousEnable;
|
||||
}
|
||||
|
||||
- (void)setRemoteCenterNextEnable:(BOOL)remoteCenterNextEnable
|
||||
{
|
||||
_remoteCenterNextEnable = remoteCenterNextEnable;
|
||||
[MPRemoteCommandCenter sharedCommandCenter].nextTrackCommand.enabled = remoteCenterNextEnable;
|
||||
}
|
||||
|
||||
#pragma mark - 数据源
|
||||
|
||||
- (void)reloadData{
|
||||
if (self.dataSource && [self.dataSource respondsToSelector:@selector(audioDataForPlayer:)]) {
|
||||
if (!self.playerModelArray) {
|
||||
self.playerModelArray = [NSMutableArray array];
|
||||
}
|
||||
if (self.playerModelArray.count != 0) {
|
||||
[self.playerModelArray removeAllObjects];
|
||||
}
|
||||
dispatch_group_enter(_dataGroupQueue);
|
||||
dispatch_group_async(_dataGroupQueue, WXYZ_PlayerHighGlobalQueue, ^{
|
||||
dispatch_async(WXYZ_PlayerHighGlobalQueue, ^{
|
||||
|
||||
[self.playerModelArray addObjectsFromArray:[self.dataSource audioDataForPlayer:self]];
|
||||
|
||||
//更新currentAudioId
|
||||
if (self.currentAudioModel.audioUrl) {
|
||||
[self.playerModelArray enumerateObjectsWithOptions:(NSEnumerationConcurrent) usingBlock:^(WXYZ_PlayerModel * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
|
||||
if ([obj.audioUrl.absoluteString isEqualToString:self.currentAudioModel.audioUrl.absoluteString]) {
|
||||
self.currentAudioModel.audioId = idx;
|
||||
*stop = YES;
|
||||
}
|
||||
}];
|
||||
}
|
||||
dispatch_group_leave(self->_dataGroupQueue);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - 播放 IMPORTANT
|
||||
|
||||
- (void)playWithAudioId:(NSUInteger)audioId{
|
||||
dispatch_group_notify(_dataGroupQueue, WXYZ_PlayerHighGlobalQueue, ^{
|
||||
if (self.playerModelArray.count > audioId) {
|
||||
self.currentAudioModel = self.playerModelArray[audioId];
|
||||
[self audioPrePlay];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
- (void)audioPrePlay{
|
||||
[self reset];
|
||||
|
||||
if (self.dataSource && [self.dataSource respondsToSelector:@selector(audioInfoForPlayer:)]) {
|
||||
self.currentAudioInfoModel = [self.dataSource audioInfoForPlayer:self];
|
||||
}
|
||||
|
||||
if (self.delegate && [self.delegate respondsToSelector:@selector(playerAudioAddToPlayQueue:)]) {
|
||||
[self.delegate playerAudioAddToPlayQueue:self];
|
||||
}
|
||||
|
||||
[self loadPlayerItemWithURL:self.currentAudioModel.audioUrl];
|
||||
}
|
||||
|
||||
- (void)loadPlayerItemWithURL:(NSURL *)URL{
|
||||
self.playerItem = [[AVPlayerItem alloc] initWithURL:URL];
|
||||
[self loadPlayer];
|
||||
}
|
||||
|
||||
- (void)loadPlayerItemWithAsset:(AVURLAsset *)asset{
|
||||
self.playerItem = [AVPlayerItem playerItemWithAsset:asset];
|
||||
[self loadPlayer];
|
||||
}
|
||||
|
||||
- (void)loadPlayer{
|
||||
self.player = [[AVPlayer alloc] initWithPlayerItem:self.playerItem];
|
||||
if (@available(iOS 10.0,*)) {
|
||||
self.player.automaticallyWaitsToMinimizeStalling = NO;
|
||||
}
|
||||
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||
// 处理章节开始播放,但监听还未执行的问题
|
||||
[self play];
|
||||
});
|
||||
[self addProgressObserver];
|
||||
[self addPlayingCenterInfo];
|
||||
}
|
||||
|
||||
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{
|
||||
if (object == self.player.currentItem) {
|
||||
if ([keyPath isEqualToString:WXYZStatusKey]) {
|
||||
AVPlayerStatus status = [[change objectForKey:NSKeyValueChangeNewKey] integerValue];
|
||||
switch (status) {
|
||||
case AVPlayerItemStatusUnknown:
|
||||
self.state = TFBasicVoicePlayerStateStoped;
|
||||
if (self.delegate && [self.delegate respondsToSelector:@selector(playerStateChange:)]) {
|
||||
[self.delegate playerStateChange:self.state];
|
||||
}
|
||||
break;
|
||||
case AVPlayerItemStatusReadyToPlay:
|
||||
if (self.delegate && [self.delegate respondsToSelector:@selector(playerReadyToPlay:)]) {
|
||||
if (self.state != TFBasicVoicePlayerStatePause) {
|
||||
[self.delegate playerReadyToPlay:self];
|
||||
}
|
||||
}
|
||||
break;
|
||||
case AVPlayerItemStatusFailed:
|
||||
self.state = TFBasicVoicePlayerStateFail;
|
||||
if (self.delegate && [self.delegate respondsToSelector:@selector(playerStateChange:)]) {
|
||||
[self.delegate playerStateChange:self.state];
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} else if ([keyPath isEqualToString:WXYZLoadedTimeRangesKey]) {
|
||||
[self addBufferProgressObserver];
|
||||
} else if ([keyPath isEqualToString:WXYZPlaybackBufferEmptyKey]) {
|
||||
|
||||
} else if ([keyPath isEqualToString:WXYZPlaybackLikelyToKeepUpKey]) {
|
||||
|
||||
}
|
||||
} else {
|
||||
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - 缓冲进度 播放进度 歌曲锁屏信息 音频跳转
|
||||
|
||||
- (void)addBufferProgressObserver{
|
||||
self.totalTime = CMTimeGetSeconds(self.playerItem.asset.duration);
|
||||
|
||||
CMTimeRange timeRange = [self.playerItem.loadedTimeRanges.firstObject CMTimeRangeValue];
|
||||
CGFloat startSeconds = CMTimeGetSeconds(timeRange.start);
|
||||
CGFloat durationSeconds = CMTimeGetSeconds(timeRange.duration);
|
||||
if (self.totalTime != 0) {//避免出现inf
|
||||
self.bufferProgress = (startSeconds + durationSeconds) / self.totalTime;
|
||||
}
|
||||
if (self.delegate && [self.delegate respondsToSelector:@selector(player:bufferProgress:)]) {
|
||||
[self.delegate player:self bufferProgress:self.bufferProgress];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)addProgressObserver
|
||||
{
|
||||
WS(weakSelf)
|
||||
self.timeObserver = [self.player addPeriodicTimeObserverForInterval:CMTimeMakeWithSeconds(1.0, 1.0) queue:nil usingBlock:^(CMTime time){
|
||||
SS(sSelf)
|
||||
AVPlayerItem *currentItem = sSelf.playerItem;
|
||||
NSArray *loadedRanges = currentItem.seekableTimeRanges;
|
||||
if (loadedRanges.count > 0 && currentItem.duration.timescale != 0){
|
||||
|
||||
if (sSelf.state != TFBasicVoicePlayerStatePlaying && sSelf.state != TFBasicVoicePlayerStatePause) {
|
||||
sSelf.state = TFBasicVoicePlayerStatePlaying;
|
||||
if (sSelf.delegate && [sSelf.delegate respondsToSelector:@selector(playerStateChange:)]) {
|
||||
[sSelf.delegate playerStateChange:sSelf.state];
|
||||
}
|
||||
}
|
||||
|
||||
CGFloat currentT = (CGFloat)CMTimeGetSeconds(time);
|
||||
sSelf.currentTime = currentT + (sSelf.totalTime > 1?1:0);
|
||||
if (sSelf.totalTime != 0 && !isnan(sSelf.totalTime)) {// 避免出现inf
|
||||
sSelf.progress = (CMTimeGetSeconds([currentItem currentTime]) + (sSelf.totalTime > 1?1:0)) / sSelf.totalTime;
|
||||
}
|
||||
|
||||
if (sSelf.delegate && [sSelf.delegate respondsToSelector:@selector(player:progress:currentTime:totalTime:)]) {
|
||||
[sSelf.delegate player:sSelf progress:sSelf.progress currentTime:currentT totalTime:sSelf.totalTime];
|
||||
}
|
||||
|
||||
[sSelf updatePlayingCenterInfo];
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)addPlayingCenterInfo{
|
||||
_remoteInfoDictionary = [NSMutableDictionary dictionary];
|
||||
|
||||
if (self.currentAudioInfoModel.audioName) {
|
||||
_remoteInfoDictionary[MPMediaItemPropertyTitle] = self.currentAudioInfoModel.audioName;
|
||||
}
|
||||
if (self.currentAudioInfoModel.audioAlbum) {
|
||||
_remoteInfoDictionary[MPMediaItemPropertyAlbumTitle] = self.currentAudioInfoModel.audioAlbum;
|
||||
}
|
||||
if (self.currentAudioInfoModel.audioSinger) {
|
||||
_remoteInfoDictionary[MPMediaItemPropertyArtist] = self.currentAudioInfoModel.audioSinger;
|
||||
}
|
||||
if ([self.currentAudioInfoModel.audioImage isKindOfClass:[UIImage class]] && self.currentAudioInfoModel.audioImage) {
|
||||
|
||||
MPMediaItemArtwork *artwork = [[MPMediaItemArtwork alloc] initWithBoundsSize:CGSizeMake(BOOK_WIDTH, BOOK_HEIGHT) requestHandler:^UIImage * _Nonnull(CGSize size) {
|
||||
return self.currentAudioInfoModel.audioImage;
|
||||
}];
|
||||
|
||||
_remoteInfoDictionary[MPMediaItemPropertyArtwork] = artwork;
|
||||
}
|
||||
_remoteInfoDictionary[MPNowPlayingInfoPropertyPlaybackRate] = [NSNumber numberWithFloat:1.0];
|
||||
[MPNowPlayingInfoCenter defaultCenter].nowPlayingInfo = _remoteInfoDictionary;
|
||||
}
|
||||
|
||||
- (void)updatePlayingCenterInfo{
|
||||
if (!_isBackground) {return;}
|
||||
|
||||
if ([WXYZ_TouchAssistantView sharedManager].productionType == TFProductionTypeAudio) {
|
||||
_remoteInfoDictionary[MPNowPlayingInfoPropertyElapsedPlaybackTime] = [NSNumber numberWithDouble:CMTimeGetSeconds(self.playerItem.currentTime)];
|
||||
_remoteInfoDictionary[MPMediaItemPropertyPlaybackDuration] = [NSNumber numberWithDouble:CMTimeGetSeconds(self.playerItem.duration)];
|
||||
[MPNowPlayingInfoCenter defaultCenter].nowPlayingInfo = _remoteInfoDictionary;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)seekToTime:(CGFloat)value completionBlock:(void (^)(void))completionBlock{
|
||||
_isSeek = YES;
|
||||
// 先暂停
|
||||
BOOL resumePlay = NO;
|
||||
if (self.state == TFBasicVoicePlayerStatePlaying || self.state == TFBasicVoicePlayerStateLoading) {
|
||||
self.state = TFBasicVoicePlayerStatePause;
|
||||
[self.player pause];
|
||||
resumePlay = YES;
|
||||
}
|
||||
|
||||
[self didSeekToTime:value resumePlay:resumePlay completionBlock:completionBlock];
|
||||
}
|
||||
|
||||
- (void)didSeekToTime:(CGFloat)value resumePlay:(BOOL)resumePlay completionBlock:(void (^)(void))completionBlock
|
||||
{
|
||||
[self.player seekToTime:CMTimeMake(floorf(self.totalTime * value), 1)
|
||||
toleranceBefore:kCMTimeZero
|
||||
toleranceAfter:kCMTimeZero
|
||||
completionHandler:^(BOOL finished) {
|
||||
if (finished) {
|
||||
self->_isSeek = NO;
|
||||
[self play];
|
||||
if (!resumePlay) {
|
||||
[self pause];
|
||||
}
|
||||
if (completionBlock) {
|
||||
completionBlock();
|
||||
}
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
/**倍速播放*/
|
||||
- (void)setRate:(CGFloat)rate {
|
||||
_playRate = rate;
|
||||
for (AVPlayerItemTrack *track in self.playerItem.tracks){
|
||||
if ([track.assetTrack.mediaType isEqual:AVMediaTypeAudio]){
|
||||
track.enabled = YES;
|
||||
}
|
||||
}
|
||||
|
||||
if (self.state == TFBasicVoicePlayerStatePlaying) {
|
||||
self.player.rate = rate;
|
||||
}
|
||||
}
|
||||
|
||||
/**释放播放器*/
|
||||
- (void)deallocPlayer{
|
||||
|
||||
[self reset];
|
||||
|
||||
self.state = TFBasicVoicePlayerStateStoped;
|
||||
|
||||
if (@available(iOS 7.1, *)) {
|
||||
[[UIApplication sharedApplication] endReceivingRemoteControlEvents];
|
||||
MPRemoteCommandCenter *center = [MPRemoteCommandCenter sharedCommandCenter];
|
||||
[[center playCommand] removeTarget:self];
|
||||
[[center pauseCommand] removeTarget:self];
|
||||
[[center nextTrackCommand] removeTarget:self];
|
||||
[[center previousTrackCommand] removeTarget:self];
|
||||
[[center togglePlayPauseCommand] removeTarget:self];
|
||||
if(@available(iOS 9.1, *)) {
|
||||
[center.changePlaybackPositionCommand removeTarget:self];
|
||||
}
|
||||
}
|
||||
|
||||
if (_isOtherPlaying) {
|
||||
[[AVAudioSession sharedInstance] setActive:NO withOptions:AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation error:nil];
|
||||
}else{
|
||||
[[AVAudioSession sharedInstance] setActive:NO error:nil];
|
||||
}
|
||||
[self.player.currentItem cancelPendingSeeks];
|
||||
[self.player.currentItem.asset cancelLoading];
|
||||
|
||||
if (self.playerModelArray) {
|
||||
self.playerModelArray = nil;
|
||||
}
|
||||
|
||||
if (self.playerItem) {
|
||||
self.playerItem = nil;
|
||||
}
|
||||
|
||||
if (self.player) {
|
||||
self.player = nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)reset{
|
||||
if (self.state == TFBasicVoicePlayerStatePlaying) {
|
||||
[self.player pause];
|
||||
}
|
||||
|
||||
//移除进度观察者
|
||||
if (self.timeObserver) {
|
||||
[self.player removeTimeObserver:self.timeObserver];
|
||||
self.timeObserver = nil;
|
||||
}
|
||||
|
||||
//重置
|
||||
self.progress = .0f;
|
||||
self.bufferProgress = .0f;
|
||||
self.currentTime = .0f;
|
||||
self.totalTime = .0f;
|
||||
}
|
||||
|
||||
#pragma mark - 播放 暂停 下一首 上一首
|
||||
/**播放*/
|
||||
- (void)play
|
||||
{
|
||||
[self.player play];
|
||||
self.player.rate = self.playRate;
|
||||
self.state = TFBasicVoicePlayerStateLoading;
|
||||
if (self.delegate && [self.delegate respondsToSelector:@selector(playerStateChange:)]) {
|
||||
[self.delegate playerStateChange:self.state];
|
||||
}
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[[UIApplication sharedApplication] endReceivingRemoteControlEvents];
|
||||
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
|
||||
});
|
||||
}
|
||||
|
||||
/**暂停*/
|
||||
- (void)pause
|
||||
{
|
||||
[self.player pause];
|
||||
self.state = TFBasicVoicePlayerStatePause;
|
||||
if (self.delegate && [self.delegate respondsToSelector:@selector(playerStateChange:)]) {
|
||||
[self.delegate playerStateChange:self.state];
|
||||
}
|
||||
}
|
||||
|
||||
/**下一首*/
|
||||
- (void)next
|
||||
{
|
||||
if (self.delegate && [self.delegate respondsToSelector:@selector(audioPlayerRemoteCenterSwitchToNext)]) {
|
||||
[self.delegate audioPlayerRemoteCenterSwitchToNext];
|
||||
}
|
||||
}
|
||||
|
||||
/**上一首*/
|
||||
- (void)last
|
||||
{
|
||||
if (self.delegate && [self.delegate respondsToSelector:@selector(audioPlayerRemoteCenterSwitchToPrevious)]) {
|
||||
[self.delegate audioPlayerRemoteCenterSwitchToPrevious];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - setter
|
||||
|
||||
- (void)setCategory:(AVAudioSessionCategory)category{
|
||||
[[AVAudioSession sharedInstance] setCategory:category error:nil];
|
||||
}
|
||||
|
||||
- (void)setPlayerItem:(AVPlayerItem *)playerItem{
|
||||
if (_playerItem == playerItem) {
|
||||
return;
|
||||
}
|
||||
if (_playerItem) {
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self name:AVPlayerItemDidPlayToEndTimeNotification object:nil];
|
||||
[_playerItem removeObserver:self forKeyPath:WXYZStatusKey];
|
||||
[_playerItem removeObserver:self forKeyPath:WXYZLoadedTimeRangesKey];
|
||||
[_playerItem removeObserver:self forKeyPath:WXYZPlaybackBufferEmptyKey];
|
||||
[_playerItem removeObserver:self forKeyPath:WXYZPlaybackLikelyToKeepUpKey];
|
||||
}
|
||||
_playerItem = playerItem;
|
||||
if (playerItem) {
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playerDidPlayToEndTime:) name:AVPlayerItemDidPlayToEndTimeNotification object:nil];
|
||||
[playerItem addObserver:self forKeyPath:WXYZStatusKey options:NSKeyValueObservingOptionNew context:nil];
|
||||
[playerItem addObserver:self forKeyPath:WXYZLoadedTimeRangesKey options:NSKeyValueObservingOptionNew context:nil];
|
||||
[playerItem addObserver:self forKeyPath:WXYZPlaybackBufferEmptyKey options:NSKeyValueObservingOptionNew context:nil];
|
||||
[playerItem addObserver:self forKeyPath:WXYZPlaybackLikelyToKeepUpKey options:NSKeyValueObservingOptionNew context:nil];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,45 @@
|
||||
//
|
||||
// WXYZ_PlayerModel.h
|
||||
// WXYZ_Player
|
||||
//
|
||||
// Created by ihoudf on 2017/7/18.
|
||||
// Copyright © 2017年 ihoudf. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
音频数据model类(必传)
|
||||
*/
|
||||
@interface WXYZ_PlayerModel : NSObject
|
||||
|
||||
@property (nonatomic, assign) NSUInteger audioId; // 音频Id(从0开始,仅标识当前音频在数组中的位置)
|
||||
|
||||
@property (nonatomic, strong) NSURL *audioUrl; // 音频地址
|
||||
|
||||
@end
|
||||
|
||||
/**
|
||||
音频信息model类(非必传)
|
||||
*/
|
||||
@interface WXYZ_PlayerInfoModel : NSObject
|
||||
|
||||
@property (nonatomic, nullable, copy) NSString *audioLyrics; // 歌词
|
||||
|
||||
/* 正确传入以下属性时,WXYZ_Player将自动设置锁屏模式和控制中心的播放信息展示 */
|
||||
|
||||
@property (nonatomic, nullable, copy) NSString *audioName; // 音频名
|
||||
|
||||
@property (nonatomic, nullable, copy) NSString *audioAlbum; // 专辑名
|
||||
|
||||
@property (nonatomic, nullable, copy) NSString *audioSinger; // 歌手名
|
||||
|
||||
@property (nonatomic, nullable, copy) UIImage *audioImage; // 音频配图
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
//
|
||||
// WXYZ_PlayerModel.m
|
||||
// WXYZ_Player
|
||||
//
|
||||
// Created by ihoudf on 2017/7/18.
|
||||
// Copyright © 2017年 ihoudf. All rights reserved.
|
||||
//
|
||||
|
||||
#import "WXYZ_PlayerModel.h"
|
||||
|
||||
@implementation WXYZ_PlayerModel
|
||||
|
||||
@end
|
||||
|
||||
@implementation WXYZ_PlayerInfoModel
|
||||
|
||||
@end
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
//
|
||||
// LLPageControl.h
|
||||
// iOSHelper
|
||||
//
|
||||
// Created by LL on 2020/5/3.
|
||||
// Copyright © 2020 Chair. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/// 自定义UIPageControl
|
||||
@interface LLPageControl : UIView
|
||||
|
||||
@property (nonatomic, assign) NSUInteger numberOfPages;
|
||||
|
||||
@property (nonatomic, assign) NSUInteger currentPage;
|
||||
|
||||
/// 小圆的半径,决定了圆的大小和控件默认宽度
|
||||
@property (nonatomic, assign) CGFloat radius;
|
||||
|
||||
/// 小圆与小圆之间的间距,决定了控件默认宽度
|
||||
@property (nonatomic, assign) CGFloat spacing;
|
||||
|
||||
@property(nonatomic, strong, nullable) UIColor *pageIndicatorTintColor;
|
||||
|
||||
@property(nonatomic, strong, nullable) UIColor *currentPageIndicatorTintColor;
|
||||
|
||||
/// 点击事件回调
|
||||
@property (nonatomic, copy) void(^clickBlock)(LLPageControl *pageControl, NSUInteger index);
|
||||
|
||||
+ (instancetype)pageControlWithRadius:(CGFloat)radius spacing:(CGFloat)spacing numberOfPages:(NSUInteger)numberOfPages;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,112 @@
|
||||
//
|
||||
// LLPageControl.m
|
||||
// iOSHelper
|
||||
//
|
||||
// Created by LL on 2020/5/3.
|
||||
// Copyright © 2020 Chair. All rights reserved.
|
||||
//
|
||||
|
||||
#import "LLPageControl.h"
|
||||
|
||||
@implementation LLPageControl
|
||||
|
||||
#pragma mark - Public
|
||||
+ (instancetype)pageControlWithRadius:(CGFloat)radius spacing:(CGFloat)spacing numberOfPages:(NSUInteger)numberOfPages {
|
||||
LLPageControl *page = [[self alloc] init];
|
||||
if (page) {
|
||||
page.radius = radius;
|
||||
page.spacing = spacing;
|
||||
page.numberOfPages = numberOfPages;
|
||||
}
|
||||
return page;
|
||||
}
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
if (self = [super initWithFrame:frame]) {
|
||||
[self initialize];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Private
|
||||
- (void)initialize {
|
||||
self.backgroundColor = [UIColor clearColor];
|
||||
}
|
||||
|
||||
#pragma mark - 创建小圆并添加约束
|
||||
- (void)layoutSubviews {
|
||||
[super layoutSubviews];
|
||||
|
||||
for (UIView *view in self.subviews) {
|
||||
[view removeFromSuperview];
|
||||
}
|
||||
|
||||
// 添加指定数量的小圆
|
||||
NSMutableArray<UIView *> *array = [NSMutableArray array];
|
||||
for (NSInteger i = 0; i < self.numberOfPages; i++) {
|
||||
UIView *view = [[UIView alloc] init];
|
||||
view.tag = 10000 + i;
|
||||
if (i == self.currentPage) {
|
||||
view.backgroundColor = self.currentPageIndicatorTintColor;
|
||||
} else {
|
||||
view.backgroundColor = self.pageIndicatorTintColor;
|
||||
}
|
||||
view.layer.cornerRadius = self.radius;
|
||||
view.layer.masksToBounds = YES;
|
||||
view.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
[view addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(click:)]];
|
||||
[self addSubview:view];
|
||||
[array addObject:view];
|
||||
}
|
||||
|
||||
// 添加约束
|
||||
UIView *_view;
|
||||
for (UIView *view in array) {
|
||||
if (!_view) {
|
||||
[self addConstraint:[NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeLeft multiplier:1.0f constant:0.0f]];
|
||||
} else {
|
||||
[self addConstraint:[NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:_view attribute:NSLayoutAttributeRight multiplier:1.0f constant:self.spacing]];
|
||||
}
|
||||
_view = view;
|
||||
[self addConstraint:[NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeCenterY multiplier:1.0f constant:0.0f]];
|
||||
[self addConstraint:[NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeHeight multiplier:0.0f constant:self.radius * 2.0]];
|
||||
[self addConstraint:[NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeWidth multiplier:0.0f constant:self.radius * 2.0]];
|
||||
}
|
||||
}
|
||||
|
||||
- (CGSize)intrinsicContentSize {
|
||||
CGFloat width = self.numberOfPages * (self.radius * 2.0) + (self.numberOfPages - 1) * self.spacing;
|
||||
return CGSizeMake(width, self.radius * 2.0);
|
||||
}
|
||||
|
||||
- (void)click:(UIView *)view {
|
||||
!self.clickBlock ?: self.clickBlock(self, view.tag - 10000);
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Setter
|
||||
- (void)setCurrentPage:(NSUInteger)currentPage {
|
||||
UIView *view = [self viewWithTag:10000 + _currentPage];
|
||||
view.backgroundColor = self.pageIndicatorTintColor;
|
||||
_currentPage = currentPage;
|
||||
view = [self viewWithTag:10000 + _currentPage];
|
||||
view.backgroundColor = self.currentPageIndicatorTintColor;
|
||||
}
|
||||
|
||||
- (void)setNumberOfPages:(NSUInteger)numberOfPages {
|
||||
_numberOfPages = numberOfPages;
|
||||
[self setNeedsLayout];
|
||||
[self layoutIfNeeded];
|
||||
}
|
||||
|
||||
#pragma mark Getter
|
||||
- (UIColor *)pageIndicatorTintColor {
|
||||
return _pageIndicatorTintColor = _pageIndicatorTintColor ?: [UIColor whiteColor];
|
||||
}
|
||||
|
||||
- (UIColor *)currentPageIndicatorTintColor {
|
||||
return _currentPageIndicatorTintColor = _currentPageIndicatorTintColor ?: [UIColor orangeColor];
|
||||
}
|
||||
|
||||
@end
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
//
|
||||
// YJAbstractDotView.h
|
||||
// YJBannerViewDemo
|
||||
//
|
||||
// Created by YJHou on 2015/5/24.
|
||||
// Copyright © Address:https://github.com/stackhou . All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface YJAbstractDotView : UIView
|
||||
|
||||
- (void)changeActivityState:(BOOL)active;
|
||||
|
||||
@end
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
//
|
||||
// YJAbstractDotView.m
|
||||
// YJBannerViewDemo
|
||||
//
|
||||
// Created by YJHou on 2015/5/24.
|
||||
// Copyright © 2015年 Address:https://github.com/stackhou . All rights reserved.
|
||||
//
|
||||
|
||||
#import "YJAbstractDotView.h"
|
||||
|
||||
@implementation YJAbstractDotView
|
||||
|
||||
- (id)init{
|
||||
@throw [NSException exceptionWithName:NSInternalInconsistencyException
|
||||
reason:[NSString stringWithFormat:@"You must override %@ in %@", NSStringFromSelector(_cmd), self.class]
|
||||
userInfo:nil];
|
||||
}
|
||||
|
||||
|
||||
- (void)changeActivityState:(BOOL)active{
|
||||
@throw [NSException exceptionWithName:NSInternalInconsistencyException
|
||||
reason:[NSString stringWithFormat:@"You must override %@ in %@", NSStringFromSelector(_cmd), self.class]
|
||||
userInfo:nil];
|
||||
}
|
||||
|
||||
@end
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
//
|
||||
// YJAnimatedDotView.h
|
||||
// YJBannerViewDemo
|
||||
//
|
||||
// Created by YJHou on 2015/5/24.
|
||||
// Copyright © 2015年 Address:https://github.com/stackhou . All rights reserved.
|
||||
//
|
||||
|
||||
#import "YJAbstractDotView.h"
|
||||
|
||||
@interface YJAnimatedDotView : YJAbstractDotView
|
||||
|
||||
@property (nonatomic, strong) UIColor *dotColor;
|
||||
@property (nonatomic, strong) UIColor *currentDotColor;
|
||||
@property (nonatomic, assign) CGFloat resizeScale; /**< 调整比例 */
|
||||
|
||||
@end
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
//
|
||||
// YJAnimatedDotView.m
|
||||
// YJBannerViewDemo
|
||||
//
|
||||
// Created by YJHou on 2015/5/24.
|
||||
// Copyright © 2015年 Address:https://github.com/stackhou . All rights reserved.
|
||||
//
|
||||
|
||||
#import "YJAnimatedDotView.h"
|
||||
|
||||
static CGFloat const kAnimateDuration = 0;
|
||||
|
||||
@implementation YJAnimatedDotView
|
||||
|
||||
- (instancetype)init{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
[self initialization];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id)initWithFrame:(CGRect)frame{
|
||||
self = [super initWithFrame:frame];
|
||||
if (self) {
|
||||
[self initialization];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id)initWithCoder:(NSCoder *)aDecoder{
|
||||
self = [super initWithCoder:aDecoder];
|
||||
if (self) {
|
||||
[self initialization];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setDotColor:(UIColor *)dotColor{
|
||||
_dotColor = dotColor;
|
||||
self.layer.borderColor = dotColor.CGColor;
|
||||
}
|
||||
|
||||
- (void)setCurrentDotColor:(UIColor *)currentDotColor{
|
||||
_currentDotColor = currentDotColor;
|
||||
self.backgroundColor = [UIColor clearColor];
|
||||
}
|
||||
|
||||
- (void)initialization{
|
||||
_dotColor = [UIColor whiteColor];
|
||||
_currentDotColor = [UIColor whiteColor];
|
||||
self.backgroundColor = [UIColor clearColor];
|
||||
self.layer.cornerRadius = CGRectGetWidth(self.frame) * 0.5;
|
||||
self.layer.borderColor = [UIColor whiteColor].CGColor;
|
||||
self.layer.borderWidth = 1.5;
|
||||
self.resizeScale = 1.4f;
|
||||
}
|
||||
|
||||
- (void)changeActivityState:(BOOL)active{
|
||||
if (active) {
|
||||
[self animateToActiveState];
|
||||
} else {
|
||||
[self animateToDeactiveState];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)animateToActiveState{
|
||||
[UIView animateWithDuration:kAnimateDuration delay:0 usingSpringWithDamping:.5 initialSpringVelocity:-20 options:UIViewAnimationOptionCurveLinear animations:^{
|
||||
self.backgroundColor = self.currentDotColor;
|
||||
self.layer.borderColor = [UIColor clearColor].CGColor;
|
||||
self.transform = CGAffineTransformMakeScale(self.resizeScale, self.resizeScale);
|
||||
} completion:nil];
|
||||
}
|
||||
|
||||
- (void)animateToDeactiveState{
|
||||
[UIView animateWithDuration:kAnimateDuration delay:0 usingSpringWithDamping:.5 initialSpringVelocity:0 options:UIViewAnimationOptionCurveLinear animations:^{
|
||||
self.backgroundColor = [UIColor clearColor];
|
||||
self.layer.borderColor = self.dotColor.CGColor;
|
||||
self.transform = CGAffineTransformIdentity;
|
||||
} completion:nil];
|
||||
}
|
||||
|
||||
@end
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
//
|
||||
// YJHollowPageControl.h
|
||||
// YJBannerViewDemo
|
||||
//
|
||||
// Created by YJHou on 2015/5/24.
|
||||
// Copyright © 2015年 Address:https://github.com/stackhou . All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "YJAbstractDotView.h"
|
||||
|
||||
@class YJHollowPageControl;
|
||||
@protocol YJHollowPageControlDelegate <NSObject>
|
||||
|
||||
@optional
|
||||
- (void)yjHollowPageControl:(YJHollowPageControl *)pageControl didSelectPageAtIndex:(NSInteger)index;
|
||||
|
||||
@end
|
||||
|
||||
@interface YJHollowPageControl : UIControl
|
||||
|
||||
@property (nonatomic, assign) CGSize dotSize; /**< 圆点大小 默认8*8 */
|
||||
@property (nonatomic, strong) UIImage *dotNormalImage; /**< 普通样式 */
|
||||
@property (nonatomic, strong) UIImage *dotCurrentImage; /**< 选中样式 */
|
||||
|
||||
@property (nonatomic, strong) UIColor *dotNormalColor; /**< 点色 */
|
||||
@property (nonatomic, strong) UIColor *dotCurrentColor; /**< 当前圆点的颜色 */
|
||||
|
||||
@property (nonatomic, strong) Class dotViewClass; /**< 圆点类 */
|
||||
@property (nonatomic, weak) id<YJHollowPageControlDelegate> delegate; /**< 代理 */
|
||||
@property (nonatomic, assign) CGFloat spacing; /**< 间距 默认 8 */
|
||||
@property (nonatomic, assign) NSInteger numberOfPages; /**< 数量 */
|
||||
@property (nonatomic, assign) NSInteger currentPage; /**< 当前位置 */
|
||||
@property (nonatomic, assign) BOOL hidesForSinglePage; /**< 单个不显示 默认NO*/
|
||||
@property (nonatomic, assign) BOOL shouldResizeFromCenter; /**< 是否调整大小 */
|
||||
@property (nonatomic, assign) CGFloat resizeScale; /**< 调整比例 */
|
||||
|
||||
- (CGSize)sizeForNumberOfPages:(NSInteger)pageCount;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@interface YJDotView : YJAbstractDotView
|
||||
|
||||
@end
|
||||
+322
@@ -0,0 +1,322 @@
|
||||
//
|
||||
// YJHollowPageControl.m
|
||||
// YJBannerViewDemo
|
||||
//
|
||||
// Created by YJHou on 2015/5/24.
|
||||
// Copyright © 2015年 Address:https://github.com/stackhou . All rights reserved.
|
||||
//
|
||||
|
||||
#import "YJHollowPageControl.h"
|
||||
#import "YJAnimatedDotView.h"
|
||||
|
||||
static CGSize const kDefaultDotSize = {8, 8};
|
||||
static NSInteger const kDefaultNumberOfPages = 0;
|
||||
static NSInteger const kDefaultCurrentPage = 0;
|
||||
static BOOL const kDefaultHideForSinglePage = NO;
|
||||
static BOOL const kDefaultShouldResizeFromCenter = YES;
|
||||
static NSInteger const kDefaultSpacingBetweenDots = 8;
|
||||
|
||||
@interface YJHollowPageControl ()
|
||||
|
||||
@property (nonatomic, strong) NSMutableArray *dots; /**< 保存所有的点 */
|
||||
|
||||
@end
|
||||
|
||||
@implementation YJHollowPageControl
|
||||
|
||||
- (id)init{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
[self _initializationSetting];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id)initWithFrame:(CGRect)frame{
|
||||
self = [super initWithFrame:frame];
|
||||
if (self) {
|
||||
[self _initializationSetting];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id)initWithCoder:(NSCoder *)aDecoder{
|
||||
self = [super initWithCoder:aDecoder];
|
||||
if (self) {
|
||||
[self _initializationSetting];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)_initializationSetting{
|
||||
self.dotViewClass = [YJAnimatedDotView class];
|
||||
self.spacing = kDefaultSpacingBetweenDots;
|
||||
self.numberOfPages = kDefaultNumberOfPages;
|
||||
self.currentPage = kDefaultCurrentPage;
|
||||
self.hidesForSinglePage = kDefaultHideForSinglePage;
|
||||
self.shouldResizeFromCenter = kDefaultShouldResizeFromCenter;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Touch event
|
||||
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
|
||||
UITouch *touch = [touches anyObject];
|
||||
if (touch.view != self) {
|
||||
NSInteger index = [self.dots indexOfObject:touch.view];
|
||||
if ([self.delegate respondsToSelector:@selector(yjHollowPageControl:didSelectPageAtIndex:)]) {
|
||||
[self.delegate yjHollowPageControl:self didSelectPageAtIndex:index];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)sizeToFit{
|
||||
[self updateFrame:YES];
|
||||
}
|
||||
|
||||
- (CGSize)sizeForNumberOfPages:(NSInteger)pageCount{
|
||||
return CGSizeMake((self.dotSize.width + self.spacing) * pageCount - self.spacing , self.dotSize.height);
|
||||
}
|
||||
|
||||
|
||||
- (void)updateDots{
|
||||
if (self.numberOfPages == 0) {return;}
|
||||
|
||||
for (NSInteger i = 0; i < self.numberOfPages; i++) {
|
||||
|
||||
UIView *dot;
|
||||
if (i < self.dots.count) {
|
||||
dot = [self.dots objectOrNilAtIndex:i];
|
||||
} else {
|
||||
dot = [self generateDotView];
|
||||
}
|
||||
[self updateDotFrame:dot atIndex:i];
|
||||
}
|
||||
[self changeActivity:YES atIndex:self.currentPage];
|
||||
|
||||
[self hideForSinglePage];
|
||||
}
|
||||
|
||||
/**
|
||||
Update frame to fit current number of pages.
|
||||
|
||||
@param newFrame override Existing Frame
|
||||
*/
|
||||
- (void)updateFrame:(BOOL)newFrame{
|
||||
CGPoint center = self.center;
|
||||
CGSize requiredSize = [self sizeForNumberOfPages:self.numberOfPages];
|
||||
|
||||
// We apply requiredSize only if authorize to and necessary
|
||||
if (newFrame || ((CGRectGetWidth(self.frame) < requiredSize.width || CGRectGetHeight(self.frame) < requiredSize.height) && !newFrame)) {
|
||||
self.frame = CGRectMake(CGRectGetMinX(self.frame), CGRectGetMinY(self.frame), requiredSize.width, requiredSize.height);
|
||||
if (self.shouldResizeFromCenter) {
|
||||
self.center = center;
|
||||
}
|
||||
}
|
||||
[self resetDotViews];
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the frame of a specific dot at a specific index
|
||||
*
|
||||
* @param dot Dot view
|
||||
* @param index Page index of dot
|
||||
*/
|
||||
- (void)updateDotFrame:(UIView *)dot atIndex:(NSInteger)index{
|
||||
// Dots are always centered within view
|
||||
CGFloat x = (self.dotSize.width + self.spacing) * index + ( (CGRectGetWidth(self.frame) - [self sizeForNumberOfPages:self.numberOfPages].width) / 2);
|
||||
CGFloat y = (CGRectGetHeight(self.frame) - self.dotSize.height) / 2;
|
||||
|
||||
dot.frame = CGRectMake(x, y, self.dotSize.width, self.dotSize.height);
|
||||
}
|
||||
|
||||
- (void)setDotCurrentColor:(UIColor *)currentDotColor{
|
||||
_dotCurrentColor = currentDotColor;
|
||||
}
|
||||
|
||||
- (void)setDotNormalColor:(UIColor *)dotColor{
|
||||
_dotNormalColor = dotColor;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Utils
|
||||
/**
|
||||
* Generate a dot view and add it to the collection
|
||||
*
|
||||
* @return The UIView object representing a dot
|
||||
*/
|
||||
- (UIView *)generateDotView{
|
||||
UIView *dotView;
|
||||
|
||||
if (self.dotViewClass) {
|
||||
dotView = [[self.dotViewClass alloc] initWithFrame:CGRectMake(0, 0, self.dotSize.width, self.dotSize.height)];
|
||||
if ([dotView isKindOfClass:[YJAnimatedDotView class]]) {
|
||||
if (self.resizeScale > 0) {
|
||||
((YJAnimatedDotView *)dotView).resizeScale = self.resizeScale;
|
||||
}
|
||||
if (self.dotNormalColor) {
|
||||
((YJAnimatedDotView *)dotView).dotColor = self.dotNormalColor;
|
||||
}
|
||||
if (self.dotCurrentColor){
|
||||
((YJAnimatedDotView *)dotView).currentDotColor = self.dotCurrentColor;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
dotView = [[UIImageView alloc] initWithImage:self.dotNormalImage];
|
||||
dotView.contentMode = UIViewContentModeScaleAspectFit;
|
||||
dotView.frame = CGRectMake(0, 0, self.dotSize.width, self.dotSize.height);
|
||||
}
|
||||
|
||||
if (dotView) {
|
||||
[self addSubview:dotView];
|
||||
[self.dots addObject:dotView];
|
||||
}
|
||||
|
||||
dotView.userInteractionEnabled = YES;
|
||||
return dotView;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Change activity state of a dot view. Current/not currrent.
|
||||
*
|
||||
* @param active Active state to apply
|
||||
* @param index Index of dot for state update
|
||||
*/
|
||||
- (void)changeActivity:(BOOL)active atIndex:(NSInteger)index{
|
||||
if (self.dotViewClass) {
|
||||
YJAbstractDotView *abstractDotView = (YJAbstractDotView *)[self.dots objectOrNilAtIndex:index];
|
||||
if ([abstractDotView respondsToSelector:@selector(changeActivityState:)]) {
|
||||
[abstractDotView changeActivityState:active];
|
||||
} else {
|
||||
|
||||
}
|
||||
} else if (self.dotNormalImage && self.dotCurrentImage) {
|
||||
UIImageView *dotView = (UIImageView *)[self.dots objectOrNilAtIndex:index];
|
||||
dotView.image = (active) ? self.dotCurrentImage : self.dotNormalImage;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)resetDotViews{
|
||||
for (UIView *dotView in self.dots) {
|
||||
[dotView removeFromSuperview];
|
||||
}
|
||||
[self.dots removeAllObjects];
|
||||
[self updateDots];
|
||||
}
|
||||
|
||||
- (void)hideForSinglePage{
|
||||
if (self.dots.count == 1 && self.hidesForSinglePage) {
|
||||
self.hidden = YES;
|
||||
} else {
|
||||
self.hidden = NO;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Setters
|
||||
- (void)setNumberOfPages:(NSInteger)numberOfPages{
|
||||
_numberOfPages = numberOfPages;
|
||||
// Update dot position to fit new number of pages
|
||||
[self resetDotViews];
|
||||
}
|
||||
|
||||
- (void)setSpacing:(CGFloat)spacing{
|
||||
_spacing = spacing;
|
||||
[self resetDotViews];
|
||||
}
|
||||
|
||||
- (void)setCurrentPage:(NSInteger)currentPage{
|
||||
// If no pages, no current page to treat.
|
||||
if (self.numberOfPages == 0 || currentPage == _currentPage) {
|
||||
_currentPage = currentPage;
|
||||
return;
|
||||
}
|
||||
|
||||
// Pre set
|
||||
[self changeActivity:NO atIndex:_currentPage];
|
||||
_currentPage = currentPage;
|
||||
// Post set
|
||||
[self changeActivity:YES atIndex:_currentPage];
|
||||
}
|
||||
|
||||
- (void)setDotNormalImage:(UIImage *)dotImage{
|
||||
_dotNormalImage = dotImage;
|
||||
[self resetDotViews];
|
||||
self.dotViewClass = nil;
|
||||
}
|
||||
|
||||
- (void)setDotCurrentImage:(UIImage *)currentDotimage{
|
||||
_dotCurrentImage = currentDotimage;
|
||||
[self resetDotViews];
|
||||
self.dotViewClass = nil;
|
||||
}
|
||||
|
||||
- (void)setDotViewClass:(Class)dotViewClass{
|
||||
_dotViewClass = dotViewClass;
|
||||
self.dotSize = CGSizeZero;
|
||||
[self resetDotViews];
|
||||
}
|
||||
|
||||
#pragma mark - Getter
|
||||
- (CGSize)dotSize{
|
||||
if (self.dotNormalImage && CGSizeEqualToSize(_dotSize, CGSizeZero)) {
|
||||
_dotSize = self.dotNormalImage.size;
|
||||
} else if (self.dotViewClass && CGSizeEqualToSize(_dotSize, CGSizeZero)) {
|
||||
_dotSize = kDefaultDotSize;
|
||||
return _dotSize;
|
||||
}
|
||||
return _dotSize;
|
||||
}
|
||||
|
||||
#pragma mark - Lazy
|
||||
- (NSMutableArray *)dots{
|
||||
if (!_dots) {
|
||||
_dots = [NSMutableArray array];
|
||||
}
|
||||
return _dots;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation YJDotView
|
||||
|
||||
- (instancetype)init{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
[self _initializationSetting];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id)initWithFrame:(CGRect)frame{
|
||||
self = [super initWithFrame:frame];
|
||||
if (self) {
|
||||
[self _initializationSetting];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id)initWithCoder:(NSCoder *)aDecoder{
|
||||
self = [super initWithCoder:aDecoder];
|
||||
if (self) {
|
||||
[self _initializationSetting];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)_initializationSetting{
|
||||
self.backgroundColor = [UIColor clearColor];
|
||||
self.layer.cornerRadius = CGRectGetWidth(self.frame) * 0.5;
|
||||
self.layer.borderColor = [UIColor whiteColor].CGColor;
|
||||
self.layer.borderWidth = 2;
|
||||
}
|
||||
|
||||
- (void)changeActivityState:(BOOL)active{
|
||||
if (active) {
|
||||
self.backgroundColor = [UIColor whiteColor];
|
||||
} else {
|
||||
self.backgroundColor = [UIColor clearColor];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 133 B |
Binary file not shown.
|
After Width: | Height: | Size: 133 B |
Binary file not shown.
|
After Width: | Height: | Size: 190 B |
Binary file not shown.
|
After Width: | Height: | Size: 191 B |
+19
@@ -0,0 +1,19 @@
|
||||
//
|
||||
// UIView+YJBannerViewExt.h
|
||||
// YJBannerViewDemo
|
||||
//
|
||||
// Created by YJHou on 2015/5/24.
|
||||
// Copyright © 2015年 Address:https://github.com/stackhou . All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface UIView (YJBannerViewExt)
|
||||
|
||||
@property (nonatomic, assign) CGFloat x_bannerView;
|
||||
@property (nonatomic, assign) CGFloat y_bannerView;
|
||||
|
||||
@property (nonatomic, assign) CGFloat width_bannerView;
|
||||
@property (nonatomic, assign) CGFloat height_bannerView;
|
||||
|
||||
@end
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
//
|
||||
// UIView+YJBannerViewExt.m
|
||||
// YJBannerViewDemo
|
||||
//
|
||||
// Created by YJHou on 2015/5/24.
|
||||
// Copyright © 2015年 Address:https://github.com/stackhou . All rights reserved.
|
||||
//
|
||||
|
||||
#import "UIView+YJBannerViewExt.h"
|
||||
|
||||
@implementation UIView (YJBannerViewExt)
|
||||
|
||||
- (void)setX_bannerView:(CGFloat)x_bannerView{
|
||||
CGRect temp = self.frame;
|
||||
temp.origin.x = x_bannerView;
|
||||
self.frame = temp;
|
||||
}
|
||||
|
||||
- (CGFloat)x_bannerView{
|
||||
return self.frame.origin.x;
|
||||
}
|
||||
|
||||
- (void)setY_bannerView:(CGFloat)y_bannerView{
|
||||
CGRect temp = self.frame;
|
||||
temp.origin.y = y_bannerView;
|
||||
self.frame = temp;
|
||||
}
|
||||
|
||||
- (CGFloat)y_bannerView{
|
||||
return self.frame.origin.y;
|
||||
}
|
||||
|
||||
- (void)setWidth_bannerView:(CGFloat)width_bannerView{
|
||||
CGRect temp = self.frame;
|
||||
temp.size.width = width_bannerView;
|
||||
self.frame = temp;
|
||||
}
|
||||
|
||||
- (CGFloat)width_bannerView{
|
||||
return self.frame.size.width;
|
||||
}
|
||||
|
||||
- (void)setHeight_bannerView:(CGFloat)height_bannerView{
|
||||
CGRect temp = self.frame;
|
||||
temp.size.height = height_bannerView;
|
||||
self.frame = temp;
|
||||
}
|
||||
|
||||
- (CGFloat)height_bannerView{
|
||||
return self.frame.size.height;
|
||||
}
|
||||
|
||||
@end
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
//
|
||||
// YJBannerViewCell.h
|
||||
// YJBannerViewDemo
|
||||
//
|
||||
// Created by YJHou on 2015/5/24.
|
||||
// Copyright © 2015年 Address:https://github.com/stackhou . All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface YJBannerViewCell : UICollectionViewCell
|
||||
|
||||
@property (nonatomic, strong) UIColor *titleLabelTextColor;
|
||||
@property (nonatomic, strong) UIFont *titleLabelTextFont;
|
||||
@property (nonatomic, strong) UIColor *titleLabelBackgroundColor;
|
||||
@property (nonatomic, assign) CGFloat titleLabelHeight;
|
||||
@property (nonatomic, assign) CGFloat titleLabelEdgeMargin;
|
||||
@property (nonatomic, assign) NSTextAlignment titleLabelTextAlignment;
|
||||
@property (nonatomic, assign) UIViewContentMode showImageViewContentMode; /**< 填充样式 默认UIViewContentModeScaleToFill */
|
||||
@property (nonatomic, assign) BOOL isConfigured;
|
||||
|
||||
- (void)cellWithSelectorString:(NSString *)selectorString imagePath:(NSString *)imagePath placeholderImage:(UIImage *)placeholderImage title:(NSString *)title;
|
||||
|
||||
@end
|
||||
+149
@@ -0,0 +1,149 @@
|
||||
//
|
||||
// YJBannerViewCell.m
|
||||
// YJBannerViewDemo
|
||||
//
|
||||
// Created by YJHou on 2015/5/24.
|
||||
// Copyright © 2015年 Address:https://github.com/stackhou . All rights reserved.
|
||||
//
|
||||
|
||||
#import "YJBannerViewCell.h"
|
||||
#import "UIView+YJBannerViewExt.h"
|
||||
|
||||
@interface YJBannerViewCell ()
|
||||
|
||||
@property (nonatomic, strong) UIImageView *showImageView; /**< 显示图片 */
|
||||
@property (nonatomic, strong) UILabel *titleLabel; /**< 标题头 */
|
||||
@property (nonatomic, strong) UIView *titleLabelBgView; /**< 标题背景 */
|
||||
|
||||
@end
|
||||
|
||||
@implementation YJBannerViewCell
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame{
|
||||
if (self = [super initWithFrame:frame]) {
|
||||
[self _setUpMainView];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)_setUpMainView{
|
||||
[self.contentView addSubview:self.showImageView];
|
||||
[self.contentView addSubview:self.titleLabelBgView];
|
||||
[self.contentView addSubview:self.titleLabel];
|
||||
}
|
||||
|
||||
#pragma mark - Setter && Getter
|
||||
- (void)setTitleLabelBackgroundColor:(UIColor *)titleLabelBackgroundColor{
|
||||
_titleLabelBackgroundColor = titleLabelBackgroundColor;
|
||||
self.titleLabelBgView.backgroundColor = titleLabelBackgroundColor;
|
||||
}
|
||||
|
||||
- (void)setTitleLabelTextColor:(UIColor *)titleLabelTextColor{
|
||||
_titleLabelTextColor = titleLabelTextColor;
|
||||
self.titleLabel.textColor = titleLabelTextColor;
|
||||
}
|
||||
|
||||
- (void)setTitleLabelTextFont:(UIFont *)titleLabelTextFont{
|
||||
_titleLabelTextFont = titleLabelTextFont;
|
||||
self.titleLabel.font = titleLabelTextFont;
|
||||
}
|
||||
|
||||
-(void)setTitleLabelTextAlignment:(NSTextAlignment)titleLabelTextAlignment{
|
||||
_titleLabelTextAlignment = titleLabelTextAlignment;
|
||||
self.titleLabel.textAlignment = titleLabelTextAlignment;
|
||||
}
|
||||
|
||||
- (void)setShowImageViewContentMode:(UIViewContentMode)showImageViewContentMode{
|
||||
_showImageViewContentMode = showImageViewContentMode;
|
||||
self.showImageView.contentMode = showImageViewContentMode;
|
||||
}
|
||||
|
||||
- (void)layoutSubviews{
|
||||
[super layoutSubviews];
|
||||
|
||||
CGFloat titleBgViewlH = self.titleLabelHeight;
|
||||
self.showImageView.frame = self.bounds;
|
||||
|
||||
CGFloat titlBgViewX = 0.0f;
|
||||
CGFloat titleBgViewY = self.height_bannerView - titleBgViewlH;
|
||||
CGFloat titleBgViewW = self.width_bannerView - 2 * titlBgViewX;
|
||||
_titleLabelBgView.frame = CGRectMake(titlBgViewX, titleBgViewY, titleBgViewW, titleBgViewlH);
|
||||
|
||||
CGFloat titleLabelH = titleBgViewlH;
|
||||
CGFloat titleLabelX = self.titleLabelEdgeMargin;
|
||||
CGFloat titleLabelY = titleBgViewY;
|
||||
CGFloat titleLabelW = self.width_bannerView - 2 * titleLabelX;
|
||||
_titleLabel.frame = CGRectMake(titleLabelX, titleLabelY, titleLabelW, titleLabelH);
|
||||
}
|
||||
|
||||
#pragma mark - 刷新数据
|
||||
- (void)cellWithSelectorString:(NSString *)selectorString imagePath:(NSString *)imagePath placeholderImage:(UIImage *)placeholderImage title:(NSString *)title{
|
||||
|
||||
if (imagePath) {
|
||||
self.showImageView.hidden = NO;
|
||||
if ([imagePath isKindOfClass:[NSString class]]) {
|
||||
if ([imagePath hasPrefix:@"http"]) {
|
||||
|
||||
// 检验方法是否可用
|
||||
SEL selector = NSSelectorFromString(selectorString);
|
||||
if ([self.showImageView respondsToSelector:selector]) {
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
|
||||
[self.showImageView performSelector:selector withObject:[NSURL URLWithString:imagePath] withObject:placeholderImage];
|
||||
#pragma clang diagnostic pop
|
||||
}
|
||||
} else {
|
||||
if (imagePath.length > 0) {
|
||||
UIImage *image = [UIImage imageNamed:imagePath];
|
||||
if (!image) {
|
||||
image = [UIImage imageWithContentsOfFile:imagePath];
|
||||
}
|
||||
self.showImageView.image = image;
|
||||
}
|
||||
}
|
||||
} else if ([imagePath isKindOfClass:[UIImage class]]) {
|
||||
self.showImageView.image = (UIImage *)imagePath;
|
||||
}else{
|
||||
self.showImageView.image = placeholderImage;
|
||||
}
|
||||
}else{
|
||||
self.showImageView.hidden = YES;
|
||||
}
|
||||
|
||||
if (title.length > 0) {
|
||||
self.titleLabel.text = title;
|
||||
self.titleLabel.hidden = NO;
|
||||
self.titleLabelBgView.hidden = NO;
|
||||
}else{
|
||||
self.titleLabel.hidden = YES;
|
||||
self.titleLabelBgView.hidden = YES;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Lazy
|
||||
- (UIImageView *)showImageView{
|
||||
if (!_showImageView) {
|
||||
_showImageView = [[UIImageView alloc] init];
|
||||
}
|
||||
return _showImageView;
|
||||
}
|
||||
|
||||
- (UILabel *)titleLabel{
|
||||
if (!_titleLabel) {
|
||||
_titleLabel = [[UILabel alloc] init];
|
||||
_titleLabel.hidden = YES;
|
||||
_titleLabel.backgroundColor = [UIColor clearColor];
|
||||
}
|
||||
return _titleLabel;
|
||||
}
|
||||
|
||||
- (UIView *)titleLabelBgView{
|
||||
if (!_titleLabelBgView) {
|
||||
_titleLabelBgView = [[UIView alloc] init];
|
||||
_titleLabelBgView.hidden = YES;
|
||||
}
|
||||
return _titleLabelBgView;
|
||||
}
|
||||
|
||||
|
||||
@end
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
//
|
||||
// YJBannerViewCollectionView.h
|
||||
// YJBannerViewDemo
|
||||
//
|
||||
// Created by YJHou on 2017/11/21.
|
||||
// Copyright © 2017年 Address:https://github.com/stackhou . All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface YJBannerViewCollectionView : UICollectionView <UIGestureRecognizerDelegate>
|
||||
|
||||
@end
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
//
|
||||
// YJBannerViewCollectionView.m
|
||||
// YJBannerViewDemo
|
||||
//
|
||||
// Created by YJHou on 2017/11/21.
|
||||
// Copyright © 2017年 Address:https://github.com/stackhou . All rights reserved.
|
||||
//
|
||||
|
||||
#import "YJBannerViewCollectionView.h"
|
||||
|
||||
@implementation YJBannerViewCollectionView
|
||||
|
||||
@end
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
//
|
||||
// YJBannerViewFooter.h
|
||||
// YJBannerViewDemo
|
||||
//
|
||||
// Created by YJHou on 2015/5/24.
|
||||
// Copyright © 2015年 Address:https://github.com/stackhou . All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
typedef NS_ENUM(NSInteger, YJBannerViewStatus) {
|
||||
YJBannerViewStatusIdle = 0, // 闲置
|
||||
YJBannerViewStatusTrigger // 触发
|
||||
};
|
||||
|
||||
@interface YJBannerViewFooter : UICollectionReusableView
|
||||
|
||||
@property (nonatomic, assign) YJBannerViewStatus state;
|
||||
|
||||
@property (nonatomic, strong) UIFont *footerTitleFont;
|
||||
@property (nonatomic, strong) UIColor *footerTitleColor;
|
||||
@property (nonatomic, copy) NSString *IndicateImageName; /**< 指示图片的名字 */
|
||||
@property (nonatomic, copy) NSString *idleTitle; /**< 闲置 */
|
||||
@property (nonatomic, copy) NSString *triggerTitle; /**< 触发 */
|
||||
|
||||
@end
|
||||
+125
@@ -0,0 +1,125 @@
|
||||
//
|
||||
// YJBannerViewFooter.m
|
||||
// YJBannerViewDemo
|
||||
//
|
||||
// Created by YJHou on 2015/5/24.
|
||||
// Copyright © 2015年 Address:https://github.com/stackhou . All rights reserved.
|
||||
//
|
||||
|
||||
#import "YJBannerViewFooter.h"
|
||||
|
||||
#define YJ_ARROW_SIZE 15.0f
|
||||
|
||||
@interface YJBannerViewFooter ()
|
||||
|
||||
@property (nonatomic, strong) UIImageView *arrowView;
|
||||
@property (nonatomic, strong) UILabel *label;
|
||||
|
||||
@end
|
||||
|
||||
@implementation YJBannerViewFooter
|
||||
|
||||
@synthesize idleTitle = _idleTitle;
|
||||
@synthesize triggerTitle = _triggerTitle;
|
||||
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame{
|
||||
self = [super initWithFrame:frame];
|
||||
if (self) {
|
||||
[self addSubview:self.arrowView];
|
||||
[self addSubview:self.label];
|
||||
|
||||
self.state = YJBannerViewStatusIdle;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)layoutSubviews{
|
||||
[super layoutSubviews];
|
||||
|
||||
CGFloat arrowX = self.bounds.size.width * 0.5 - YJ_ARROW_SIZE - 2.5;
|
||||
CGFloat arrowY = (self.bounds.size.height - YJ_ARROW_SIZE) * 0.5;
|
||||
CGFloat arrowW = YJ_ARROW_SIZE;
|
||||
CGFloat arrowH = YJ_ARROW_SIZE;
|
||||
self.arrowView.frame = CGRectMake(arrowX, arrowY, arrowW, arrowH);
|
||||
|
||||
CGFloat labelX = self.bounds.size.width * 0.5 + 2.5;
|
||||
CGFloat labelY = 0;
|
||||
CGFloat labelW = YJ_ARROW_SIZE;
|
||||
CGFloat labelH = self.bounds.size.height;
|
||||
self.label.frame = CGRectMake(labelX, labelY, labelW, labelH);
|
||||
}
|
||||
|
||||
#pragma mark - setters & getters
|
||||
- (void)setState:(YJBannerViewStatus)state{
|
||||
_state = state;
|
||||
|
||||
switch (state) {
|
||||
case YJBannerViewStatusIdle:{
|
||||
self.label.text = self.idleTitle;
|
||||
[UIView animateWithDuration:0.25 animations:^{
|
||||
self.arrowView.transform = CGAffineTransformMakeRotation(0);
|
||||
}];
|
||||
}
|
||||
break;
|
||||
case YJBannerViewStatusTrigger:{
|
||||
self.label.text = self.triggerTitle;
|
||||
[UIView animateWithDuration:0.25 animations:^{
|
||||
self.arrowView.transform = CGAffineTransformMakeRotation(M_PI);
|
||||
}];
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Setter
|
||||
- (void)setFooterTitleFont:(UIFont *)footerTitleFont{
|
||||
_footerTitleFont = footerTitleFont;
|
||||
self.label.font = _footerTitleFont;
|
||||
}
|
||||
|
||||
- (void)setFooterTitleColor:(UIColor *)footerTitleColor{
|
||||
_footerTitleColor = footerTitleColor;
|
||||
self.label.textColor = _footerTitleColor;
|
||||
}
|
||||
|
||||
- (void)setIndicateImageName:(NSString *)IndicateImageName{
|
||||
_IndicateImageName = IndicateImageName;
|
||||
if (_IndicateImageName.length > 0 && self.arrowView.image == nil) {
|
||||
self.arrowView.image = [UIImage imageNamed:_IndicateImageName];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Lazy
|
||||
- (UIImageView *)arrowView{
|
||||
if (!_arrowView) {
|
||||
_arrowView = [[UIImageView alloc] init];
|
||||
}
|
||||
return _arrowView;
|
||||
}
|
||||
|
||||
- (UILabel *)label{
|
||||
if (!_label) {
|
||||
_label = [[UILabel alloc] init];
|
||||
_label.numberOfLines = 0;
|
||||
}
|
||||
return _label;
|
||||
}
|
||||
|
||||
- (void)setIdleTitle:(NSString *)idleTitle{
|
||||
_idleTitle = idleTitle;
|
||||
if (self.state == YJBannerViewStatusIdle) {
|
||||
self.label.text = idleTitle;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setTriggerTitle:(NSString *)triggerTitle{
|
||||
_triggerTitle = triggerTitle;
|
||||
if (self.state == YJBannerViewStatusTrigger) {
|
||||
self.label.text = triggerTitle;
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
//
|
||||
// YJMallCollectionViewCell.h
|
||||
// WXReader
|
||||
//
|
||||
// Created by Andrew on 2019/5/28.
|
||||
// Copyright © 2019 Andrew. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface YJMallCollectionViewCell : UICollectionViewCell
|
||||
|
||||
@property (nonatomic, strong) TFBannerModel *bannerModel;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
+85
@@ -0,0 +1,85 @@
|
||||
//
|
||||
// YJMallCollectionViewCell.m
|
||||
// WXReader
|
||||
//
|
||||
// Created by Andrew on 2019/5/28.
|
||||
// Copyright © 2019 Andrew. All rights reserved.
|
||||
//
|
||||
|
||||
#import "YJMallCollectionViewCell.h"
|
||||
#import "UIView+AZGradient.h"
|
||||
|
||||
@implementation YJMallCollectionViewCell
|
||||
{
|
||||
UIImageView *backHoldImageView;
|
||||
UIView *backFrostedGlassView;
|
||||
|
||||
YYAnimatedImageView *bannerImageView;
|
||||
}
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame
|
||||
{
|
||||
if (self = [super initWithFrame:frame]) {
|
||||
[self createSubViews];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)createSubViews
|
||||
{
|
||||
backHoldImageView = [[UIImageView alloc] init];
|
||||
backHoldImageView.clipsToBounds = YES;
|
||||
backHoldImageView.contentMode = UIViewContentModeScaleAspectFill;
|
||||
[self addSubview:backHoldImageView];
|
||||
|
||||
[backHoldImageView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.edges.mas_equalTo(self);
|
||||
}];
|
||||
|
||||
backFrostedGlassView = [[UIView alloc] init];
|
||||
[self addSubview:backFrostedGlassView];
|
||||
|
||||
[backFrostedGlassView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.edges.mas_equalTo(self);
|
||||
}];
|
||||
|
||||
bannerImageView = [[YYAnimatedImageView alloc] init];
|
||||
bannerImageView.layer.cornerRadius = 4;
|
||||
bannerImageView.clipsToBounds = YES;
|
||||
bannerImageView.contentMode = UIViewContentModeScaleAspectFill;
|
||||
[self addSubview:bannerImageView];
|
||||
|
||||
[bannerImageView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.left.mas_equalTo(kHalfMargin + kQuarterMargin);
|
||||
make.bottom.mas_equalTo(self.mas_bottom);
|
||||
make.width.mas_equalTo(SCREEN_WIDTH - 2 * (kHalfMargin + kQuarterMargin));
|
||||
make.height.mas_equalTo(kGeometricHeight((SCREEN_WIDTH - 2 * (kHalfMargin + kQuarterMargin)), 5, 3));
|
||||
}];
|
||||
|
||||
}
|
||||
|
||||
- (void)setBannerModel:(TFBannerModel *)bannerModel
|
||||
{
|
||||
_bannerModel = bannerModel;
|
||||
|
||||
if (!bannerModel) {
|
||||
return;
|
||||
}
|
||||
@try {
|
||||
[[YYWebImageManager sharedManager] requestImageWithURL:[NSURL URLWithString:bannerModel.image?:@""] options:YYWebImageOptionSetImageWithFadeAnimation progress:nil transform:nil completion:^(UIImage * _Nullable image, NSURL * _Nonnull url, YYWebImageFromType from, YYWebImageStage stage, NSError * _Nullable error) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
self->backHoldImageView.image = image;
|
||||
[self->backFrostedGlassView az_setGradientBackgroundWithColors:@[[UIColor whiteColor], [[UIColor colorWithHexString:bannerModel.color?:@"#000000"] colorWithAlphaComponent:0.8]] locations:nil startPoint:CGPointMake(1.0, 1.0) endPoint:CGPointMake(1.0, 0.0)];
|
||||
|
||||
});
|
||||
}];
|
||||
} @catch (NSException *exception) {
|
||||
|
||||
} @finally {
|
||||
|
||||
}
|
||||
|
||||
[bannerImageView setImageWithURL:[NSURL URLWithString:bannerModel.image?:@""] placeholder:HoldImage options:YYWebImageOptionSetImageWithFadeAnimation completion:nil];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,214 @@
|
||||
//
|
||||
// YJBannerView.h
|
||||
// YJBannerViewDemo
|
||||
//
|
||||
// Created by YJHou on 2015/5/24.
|
||||
// Copyright © 2015年 Address:https://github.com/stackhou . All rights reserved.
|
||||
//
|
||||
|
||||
/**
|
||||
__ __ _ ____ __ ___
|
||||
\ \ / / | | __ ) __ _ _ __ _ __ ___ _ __ \ / (_) _____ __
|
||||
\ V / | | _ \ / _` | '_ \| '_ \ / _ \ '__\ \ / /| |/ _ \ \ /\ / /
|
||||
| | |_| | |_) | (_| | | | | | | | __/ | \ V / | | __/\ V V /
|
||||
|_|\___/|____/ \__,_|_| |_|_| |_|\___|_| \_/ |_|\___| \_/\_/
|
||||
|
||||
********* Current-Version : 2.3.8 ************
|
||||
|
||||
Version record: https://github.com/stackhou/YJBannerViewOC
|
||||
|
||||
*/
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "YJBannerViewCollectionView.h"
|
||||
|
||||
/** 指示器位置 */
|
||||
typedef NS_ENUM(NSInteger, PageControlAliment) {
|
||||
PageControlAlimentLeft = 0, // 居左
|
||||
PageControlAlimentCenter, // 居中
|
||||
PageControlAlimentRight // 居右
|
||||
};
|
||||
|
||||
/** 指示器样式 */
|
||||
typedef NS_ENUM(NSInteger, PageControlStyle) {
|
||||
PageControlNone = 0, // 无
|
||||
PageControlSystem, // 系统自带
|
||||
PageControlHollow, // 空心的
|
||||
PageControlCustom // 自定义 需要图片Dot
|
||||
};
|
||||
|
||||
/** 滚动方向 */
|
||||
typedef NS_ENUM(NSInteger, BannerViewDirection) {
|
||||
BannerViewDirectionLeft = 0, // 水平向左
|
||||
BannerViewDirectionRight, // 水平向右
|
||||
BannerViewDirectionTop, // 竖直向上
|
||||
BannerViewDirectionBottom // 竖直向下
|
||||
};
|
||||
|
||||
|
||||
@class YJBannerView;
|
||||
@protocol YJBannerViewDataSource, YJBannerViewDelegate;
|
||||
|
||||
@interface YJBannerView : UIView
|
||||
|
||||
@property (nonatomic, strong) UIImageView *backgroundImageView; /**< 数据为空时的背景图 */
|
||||
@property (nonatomic, strong, readonly) UICollectionViewFlowLayout *flowLayout;
|
||||
|
||||
@property (nonatomic, strong, readonly) YJBannerViewCollectionView *collectionView;
|
||||
|
||||
@property (nonatomic, weak) id<YJBannerViewDataSource> dataSource; /**< 数据源代理 */
|
||||
|
||||
@property (nonatomic, weak) id<YJBannerViewDelegate> delegate; /**< 代理 */
|
||||
|
||||
@property (nonatomic, assign) IBInspectable BOOL autoScroll; /**< 是否自动 默认YES */
|
||||
|
||||
@property (nonatomic, assign) IBInspectable CGFloat autoDuration; /**< 自动滚动时间间隔 默认3s */
|
||||
|
||||
@property (nonatomic, assign) IBInspectable BOOL cycleScrollEnable; /**< 是否首尾循环 默认是YES */
|
||||
|
||||
@property (nonatomic, assign) BannerViewDirection bannerViewScrollDirection; /**< 滚动方向 默认水平向左 */
|
||||
|
||||
@property (nonatomic, assign) BOOL bannerGestureEnable; /**< 手势是否可用 默认可用YES */
|
||||
|
||||
@property (nonatomic, assign) IBInspectable BOOL showFooter; /**< 显示footerView 默认是 NO 设置为YES 后将 autoScroll和cycleScrollEnable 自动置为NO 只支持水平向左 */
|
||||
@property (nonatomic, assign) NSInteger repeatCount; /**< 数据源重复次数 默认是200 若循环必须大于2的偶数 */
|
||||
|
||||
@property (nonatomic, strong) UIImage *placeholderImage; /**< 默认图片 */
|
||||
|
||||
@property (nonatomic, strong) UIImage *emptyImage; /**< 空数据图片 */
|
||||
|
||||
@property (nonatomic, copy) NSString *bannerViewSelectorString; /**< 自定义设置网络和默认图片的方法 */
|
||||
|
||||
@property (nonatomic, assign) UIViewContentMode bannerImageViewContentMode; /**< 填充样式 默认UIViewContentModeScaleAspectFill */
|
||||
|
||||
@property (nonatomic, assign) PageControlAliment pageControlAliment; /**< 指示器的位置 默认是Center */
|
||||
|
||||
@property (nonatomic, assign) PageControlStyle pageControlStyle; /**< 指示器样式 默认System */
|
||||
|
||||
@property (nonatomic, assign) CGFloat pageControlBottomMargin; /**< 指示器距离底部的间距 默认10 */
|
||||
|
||||
@property (nonatomic, assign) CGFloat pageControlHorizontalEdgeMargin; /**< 指示器水平方向上的边缘间距 默认10 */
|
||||
|
||||
@property (nonatomic, assign) CGFloat pageControlPadding; /**< 指示器水平方向上间距 默认 5 系统样式无效 */
|
||||
|
||||
@property (nonatomic, assign) CGSize pageControlDotSize; /**< 指示器圆标大小 默认 8*8*/
|
||||
|
||||
@property (nonatomic, strong) UIColor *pageControlNormalColor; /**< 指示器正常颜色 */
|
||||
|
||||
@property (nonatomic, strong) UIColor *pageControlHighlightColor; /**< 指示器小圆标颜色 */
|
||||
|
||||
@property (nonatomic, strong) UIImage *customPageControlNormalImage; /**< 指示器小圆点正常的图片 */
|
||||
|
||||
@property (nonatomic, strong) UIImage *customPageControlHighlightImage; /**< 当前分页控件图片 */
|
||||
|
||||
@property (nonatomic, strong) UIFont *titleFont; /**< 文字大小 默认14.0f */
|
||||
|
||||
@property (nonatomic, strong) UIColor *titleTextColor; /**< 文字颜色 默认 whiteColor */
|
||||
|
||||
@property (nonatomic, assign) NSTextAlignment titleAlignment; /**< 文字对齐方式 默认 Left */
|
||||
|
||||
@property (nonatomic, strong) UIColor *titleBackgroundColor; /**< 文字背景颜色 默认 黑0.5 */
|
||||
|
||||
@property (nonatomic, assign) CGFloat titleHeight; /**< 文字高度 默认30 */
|
||||
|
||||
@property (nonatomic, assign) CGFloat titleEdgeMargin; /**< 文字边缘间距 默认是10 */
|
||||
|
||||
@property (nonatomic, copy) NSString *footerIndicateImageName; /**< footer 指示图片名字 默认是自带的 */
|
||||
|
||||
@property (nonatomic, copy) NSString *footerNormalTitle; /**< footer 常态Title 默认 "拖动查看详情" */
|
||||
|
||||
@property (nonatomic, copy) NSString *footerTriggerTitle; /**< footer Trigger Title 默认 "释放查看详情" */
|
||||
|
||||
@property (nonatomic, strong) UIFont *footerTitleFont; /**< footer Font 默认 12 */
|
||||
|
||||
@property (nonatomic, strong) UIColor *footerTitleColor; /**< footer TitleColoe 默认是 darkGrayColor */
|
||||
|
||||
@property (nonatomic, copy) void(^didScroll2IndexBlock)(NSInteger index);
|
||||
@property (nonatomic, copy) void(^didSelectItemAtIndexBlock)(NSInteger index);
|
||||
@property (nonatomic, copy) void(^didEndTriggerFooterBlock)(void);
|
||||
|
||||
/**
|
||||
创建bannerView实例的方法
|
||||
|
||||
@param frame bannerView的Frame
|
||||
@param dataSource 数据源代理
|
||||
@param delegate 普通代理
|
||||
@param emptyImage 空数据图片
|
||||
@param placeholderImage 默认图片
|
||||
@param selectorString 必须是 UIImageView 设置图片和placeholderImage的方法 如: @"sd_setImageWithURL:placeholderImage:", 分别接收NSURL和UIImage两个参数
|
||||
@return YJBannerView 实例
|
||||
*/
|
||||
+ (YJBannerView *)bannerViewWithFrame:(CGRect)frame
|
||||
dataSource:(id<YJBannerViewDataSource>)dataSource
|
||||
delegate:(id<YJBannerViewDelegate>)delegate
|
||||
emptyImage:(UIImage *)emptyImage
|
||||
placeholderImage:(UIImage *)placeholderImage
|
||||
selectorString:(NSString *)selectorString;
|
||||
|
||||
/** 刷新BannerView数据 */
|
||||
- (void)reloadData;
|
||||
|
||||
/** 停止定时器接口 */
|
||||
- (void)invalidateTimerWhenAutoScroll;
|
||||
|
||||
/** 重新开启定时器 */
|
||||
- (void)startTimerWhenAutoScroll;
|
||||
|
||||
/** 调整滚动到指定位置 */
|
||||
- (void)adjustBannerViewScrollToIndex:(NSInteger)index animated:(BOOL)animated;
|
||||
|
||||
/** 如果卡屏请在控制器 viewWillAppear 内调用此方法 */
|
||||
- (void)adjustBannerViewWhenCardScreen;
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark - 协议部分
|
||||
@protocol YJBannerViewDataSource <NSObject>
|
||||
|
||||
@required
|
||||
/**
|
||||
显示Banner数据源代理方法
|
||||
|
||||
@param bannerView 当前Banner
|
||||
@return 兼容 http(s):// 和 本地图片Name 类型: NSString 数组
|
||||
*/
|
||||
- (NSArray *)bannerViewImages:(YJBannerView *)bannerView;
|
||||
|
||||
@optional
|
||||
/** 文字数据源 */
|
||||
- (NSArray *)bannerViewTitles:(YJBannerView *)bannerView;
|
||||
|
||||
/**
|
||||
自定义 View 要同时配合实现以下3个方法
|
||||
|
||||
@param bannerView 当前的Banner
|
||||
@return 需要注册的自定义View类的集合. e.g.: @[[CustomViewA class], [CustomViewB class]]
|
||||
*/
|
||||
- (NSArray *)bannerViewRegistCustomCellClass:(YJBannerView *)bannerView;
|
||||
/** 根据 Index 选择使用哪个 reuseIdentifier */
|
||||
- (Class)bannerView:(YJBannerView *)bannerView reuseIdentifierForIndex:(NSInteger)index;
|
||||
/** 自定义 View 刷新数据或者其他配置 */
|
||||
- (UICollectionViewCell *)bannerView:(YJBannerView *)bannerView customCell:(UICollectionViewCell *)customCell index:(NSInteger)index;
|
||||
|
||||
/** Footer 高度 默认是 49.0 */
|
||||
- (CGFloat)bannerViewFooterViewHeight:(YJBannerView *)bannerView;
|
||||
|
||||
@end
|
||||
|
||||
@protocol YJBannerViewDelegate <NSObject>
|
||||
|
||||
@optional
|
||||
/** 正在滚动的位置及偏移量 */
|
||||
- (void)bannerView:(YJBannerView *)bannerView didScrollCurrentIndex:(NSInteger)currentIndex contentOffset:(CGFloat)contentOffset;
|
||||
|
||||
/** 滚动到 index */
|
||||
- (void)bannerView:(YJBannerView *)bannerView didScroll2Index:(NSInteger)index;
|
||||
|
||||
/** 点击回调 */
|
||||
- (void)bannerView:(YJBannerView *)bannerView didSelectItemAtIndex:(NSInteger)index;
|
||||
|
||||
/** BannerView Footer 回调 */
|
||||
- (void)bannerViewFooterDidEndTrigger:(YJBannerView *)bannerView;
|
||||
|
||||
@end
|
||||
|
||||
@@ -0,0 +1,905 @@
|
||||
//
|
||||
// YJBannerView.m
|
||||
// YJBannerViewDemo
|
||||
//
|
||||
// Created by YJHou on 2015/5/24.
|
||||
// Copyright © 2015年 Address:https://github.com/stackhou/YJBannerViewOC . All rights reserved.
|
||||
//
|
||||
|
||||
/**
|
||||
__ __ _ ____ __ ___
|
||||
\ \ / / | | __ ) __ _ _ __ _ __ ___ _ __ \ / (_) _____ __
|
||||
\ V / | | _ \ / _` | '_ \| '_ \ / _ \ '__\ \ / /| |/ _ \ \ /\ / /
|
||||
| | |_| | |_) | (_| | | | | | | | __/ | \ V / | | __/\ V V /
|
||||
|_|\___/|____/ \__,_|_| |_|_| |_|\___|_| \_/ |_|\___| \_/\_/
|
||||
|
||||
*/
|
||||
|
||||
#import "YJBannerView.h"
|
||||
#import "YJBannerViewCell.h"
|
||||
#import "UIView+YJBannerViewExt.h"
|
||||
#import "YJHollowPageControl.h"
|
||||
#import "YJBannerViewFooter.h"
|
||||
|
||||
static NSString *const bannerViewCellId = @"YJBannerView";
|
||||
static NSString *const bannerViewFooterId = @"YJBannerViewFooter";
|
||||
static NSInteger const totalCollectionViewCellCount = 200;
|
||||
#define kPageControlDotDefaultSize CGSizeMake(8, 8)
|
||||
#define BANNER_FOOTER_HEIGHT 49.0
|
||||
|
||||
@interface YJBannerView () <UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout> {
|
||||
YJBannerViewCollectionView *_collectionView;
|
||||
UICollectionViewFlowLayout *_flowLayout;
|
||||
}
|
||||
|
||||
@property (nonatomic, weak) UIControl *pageControl;
|
||||
@property (nonatomic, weak) NSTimer *timer;
|
||||
@property (nonatomic, assign) NSInteger totalBannerItemsCount;
|
||||
@property (nonatomic, strong) NSArray *saveScrollViewGestures;
|
||||
@property (nonatomic, strong) YJBannerViewFooter *bannerFooter;
|
||||
@property (nonatomic, strong) NSArray *showNewDatasource;
|
||||
@property (nonatomic, assign) CGFloat lastContentOffset;
|
||||
|
||||
@end
|
||||
|
||||
@implementation YJBannerView
|
||||
@synthesize autoScroll = _autoScroll;
|
||||
@synthesize cycleScrollEnable = _cycleScrollEnable;
|
||||
@synthesize bannerImageViewContentMode = _bannerImageViewContentMode;
|
||||
@synthesize pageControlNormalColor = _pageControlNormalColor;
|
||||
@synthesize pageControlHighlightColor = _pageControlHighlightColor;
|
||||
|
||||
#pragma mark - Public API
|
||||
+ (YJBannerView *)bannerViewWithFrame:(CGRect)frame
|
||||
dataSource:(id<YJBannerViewDataSource>)dataSource
|
||||
delegate:(id<YJBannerViewDelegate>)delegate
|
||||
emptyImage:(UIImage *)emptyImage
|
||||
placeholderImage:(UIImage *)placeholderImage
|
||||
selectorString:(NSString *)selectorString{
|
||||
|
||||
YJBannerView *bannerView = [[YJBannerView alloc] initWithFrame:frame];
|
||||
bannerView.dataSource = dataSource;
|
||||
bannerView.delegate = delegate;
|
||||
bannerView.bannerViewSelectorString = selectorString;
|
||||
bannerView.emptyImage = emptyImage;
|
||||
bannerView.placeholderImage = placeholderImage;
|
||||
|
||||
return bannerView;
|
||||
}
|
||||
|
||||
- (void)reloadData{
|
||||
|
||||
[self invalidateTimer];
|
||||
self.showNewDatasource = [self _getImageDataSources];
|
||||
|
||||
// Hidden when data source is greater than zero
|
||||
self.backgroundImageView.hidden = ([self _imageDataSources].count > 0);
|
||||
|
||||
if ([self _imageDataSources].count > 1) {
|
||||
self.collectionView.scrollEnabled = YES;
|
||||
[self setAutoScroll:self.autoScroll];
|
||||
} else {
|
||||
|
||||
if ([self _imageDataSources].count == 0) { self.showFooter = NO; }
|
||||
|
||||
BOOL isCan = ([self _imageDataSources].count == 0)?NO:(self.showFooter?YES:NO);
|
||||
|
||||
self.collectionView.scrollEnabled = isCan;
|
||||
|
||||
[self invalidateTimerWhenAutoScroll];
|
||||
}
|
||||
|
||||
[self _setFooterViewCanShow:self.showFooter];
|
||||
[self _setupPageControl];
|
||||
|
||||
// Regist Custom Cell
|
||||
if (self.dataSource && [self.dataSource respondsToSelector:@selector(bannerViewRegistCustomCellClass:)] && [self.dataSource bannerViewRegistCustomCellClass:self]) {
|
||||
NSArray *clazzs = [self.dataSource bannerViewRegistCustomCellClass:self];
|
||||
for (Class clazz in clazzs) {
|
||||
[self.collectionView registerClass:clazz forCellWithReuseIdentifier:NSStringFromClass(clazz)];
|
||||
}
|
||||
}
|
||||
|
||||
[self.collectionView reloadData];
|
||||
}
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame{
|
||||
self = [super initWithFrame:frame];
|
||||
if (self) {
|
||||
[self _initSetting];
|
||||
[self addSubview:self.collectionView];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id)initWithCoder:(NSCoder *)aDecoder{
|
||||
if (self = [super initWithCoder:aDecoder]) {
|
||||
[self _initSetting];
|
||||
[self addSubview:self.collectionView];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)awakeFromNib{
|
||||
[super awakeFromNib];
|
||||
[self _initSetting];
|
||||
[self addSubview:self.collectionView];
|
||||
}
|
||||
|
||||
/** Initialize the default settings */
|
||||
- (void)_initSetting{
|
||||
|
||||
self.backgroundColor = [UIColor whiteColor];
|
||||
_autoDuration = 3.0;
|
||||
_autoScroll = YES;
|
||||
_pageControlStyle = PageControlSystem;
|
||||
_pageControlAliment = PageControlAlimentCenter;
|
||||
_pageControlDotSize = kPageControlDotDefaultSize;
|
||||
_pageControlBottomMargin = 10.0f;
|
||||
_pageControlHorizontalEdgeMargin = 10.0f;
|
||||
_pageControlPadding = 5.0f;
|
||||
|
||||
_titleHeight = 30.0f;
|
||||
_titleEdgeMargin = 10.0f;
|
||||
_titleAlignment = NSTextAlignmentLeft;
|
||||
_bannerGestureEnable = YES;
|
||||
_cycleScrollEnable = YES;
|
||||
|
||||
_showFooter = NO;
|
||||
_footerIndicateImageName = @"YJBannerView.bundle/yjbanner_arrow.png";
|
||||
_footerNormalTitle = TFLocalizedString(@"拖动查看详情");
|
||||
_footerTriggerTitle = TFLocalizedString(@"释放查看详情");
|
||||
}
|
||||
|
||||
#pragma mark - Setter && Getter
|
||||
- (void)setEmptyImage:(UIImage *)emptyImage{
|
||||
_emptyImage = emptyImage;
|
||||
if (emptyImage) {
|
||||
self.backgroundImageView.image = emptyImage;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setPageControlDotSize:(CGSize)pageControlDotSize{
|
||||
|
||||
_pageControlDotSize = pageControlDotSize;
|
||||
|
||||
[self _setupPageControl];
|
||||
if ([self.pageControl isKindOfClass:[YJHollowPageControl class]]) {
|
||||
YJHollowPageControl *pageContol = (YJHollowPageControl *)_pageControl;
|
||||
pageContol.dotSize = pageControlDotSize;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setPageControlStyle:(PageControlStyle)pageControlStyle{
|
||||
|
||||
_pageControlStyle = pageControlStyle;
|
||||
|
||||
[self _setupPageControl];
|
||||
}
|
||||
|
||||
- (void)setPageControlNormalColor:(UIColor *)pageControlNormalColor{
|
||||
|
||||
_pageControlNormalColor = pageControlNormalColor;
|
||||
|
||||
if ([self.pageControl isKindOfClass:[YJHollowPageControl class]]) {
|
||||
YJHollowPageControl *pageControl = (YJHollowPageControl *)_pageControl;
|
||||
pageControl.dotNormalColor = pageControlNormalColor;
|
||||
}else if ([self.pageControl isKindOfClass:[UIPageControl class]]) {
|
||||
UIPageControl *pageControl = (UIPageControl *)_pageControl;
|
||||
pageControl.pageIndicatorTintColor = pageControlNormalColor;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setPageControlHighlightColor:(UIColor *)pageControlHighlightColor{
|
||||
|
||||
_pageControlHighlightColor = pageControlHighlightColor;
|
||||
|
||||
if ([self.pageControl isKindOfClass:[YJHollowPageControl class]]) {
|
||||
YJHollowPageControl *pageControl = (YJHollowPageControl *)_pageControl;
|
||||
pageControl.dotCurrentColor = pageControlHighlightColor;
|
||||
} else if ([self.pageControl isKindOfClass:[UIPageControl class]]){
|
||||
UIPageControl *pageControl = (UIPageControl *)_pageControl;
|
||||
pageControl.currentPageIndicatorTintColor = pageControlHighlightColor;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setCustomPageControlNormalImage:(UIImage *)customPageControlNormalImage{
|
||||
_customPageControlNormalImage = customPageControlNormalImage;
|
||||
[self setCustomPageControlDotImage:customPageControlNormalImage isCurrentPageDot:NO];
|
||||
}
|
||||
|
||||
- (void)setCustomPageControlHighlightImage:(UIImage *)customPageControlHighlightImage{
|
||||
_customPageControlHighlightImage = customPageControlHighlightImage;
|
||||
[self setCustomPageControlDotImage:customPageControlHighlightImage isCurrentPageDot:YES];
|
||||
}
|
||||
|
||||
- (void)setCustomPageControlDotImage:(UIImage *)image isCurrentPageDot:(BOOL)isCurrentPageDot{
|
||||
|
||||
if (!image || !self.pageControl) return;
|
||||
|
||||
if ([self.pageControl isKindOfClass:[YJHollowPageControl class]]) {
|
||||
YJHollowPageControl *pageControl = (YJHollowPageControl *)_pageControl;
|
||||
if (isCurrentPageDot) {
|
||||
pageControl.dotCurrentImage = image;
|
||||
} else {
|
||||
pageControl.dotNormalImage = image;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setAutoScroll:(BOOL)autoScroll{
|
||||
|
||||
_autoScroll = autoScroll;
|
||||
[self invalidateTimer];
|
||||
if (autoScroll) {
|
||||
[self _setupTimer];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setBannerViewScrollDirection:(BannerViewDirection)bannerViewScrollDirection{
|
||||
|
||||
if (self.showFooter && bannerViewScrollDirection != BannerViewDirectionLeft) {
|
||||
bannerViewScrollDirection = BannerViewDirectionLeft;
|
||||
}
|
||||
|
||||
_bannerViewScrollDirection = bannerViewScrollDirection;
|
||||
|
||||
if (bannerViewScrollDirection == BannerViewDirectionLeft || bannerViewScrollDirection == BannerViewDirectionRight) {
|
||||
self.flowLayout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
|
||||
}else if (bannerViewScrollDirection == BannerViewDirectionTop || bannerViewScrollDirection == BannerViewDirectionBottom){
|
||||
self.flowLayout.scrollDirection = UICollectionViewScrollDirectionVertical;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setAutoDuration:(CGFloat)autoDuration{
|
||||
|
||||
_autoDuration = autoDuration;
|
||||
[self setAutoScroll:self.autoScroll];
|
||||
}
|
||||
|
||||
- (void)setBannerGestureEnable:(BOOL)bannerGestureEnable{
|
||||
if (_bannerGestureEnable && bannerGestureEnable) { // 不操作
|
||||
}else if (!_bannerGestureEnable && bannerGestureEnable){
|
||||
self.collectionView.canCancelContentTouches = YES;
|
||||
for (NSInteger i = 0; i < self.saveScrollViewGestures.count; i++) {
|
||||
UIGestureRecognizer *gesture = self.saveScrollViewGestures[i];
|
||||
[self.collectionView addGestureRecognizer:gesture];
|
||||
}
|
||||
}else if (_bannerGestureEnable && !bannerGestureEnable){
|
||||
self.collectionView.canCancelContentTouches = NO;
|
||||
for (UIGestureRecognizer *gesture in self.collectionView.gestureRecognizers) {
|
||||
[self.collectionView removeGestureRecognizer:gesture];
|
||||
}
|
||||
}
|
||||
_bannerGestureEnable = bannerGestureEnable;
|
||||
}
|
||||
|
||||
- (void)setBannerImageViewContentMode:(UIViewContentMode)bannerImageViewContentMode{
|
||||
_bannerImageViewContentMode = bannerImageViewContentMode;
|
||||
self.backgroundImageView.contentMode = bannerImageViewContentMode;
|
||||
}
|
||||
|
||||
- (NSInteger)repeatCount{
|
||||
if (_repeatCount <= 0) {
|
||||
return totalCollectionViewCellCount;
|
||||
}else{
|
||||
if (_repeatCount % 2 != 0) {
|
||||
return _repeatCount + 1;
|
||||
}else{
|
||||
return _repeatCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Getter
|
||||
- (NSInteger)totalBannerItemsCount{
|
||||
|
||||
return self.cycleScrollEnable?(([self _imageDataSources].count > 1)?([self _imageDataSources].count * self.repeatCount):[self _imageDataSources].count):([self _imageDataSources].count);
|
||||
}
|
||||
|
||||
- (BOOL)autoScroll{
|
||||
if (self.showFooter) {
|
||||
return NO;
|
||||
}
|
||||
return _autoScroll;
|
||||
}
|
||||
|
||||
- (BOOL)cycleScrollEnable{
|
||||
if (self.showFooter) {
|
||||
return NO;
|
||||
}
|
||||
return _cycleScrollEnable;
|
||||
}
|
||||
|
||||
- (UIFont *)titleFont{
|
||||
if (!_titleFont) {
|
||||
_titleFont = kMainFont;
|
||||
}
|
||||
return _titleFont;
|
||||
}
|
||||
|
||||
- (UIColor *)titleTextColor{
|
||||
if (!_titleTextColor) {
|
||||
_titleTextColor = [UIColor whiteColor];
|
||||
}
|
||||
return _titleTextColor;
|
||||
}
|
||||
|
||||
- (UIColor *)titleBackgroundColor{
|
||||
if (!_titleBackgroundColor) {
|
||||
_titleBackgroundColor = [UIColor colorWithRed:0 green:0 blue:0 alpha:0.5];
|
||||
}
|
||||
return _titleBackgroundColor;
|
||||
}
|
||||
|
||||
- (UIFont *)footerTitleFont{
|
||||
if (!_footerTitleFont) {
|
||||
_footerTitleFont = kFont12;
|
||||
}
|
||||
return _footerTitleFont;
|
||||
}
|
||||
|
||||
- (UIColor *)footerTitleColor{
|
||||
if (!_footerTitleColor) {
|
||||
_footerTitleColor = [UIColor darkGrayColor];
|
||||
}
|
||||
return _footerTitleColor;
|
||||
}
|
||||
|
||||
- (UIViewContentMode)bannerImageViewContentMode{
|
||||
if (!_bannerImageViewContentMode) {
|
||||
_bannerImageViewContentMode = UIViewContentModeScaleAspectFill;
|
||||
}
|
||||
return _bannerImageViewContentMode;
|
||||
}
|
||||
|
||||
- (UIColor *)pageControlNormalColor{
|
||||
if (!_pageControlNormalColor) {
|
||||
_pageControlNormalColor = [UIColor lightGrayColor];
|
||||
}
|
||||
return _pageControlNormalColor;
|
||||
}
|
||||
|
||||
- (UIColor *)pageControlHighlightColor{
|
||||
if (!_pageControlHighlightColor) {
|
||||
_pageControlHighlightColor = [UIColor whiteColor];
|
||||
}
|
||||
return _pageControlHighlightColor;
|
||||
}
|
||||
|
||||
- (NSArray *)saveScrollViewGestures{
|
||||
if (!_saveScrollViewGestures) {
|
||||
_saveScrollViewGestures = self.collectionView.gestureRecognizers;
|
||||
}
|
||||
return _saveScrollViewGestures;
|
||||
}
|
||||
|
||||
#pragma mark - layoutSubviews
|
||||
- (void)layoutSubviews{
|
||||
[super layoutSubviews];
|
||||
|
||||
self.dataSource = self.dataSource;
|
||||
[super layoutSubviews];
|
||||
|
||||
self.flowLayout.itemSize = self.frame.size;
|
||||
|
||||
self.collectionView.frame = self.bounds;
|
||||
|
||||
if (self.collectionView.contentOffset.x == 0 && self.totalBannerItemsCount) {
|
||||
NSInteger targetIndex = self.cycleScrollEnable?(self.totalBannerItemsCount * 0.5):(0);
|
||||
[self _scrollBannerViewToSpecifiedPositionIndex:targetIndex animated:NO];
|
||||
}
|
||||
|
||||
CGSize size = CGSizeZero;
|
||||
|
||||
if ([self.pageControl isKindOfClass:[YJHollowPageControl class]]) {
|
||||
|
||||
YJHollowPageControl *pageControl = (YJHollowPageControl *)_pageControl;
|
||||
|
||||
if (!(self.customPageControlNormalImage && self.customPageControlHighlightImage && CGSizeEqualToSize(kPageControlDotDefaultSize, self.pageControlDotSize))) {
|
||||
pageControl.dotSize = self.pageControlDotSize;
|
||||
}
|
||||
|
||||
size = [pageControl sizeForNumberOfPages:[self _imageDataSources].count];
|
||||
} else {
|
||||
size = CGSizeMake([self _imageDataSources].count * self.pageControlDotSize.width * 1.5, self.pageControlDotSize.height);
|
||||
}
|
||||
CGFloat x = (self.width_bannerView - size.width) * 0.5;
|
||||
if (self.pageControlAliment == PageControlAlimentLeft) {
|
||||
x = 0.0f;
|
||||
}else if (self.pageControlAliment == PageControlAlimentCenter){
|
||||
}else if (self.pageControlAliment == PageControlAlimentRight){
|
||||
x = self.collectionView.width_bannerView - size.width;
|
||||
}
|
||||
|
||||
CGFloat y = self.collectionView.height_bannerView - size.height;
|
||||
|
||||
if ([self.pageControl isKindOfClass:[YJHollowPageControl class]]) {
|
||||
|
||||
YJHollowPageControl *pageControl = (YJHollowPageControl *)_pageControl;
|
||||
[pageControl sizeToFit];
|
||||
}
|
||||
|
||||
CGRect pageControlFrame = CGRectMake(x, y, size.width, size.height);
|
||||
if (self.pageControlAliment == PageControlAlimentLeft) {
|
||||
pageControlFrame.origin.x += self.pageControlHorizontalEdgeMargin;
|
||||
}else if (self.pageControlAliment == PageControlAlimentRight){
|
||||
pageControlFrame.origin.x -= self.pageControlHorizontalEdgeMargin;
|
||||
}
|
||||
pageControlFrame.origin.y -= self.pageControlBottomMargin;
|
||||
self.pageControl.frame = pageControlFrame;
|
||||
|
||||
self.pageControl.hidden = self.pageControlStyle == PageControlNone;
|
||||
|
||||
if (self.backgroundImageView) {
|
||||
self.backgroundImageView.frame = self.bounds;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Resolve compatibility optimization issues
|
||||
- (void)willMoveToSuperview:(UIView *)newSuperview{
|
||||
if (!newSuperview) {
|
||||
[self invalidateTimer];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)adjustBannerViewScrollToIndex:(NSInteger)index animated:(BOOL)animated{
|
||||
|
||||
if (self.showNewDatasource.count == 0) { return; }
|
||||
if (index >= 0 && index < self.showNewDatasource.count) {
|
||||
if (self.autoScroll) { [self invalidateTimer]; }
|
||||
|
||||
[self _scrollToIndex:((int)(self.totalBannerItemsCount * 0.5 + index)) animated:animated];
|
||||
|
||||
if (self.autoScroll) { [self _setupTimer]; }
|
||||
}
|
||||
}
|
||||
|
||||
- (void)adjustBannerViewWhenCardScreen{
|
||||
|
||||
long targetIndex = [self _currentPageIndex];
|
||||
if (targetIndex < self.totalBannerItemsCount) {
|
||||
[self _scrollBannerViewToSpecifiedPositionIndex:targetIndex animated:NO];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - UICollectionViewDataSource
|
||||
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section{
|
||||
return self.totalBannerItemsCount;
|
||||
}
|
||||
|
||||
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
|
||||
|
||||
YJBannerViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:bannerViewCellId forIndexPath:indexPath];
|
||||
long itemIndex = [self _getRealIndexFromCurrentCellIndex:indexPath.item];
|
||||
|
||||
// Custom Cell
|
||||
if (self.dataSource && [self.dataSource respondsToSelector:@selector(bannerViewRegistCustomCellClass:)] && [self.dataSource bannerViewRegistCustomCellClass:self] && [self.dataSource respondsToSelector:@selector(bannerView:customCell:index:)] && [self.dataSource respondsToSelector:@selector(bannerView:reuseIdentifierForIndex:)]) {
|
||||
|
||||
NSString *reuseIdentifier = NSStringFromClass([self.dataSource bannerView:self reuseIdentifierForIndex:itemIndex]);
|
||||
if (reuseIdentifier.length > 0) {
|
||||
UICollectionViewCell *customCell = [collectionView dequeueReusableCellWithReuseIdentifier:reuseIdentifier forIndexPath:indexPath];
|
||||
|
||||
if ([self.dataSource bannerView:self customCell:customCell index:itemIndex]) {
|
||||
return customCell;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NSString *imagePath = (itemIndex < [self _imageDataSources].count)?[self _imageDataSources][itemIndex]:nil;
|
||||
NSString *title = (itemIndex < [self _titlesDataSources].count)?[self _titlesDataSources][itemIndex]:nil;
|
||||
|
||||
if (!cell.isConfigured) {
|
||||
cell.titleLabelBackgroundColor = self.titleBackgroundColor;
|
||||
cell.titleLabelHeight = self.titleHeight;
|
||||
cell.titleLabelEdgeMargin = self.titleEdgeMargin;
|
||||
cell.titleLabelTextAlignment = self.titleAlignment;
|
||||
cell.titleLabelTextColor = self.titleTextColor;
|
||||
cell.titleLabelTextFont = self.titleFont;
|
||||
cell.showImageViewContentMode = self.bannerImageViewContentMode;
|
||||
cell.clipsToBounds = YES;
|
||||
cell.isConfigured = YES;
|
||||
}
|
||||
|
||||
[cell cellWithSelectorString:self.bannerViewSelectorString imagePath:imagePath placeholderImage:self.placeholderImage title:title];
|
||||
|
||||
return cell;
|
||||
}
|
||||
|
||||
// Setting Footer Size
|
||||
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout referenceSizeForFooterInSection:(NSInteger)section{
|
||||
return CGSizeMake((self.showFooter && [self _imageDataSources].count != 0)?[self _bannerViewFooterHeight]:0.0f, self.frame.size.height);
|
||||
}
|
||||
|
||||
// Footer
|
||||
- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath{
|
||||
|
||||
if(kind == UICollectionElementKindSectionFooter){
|
||||
|
||||
YJBannerViewFooter *footer = [collectionView dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionFooter withReuseIdentifier:bannerViewFooterId forIndexPath:indexPath];
|
||||
self.bannerFooter = footer;
|
||||
|
||||
footer.IndicateImageName = self.footerIndicateImageName;
|
||||
footer.footerTitleFont = self.footerTitleFont;
|
||||
footer.footerTitleColor = self.footerTitleColor;
|
||||
footer.idleTitle = self.footerNormalTitle;
|
||||
footer.triggerTitle = self.footerTriggerTitle;
|
||||
|
||||
footer.hidden = !(self.showFooter && [self _imageDataSources].count != 0);
|
||||
|
||||
return footer;
|
||||
}else{
|
||||
UICollectionReusableView *collectionHeaderView = [collectionView dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:@"HeaderView" forIndexPath:indexPath];
|
||||
return collectionHeaderView;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - UIScrollViewDelegate
|
||||
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath{
|
||||
if (self.delegate && [self.delegate respondsToSelector:@selector(bannerView:didSelectItemAtIndex:)]) {
|
||||
[self.delegate bannerView:self didSelectItemAtIndex:[self _getRealIndexFromCurrentCellIndex:indexPath.item]];
|
||||
}
|
||||
if (self.didSelectItemAtIndexBlock) {
|
||||
self.didSelectItemAtIndexBlock([self _getRealIndexFromCurrentCellIndex:indexPath.item]);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)scrollViewDidScroll:(UIScrollView *)scrollView{
|
||||
|
||||
if (![self _imageDataSources].count) return;
|
||||
int itemIndex = [self _currentPageIndex];
|
||||
int indexOnPageControl = [self _getRealIndexFromCurrentCellIndex:itemIndex];
|
||||
|
||||
// 手动退拽时左右两端
|
||||
if (scrollView == self.collectionView && scrollView.isDragging && self.cycleScrollEnable) {
|
||||
NSInteger targetIndex = self.totalBannerItemsCount * 0.5;
|
||||
if (itemIndex == 0) { // top
|
||||
[self _scrollBannerViewToSpecifiedPositionIndex:targetIndex animated:NO];
|
||||
}else if (itemIndex == (self.totalBannerItemsCount - 1)){ // bottom
|
||||
targetIndex -= 1;
|
||||
[self _scrollBannerViewToSpecifiedPositionIndex:targetIndex animated:NO];
|
||||
}
|
||||
}
|
||||
|
||||
// pageControl
|
||||
if ([self.pageControl isKindOfClass:[YJHollowPageControl class]]) {
|
||||
YJHollowPageControl *pageControl = (YJHollowPageControl *)_pageControl;
|
||||
pageControl.currentPage = indexOnPageControl;
|
||||
} else {
|
||||
UIPageControl *pageControl = (UIPageControl *)_pageControl;
|
||||
pageControl.currentPage = indexOnPageControl;
|
||||
}
|
||||
|
||||
// Footer
|
||||
if (self.showFooter) {
|
||||
static CGFloat lastOffset;
|
||||
CGFloat footerDisplayOffset = (self.collectionView.contentOffset.x - (self.flowLayout.itemSize.width * (self.totalBannerItemsCount - 1)));
|
||||
|
||||
if (footerDisplayOffset > 0){
|
||||
if (footerDisplayOffset > [self _bannerViewFooterHeight]) {
|
||||
if (lastOffset > 0) return;
|
||||
self.bannerFooter.state = YJBannerViewStatusTrigger;
|
||||
} else {
|
||||
if (lastOffset < 0) return;
|
||||
self.bannerFooter.state = YJBannerViewStatusIdle;
|
||||
}
|
||||
lastOffset = footerDisplayOffset - [self _bannerViewFooterHeight];
|
||||
}
|
||||
}
|
||||
|
||||
// contentOffset
|
||||
[self _saveInitializationContentOffsetJudgeZero:YES];
|
||||
CGFloat contentOffset = 0.0f;
|
||||
if (self.flowLayout.scrollDirection == UICollectionViewScrollDirectionHorizontal) {
|
||||
contentOffset = self.collectionView.contentOffset.x;
|
||||
} else {
|
||||
contentOffset = self.collectionView.contentOffset.y;
|
||||
}
|
||||
CGFloat distance = fabs(self.lastContentOffset - contentOffset);
|
||||
|
||||
if (self.delegate && [self.delegate respondsToSelector:@selector(bannerView:didScrollCurrentIndex:contentOffset:)]) {
|
||||
[self.delegate bannerView:self didScrollCurrentIndex:indexOnPageControl contentOffset:distance];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView{
|
||||
[self invalidateTimerWhenAutoScroll];
|
||||
}
|
||||
|
||||
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate{
|
||||
|
||||
[self startTimerWhenAutoScroll];
|
||||
|
||||
if (self.showFooter) {
|
||||
CGFloat footerDisplayOffset = (self.collectionView.contentOffset.x - (self.flowLayout.itemSize.width * (self.totalBannerItemsCount - 1)));
|
||||
|
||||
if (footerDisplayOffset > [self _bannerViewFooterHeight]) {
|
||||
if (self.delegate && [self.delegate respondsToSelector:@selector(bannerViewFooterDidEndTrigger:)]) {
|
||||
[self.delegate bannerViewFooterDidEndTrigger:self];
|
||||
}
|
||||
|
||||
if (self.didEndTriggerFooterBlock) {
|
||||
self.didEndTriggerFooterBlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView{
|
||||
[self scrollViewDidEndScrollingAnimation:self.collectionView];
|
||||
}
|
||||
|
||||
- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView{
|
||||
if (![self _imageDataSources].count) return;
|
||||
int itemIndex = [self _currentPageIndex];
|
||||
int indexOnPageControl = [self _getRealIndexFromCurrentCellIndex:itemIndex];
|
||||
|
||||
if (self.delegate && [self.delegate respondsToSelector:@selector(bannerView:didScroll2Index:)]) {
|
||||
[self.delegate bannerView:self didScroll2Index:indexOnPageControl];
|
||||
}
|
||||
if (self.didScroll2IndexBlock) {
|
||||
self.didScroll2IndexBlock(indexOnPageControl);
|
||||
}
|
||||
|
||||
[self _saveInitializationContentOffsetJudgeZero:NO];
|
||||
}
|
||||
|
||||
#pragma mark - Private function method
|
||||
/** install PageControl */
|
||||
- (void)_setupPageControl{
|
||||
|
||||
if (_pageControl) [_pageControl removeFromSuperview];
|
||||
|
||||
if ([self _imageDataSources].count == 0) {return;}
|
||||
|
||||
if ([self _imageDataSources].count == 1) {return;}
|
||||
|
||||
int indexOnPageControl = [self _getRealIndexFromCurrentCellIndex:[self _currentPageIndex]];
|
||||
|
||||
switch (self.pageControlStyle) {
|
||||
case PageControlNone:{
|
||||
break;
|
||||
}
|
||||
case PageControlSystem:{
|
||||
UIPageControl *pageControl = [[UIPageControl alloc] init];
|
||||
pageControl.numberOfPages = [self _imageDataSources].count;
|
||||
pageControl.currentPageIndicatorTintColor = self.pageControlHighlightColor;
|
||||
pageControl.pageIndicatorTintColor = self.pageControlNormalColor;
|
||||
pageControl.userInteractionEnabled = NO;
|
||||
pageControl.currentPage = indexOnPageControl;
|
||||
[self addSubview:pageControl];
|
||||
_pageControl = pageControl;
|
||||
break;
|
||||
}
|
||||
case PageControlHollow:{
|
||||
YJHollowPageControl *pageControl = [[YJHollowPageControl alloc] init];
|
||||
pageControl.numberOfPages = [self _imageDataSources].count;
|
||||
pageControl.dotNormalColor = self.pageControlNormalColor;
|
||||
pageControl.dotCurrentColor = self.pageControlHighlightColor;
|
||||
pageControl.userInteractionEnabled = NO;
|
||||
pageControl.resizeScale = 1.0;
|
||||
pageControl.spacing = self.pageControlPadding;
|
||||
pageControl.currentPage = indexOnPageControl;
|
||||
[self addSubview:pageControl];
|
||||
_pageControl = pageControl;
|
||||
break;
|
||||
}
|
||||
case PageControlCustom:{
|
||||
|
||||
YJHollowPageControl *pageControl = [[YJHollowPageControl alloc] init];
|
||||
pageControl.numberOfPages = [self _imageDataSources].count;
|
||||
pageControl.dotNormalColor = self.pageControlNormalColor;
|
||||
pageControl.dotCurrentColor = self.pageControlHighlightColor;
|
||||
pageControl.userInteractionEnabled = NO;
|
||||
pageControl.resizeScale = 1.0;
|
||||
pageControl.spacing = self.pageControlPadding;
|
||||
pageControl.currentPage = indexOnPageControl;
|
||||
[self addSubview:pageControl];
|
||||
_pageControl = pageControl;
|
||||
|
||||
if (self.customPageControlNormalImage) {
|
||||
self.customPageControlNormalImage = self.customPageControlNormalImage;
|
||||
}
|
||||
|
||||
if (self.customPageControlHighlightImage) {
|
||||
self.customPageControlHighlightImage = self.customPageControlHighlightImage;
|
||||
}
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/** install Timer */
|
||||
- (void)_setupTimer{
|
||||
|
||||
[self invalidateTimer];
|
||||
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:self.autoDuration target:self selector:@selector(_automaticScrollAction) userInfo:nil repeats:YES];
|
||||
_timer = timer;
|
||||
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
|
||||
}
|
||||
|
||||
/** stop timer */
|
||||
- (void)invalidateTimer{
|
||||
|
||||
[_timer invalidate];
|
||||
_timer = nil;
|
||||
}
|
||||
|
||||
/** stop timer api */
|
||||
- (void)invalidateTimerWhenAutoScroll{
|
||||
if (self.autoScroll) {
|
||||
[self invalidateTimer];
|
||||
}
|
||||
}
|
||||
|
||||
/** restart timer api */
|
||||
- (void)startTimerWhenAutoScroll{
|
||||
if (self.autoScroll) {
|
||||
[self _setupTimer];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)_automaticScrollAction{
|
||||
|
||||
if (self.totalBannerItemsCount == 0) return;
|
||||
int currentIndex = [self _currentPageIndex];
|
||||
if (self.bannerViewScrollDirection == BannerViewDirectionLeft || self.bannerViewScrollDirection == BannerViewDirectionTop) {
|
||||
[self _scrollToIndex:(currentIndex + 1) animated:YES];
|
||||
}else if (self.bannerViewScrollDirection == BannerViewDirectionRight || self.bannerViewScrollDirection == BannerViewDirectionBottom){
|
||||
if ((currentIndex - 1) < 0) { // 小于零
|
||||
currentIndex = self.cycleScrollEnable?(self.totalBannerItemsCount * 0.5):(0);
|
||||
[self _scrollBannerViewToSpecifiedPositionIndex:(currentIndex - 1) animated:NO];
|
||||
}else{
|
||||
[self _scrollToIndex:(currentIndex - 1) animated:YES];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)_scrollToIndex:(int)targetIndex animated:(BOOL)animated{
|
||||
|
||||
if (targetIndex >= self.totalBannerItemsCount) { // 超过最大
|
||||
targetIndex = self.cycleScrollEnable?(self.totalBannerItemsCount * 0.5):(0);
|
||||
[self _scrollBannerViewToSpecifiedPositionIndex:targetIndex animated:NO];
|
||||
}else{
|
||||
[self _scrollBannerViewToSpecifiedPositionIndex:targetIndex animated:animated];
|
||||
}
|
||||
}
|
||||
|
||||
/** current page index */
|
||||
- (int)_currentPageIndex{
|
||||
|
||||
if (self.collectionView.width_bannerView == 0 || self.collectionView.height_bannerView == 0) {return 0;}
|
||||
int index = 0;
|
||||
if (self.flowLayout.scrollDirection == UICollectionViewScrollDirectionHorizontal) {
|
||||
index = (self.collectionView.contentOffset.x + self.flowLayout.itemSize.width * 0.5) / self.flowLayout.itemSize.width;
|
||||
} else {
|
||||
index = (self.collectionView.contentOffset.y + self.flowLayout.itemSize.height * 0.5) / self.flowLayout.itemSize.height;
|
||||
}
|
||||
return MAX(0, index);
|
||||
}
|
||||
|
||||
/** current real index */
|
||||
- (int)_getRealIndexFromCurrentCellIndex:(NSInteger)cellIndex{
|
||||
return (int)cellIndex % [self _imageDataSources].count;
|
||||
}
|
||||
|
||||
- (NSArray *)_imageDataSources{
|
||||
return self.showNewDatasource;
|
||||
}
|
||||
|
||||
/** Get new data from the proxy method */
|
||||
- (NSArray *)_getImageDataSources{
|
||||
if (self.dataSource && [self.dataSource respondsToSelector:@selector(bannerViewImages:)]) {
|
||||
return [self.dataSource bannerViewImages:self];
|
||||
}
|
||||
return @[];
|
||||
}
|
||||
|
||||
/** Get new data from the proxy method */
|
||||
- (NSArray *)_titlesDataSources{
|
||||
if (self.dataSource && [self.dataSource respondsToSelector:@selector(bannerViewTitles:)]) {
|
||||
return [self.dataSource bannerViewTitles:self];
|
||||
}
|
||||
return @[];
|
||||
}
|
||||
|
||||
/** Footer Height */
|
||||
- (CGFloat)_bannerViewFooterHeight{
|
||||
if (self.dataSource && [self.dataSource respondsToSelector:@selector(bannerViewFooterViewHeight:)]) {
|
||||
return [self.dataSource bannerViewFooterViewHeight:self];
|
||||
}
|
||||
return BANNER_FOOTER_HEIGHT;
|
||||
}
|
||||
|
||||
/** reload 时控制尾巴的显示和消失 */
|
||||
- (void)_setFooterViewCanShow:(BOOL)showFooter{
|
||||
|
||||
if (showFooter) {
|
||||
self.bannerViewScrollDirection = BannerViewDirectionLeft;
|
||||
self.collectionView.contentInset = UIEdgeInsetsMake(0, 0, 0, -[self _bannerViewFooterHeight]);
|
||||
}else{
|
||||
self.collectionView.contentInset = UIEdgeInsetsZero;
|
||||
}
|
||||
|
||||
if (self.bannerViewScrollDirection == BannerViewDirectionLeft) {
|
||||
self.collectionView.alwaysBounceHorizontal = showFooter;
|
||||
}else {
|
||||
self.collectionView.accessibilityViewIsModal = showFooter;
|
||||
}
|
||||
}
|
||||
|
||||
/** Scroll the CollectionView to the specified location */
|
||||
- (void)_scrollBannerViewToSpecifiedPositionIndex:(NSInteger)targetIndex animated:(BOOL)animated{
|
||||
NSInteger itemCount = [self.collectionView numberOfItemsInSection:0];
|
||||
if (targetIndex < itemCount) {
|
||||
[self.collectionView scrollToItemAtIndexPath:[NSIndexPath indexPathForItem:targetIndex?:0 inSection:0] atScrollPosition:UICollectionViewScrollPositionNone animated:animated];
|
||||
}
|
||||
}
|
||||
|
||||
/** Save the current offset */
|
||||
- (void)_saveInitializationContentOffsetJudgeZero:(BOOL)judgeZero{
|
||||
|
||||
if (self.collectionView.width_bannerView == 0 || self.collectionView.height_bannerView == 0) { return; }
|
||||
if (judgeZero) {
|
||||
if (self.lastContentOffset == 0) {
|
||||
|
||||
if (self.flowLayout.scrollDirection == UICollectionViewScrollDirectionHorizontal) {
|
||||
self.lastContentOffset = self.collectionView.contentOffset.x;
|
||||
} else {
|
||||
self.lastContentOffset = self.collectionView.contentOffset.y;
|
||||
}
|
||||
}
|
||||
}else{
|
||||
if (self.flowLayout.scrollDirection == UICollectionViewScrollDirectionHorizontal) {
|
||||
self.lastContentOffset = self.collectionView.contentOffset.x;
|
||||
} else {
|
||||
self.lastContentOffset = self.collectionView.contentOffset.y;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Lazy
|
||||
- (UIImageView *)backgroundImageView{
|
||||
if (!_backgroundImageView) {
|
||||
_backgroundImageView = [[UIImageView alloc] initWithFrame:CGRectZero];
|
||||
_backgroundImageView.contentMode = self.bannerImageViewContentMode;
|
||||
_backgroundImageView.clipsToBounds = YES;
|
||||
[self insertSubview:_backgroundImageView belowSubview:self.collectionView];
|
||||
}
|
||||
return _backgroundImageView;
|
||||
}
|
||||
|
||||
- (UICollectionViewFlowLayout *)flowLayout{
|
||||
if (!_flowLayout) {
|
||||
_flowLayout = [[UICollectionViewFlowLayout alloc] init];
|
||||
_flowLayout.minimumLineSpacing = 0.0f;
|
||||
_flowLayout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
|
||||
}
|
||||
return _flowLayout;
|
||||
}
|
||||
|
||||
- (YJBannerViewCollectionView *)collectionView{
|
||||
if (!_collectionView) {
|
||||
_collectionView = [[YJBannerViewCollectionView alloc] initWithFrame:self.bounds collectionViewLayout:self.flowLayout];
|
||||
_collectionView.pagingEnabled = YES;
|
||||
_collectionView.showsHorizontalScrollIndicator = NO;
|
||||
_collectionView.showsVerticalScrollIndicator = NO;
|
||||
_collectionView.backgroundColor = [UIColor clearColor];
|
||||
|
||||
[_collectionView registerClass:[YJBannerViewCell class] forCellWithReuseIdentifier:bannerViewCellId];
|
||||
[_collectionView registerClass:[YJBannerViewFooter class] forSupplementaryViewOfKind:UICollectionElementKindSectionFooter withReuseIdentifier:bannerViewFooterId];
|
||||
|
||||
_collectionView.dataSource = self;
|
||||
_collectionView.delegate = self;
|
||||
_collectionView.scrollsToTop = NO;
|
||||
}
|
||||
return _collectionView;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
self.collectionView.delegate = nil;
|
||||
self.collectionView.dataSource = nil;
|
||||
}
|
||||
|
||||
@end
|
||||
Reference in New Issue
Block a user