小说绘上架版本

This commit is contained in:
xtfei2011
2021-02-07 11:24:08 +08:00
commit ee5c1c8b12
1762 changed files with 115892 additions and 0 deletions
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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

@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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