小说绘上架版本

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
@@ -0,0 +1,87 @@
//
// NSMutableAttributedString+TY.h
// TYAttributedLabelDemo
//
// Created by tanyang on 15/4/8.
// Copyright (c) 2015年 tanyang. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import <CoreText/CoreText.h>
@interface NSMutableAttributedString (TY)
/**
* 添加文本颜色属性
*
* @param color 文本颜色
*/
- (void)addAttributeTextColor:(UIColor*)color;
- (void)addAttributeTextColor:(UIColor*)color range:(NSRange)range;
/**
* 添加文本字体属性
*
* @param font 字体
*/
- (void)addAttributeFont:(UIFont *)font;
- (void)addAttributeFont:(UIFont *)font range:(NSRange)range;
/**
* 添加文本字符间隔
*
* @param characterSpacing 字符间隔
*/
- (void)addAttributeCharacterSpacing:(unichar)characterSpacing;
- (void)addAttributeCharacterSpacing:(unichar)characterSpacing range:(NSRange)range;
/**
* 添加下划线样式
*
* @param style 下划线 (单下划线 双 无)
* @param modifier 下划线样式 (点 线)
*/
- (void)addAttributeUnderlineStyle:(CTUnderlineStyle)style
modifier:(CTUnderlineStyleModifiers)modifier;
- (void)addAttributeUnderlineStyle:(CTUnderlineStyle)style
modifier:(CTUnderlineStyleModifiers)modifier
range:(NSRange)range;
/**
* 添加空心字
*
* @param strokeWidth 空心字边框宽
* @param strokeColor 空心字边框颜色
*/
- (void)addAttributeStrokeWidth:(unichar)strokeWidth
strokeColor:(UIColor *)strokeColor;
- (void)addAttributeStrokeWidth:(unichar)strokeWidth
strokeColor:(UIColor *)strokeColor
range:(NSRange)range;
/**
* 添加文本段落样式
*
* @param textAlignment 文本对齐样式
* @param linesSpacing 文本行间距
* @param paragraphSpacing 段落间距
* @param lineBreakMode 文本换行样式
*/
- (void)addAttributeAlignmentStyle:(CTTextAlignment)textAlignment
lineSpaceStyle:(CGFloat)linesSpacing
paragraphSpaceStyle:(CGFloat)paragraphSpacing
lineBreakStyle:(CTLineBreakMode)lineBreakMode;
- (void)addAttributeAlignmentStyle:(CTTextAlignment)textAlignment
lineSpaceStyle:(CGFloat)linesSpacing
paragraphSpaceStyle:(CGFloat)paragraphSpacing
lineBreakStyle:(CTLineBreakMode)lineBreakMode
range:(NSRange)range;
@end
@@ -0,0 +1,168 @@
//
// NSMutableAttributedString+TY.m
// TYAttributedLabelDemo
//
// Created by tanyang on 15/4/8.
// Copyright (c) 2015年 tanyang. All rights reserved.
//
#import "NSMutableAttributedString+TY.h"
@implementation NSMutableAttributedString (TY)
#pragma mark - 文本颜色属性
- (void)addAttributeTextColor:(UIColor*)color
{
[self addAttributeTextColor:color range:NSMakeRange(0, [self length])];
}
- (void)addAttributeTextColor:(UIColor*)color range:(NSRange)range
{
if (color.CGColor)
{
[self removeAttribute:(NSString *)kCTForegroundColorAttributeName range:range];
[self addAttribute:(NSString *)kCTForegroundColorAttributeName
value:(id)color.CGColor
range:range];
}
}
#pragma mark - 文本字体属性
- (void)addAttributeFont:(UIFont *)font
{
[self addAttributeFont:font range:NSMakeRange(0, [self length])];
}
- (void)addAttributeFont:(UIFont *)font range:(NSRange)range
{
if (font)
{
[self removeAttribute:(NSString*)kCTFontAttributeName range:range];
CTFontRef fontRef = CTFontCreateWithName((CFStringRef)@"SFUI-Regular", font.pointSize, nil);
if (nil != fontRef)
{
[self addAttribute:(NSString *)kCTFontAttributeName value:(__bridge id)fontRef range:range];
CFRelease(fontRef);
}
}
}
#pragma mark - 文本字符间隔属性
- (void)addAttributeCharacterSpacing:(unichar)characterSpacing
{
[self addAttributeCharacterSpacing:characterSpacing range:NSMakeRange(0, self.length)];
}
- (void)addAttributeCharacterSpacing:(unichar)characterSpacing range:(NSRange)range
{
[self removeAttribute:(id)kCTKernAttributeName range:range];
CFNumberRef num = CFNumberCreate(kCFAllocatorDefault,kCFNumberSInt16Type,&characterSpacing);
[self addAttribute:(id)kCTKernAttributeName value:(__bridge id)num range:range];
CFRelease(num);
}
#pragma mark - 文本下划线属性
- (void)addAttributeUnderlineStyle:(CTUnderlineStyle)style
modifier:(CTUnderlineStyleModifiers)modifier
{
[self addAttributeUnderlineStyle:style
modifier:modifier
range:NSMakeRange(0, self.length)];
}
- (void)addAttributeUnderlineStyle:(CTUnderlineStyle)style
modifier:(CTUnderlineStyleModifiers)modifier
range:(NSRange)range
{
[self removeAttribute:(NSString *)kCTUnderlineColorAttributeName range:range];
if (style != kCTUnderlineStyleNone) {
[self addAttribute:(NSString *)kCTUnderlineStyleAttributeName
value:[NSNumber numberWithInt:(style|modifier)]
range:range];
}
}
#pragma mark - 文本空心字及颜色
- (void)addAttributeStrokeWidth:(unichar)strokeWidth
strokeColor:(UIColor *)strokeColor
{
[self addAttributeStrokeWidth:strokeWidth strokeColor:strokeColor range:NSMakeRange(0, self.length)];
}
- (void)addAttributeStrokeWidth:(unichar)strokeWidth
strokeColor:(UIColor *)strokeColor
range:(NSRange)range
{
[self removeAttribute:(id)kCTStrokeWidthAttributeName range:range];
if (strokeWidth > 0) {
CFNumberRef num = CFNumberCreate(kCFAllocatorDefault,kCFNumberSInt16Type,&strokeWidth);
[self addAttribute:(id)kCTStrokeWidthAttributeName value:(__bridge id)num range:range];
CFRelease(num);
}
[self removeAttribute:(id)kCTStrokeColorAttributeName range:range];
if (strokeColor) {
[self addAttribute:(id)kCTStrokeColorAttributeName value:(id)strokeColor.CGColor range:range];
}
}
#pragma mark - 文本段落样式属性
- (void)addAttributeAlignmentStyle:(CTTextAlignment)textAlignment
lineSpaceStyle:(CGFloat)linesSpacing
paragraphSpaceStyle:(CGFloat)paragraphSpacing
lineBreakStyle:(CTLineBreakMode)lineBreakMode
{
[self addAttributeAlignmentStyle:textAlignment lineSpaceStyle:linesSpacing paragraphSpaceStyle:paragraphSpacing lineBreakStyle:lineBreakMode range:NSMakeRange(0, self.length)];
}
- (void)addAttributeAlignmentStyle:(CTTextAlignment)textAlignment
lineSpaceStyle:(CGFloat)linesSpacing
paragraphSpaceStyle:(CGFloat)paragraphSpacing
lineBreakStyle:(CTLineBreakMode)lineBreakMode
range:(NSRange)range
{
[self removeAttribute:(id)kCTParagraphStyleAttributeName range:range];
// 创建文本对齐方式
CTParagraphStyleSetting alignmentStyle;
alignmentStyle.spec = kCTParagraphStyleSpecifierAlignment;//指定为对齐属性
alignmentStyle.valueSize = sizeof(textAlignment);
alignmentStyle.value = &textAlignment;
// 创建文本行间距
CTParagraphStyleSetting lineSpaceStyle;
lineSpaceStyle.spec = kCTParagraphStyleSpecifierLineSpacingAdjustment;
lineSpaceStyle.valueSize = sizeof(linesSpacing);
lineSpaceStyle.value = &linesSpacing;
//段落间距
CTParagraphStyleSetting paragraphSpaceStyle;
paragraphSpaceStyle.spec = kCTParagraphStyleSpecifierParagraphSpacing;
paragraphSpaceStyle.value = &paragraphSpacing;
paragraphSpaceStyle.valueSize = sizeof(paragraphSpacing);
//换行模式
CTParagraphStyleSetting lineBreakStyle;
lineBreakStyle.spec = kCTParagraphStyleSpecifierLineBreakMode;
lineBreakStyle.value = &lineBreakMode;
lineBreakStyle.valueSize = sizeof(lineBreakMode);
// 创建样式数组
CTParagraphStyleSetting settings[] = {alignmentStyle ,lineSpaceStyle, paragraphSpaceStyle, lineBreakStyle};
CTParagraphStyleRef paragraphStyle = CTParagraphStyleCreate(settings, sizeof(settings) / sizeof(settings[0])); // 设置样式
// 设置段落属性
[self addAttribute:(id)kCTParagraphStyleAttributeName value:(id)CFBridgingRelease(paragraphStyle) range:range];
}
@end
@@ -0,0 +1,329 @@
//
// TYAttributedLabel.h
// TYAttributedLabelDemo
//
// Created by tanyang on 15/4/8.
// Copyright (c) 2015年 tanyang. All rights reserved.
// TYAttributedLabel v2.0 verson
#import <UIKit/UIKit.h>
#import "TYTextContainer.h"
typedef NS_ENUM(NSUInteger, TYVerticalAlignment) {
TYVerticalAlignmentTop,
TYVerticalAlignmentCenter,
TYVerticalAlignmentBottom,
};
@class TYAttributedLabel;
@protocol TYAttributedLabelDelegate <NSObject>
@optional
// 点击代理
- (void)attributedLabel:(TYAttributedLabel *)attributedLabel textStorageClicked:(id<TYTextStorageProtocol>)textStorage atPoint:(CGPoint)point;
// 长按代理 有多个状态 begin, changes, end 都会调用,所以需要判断状态
- (void)attributedLabel:(TYAttributedLabel *)attributedLabel textStorageLongPressed:(id<TYTextStorageProtocol>)textStorage onState:(UIGestureRecognizerState)state atPoint:(CGPoint)point;
// 长按非Container区域代理 有多个状态 begin, changes, end 都会调用,所以需要判断状态
- (void)attributedLabel:(TYAttributedLabel *)attributedLabel lableLongPressOnState:(UIGestureRecognizerState)state atPoint:(CGPoint)point;
@end
/**
* TYAttributedLabel 属性文本 支持图文混排显示,支持添加image和UIView,支持自定义排版
*/
@interface TYAttributedLabel : UIView
@property (nonatomic, assign) id<TYAttributedLabelDelegate> delegate;
@property (nonatomic, strong) NSString *text;
@property (nonatomic, strong) UIColor *textColor; // 文字颜色
@property (nonatomic, strong) UIFont *font; // 文字大小
@property (nonatomic,assign) NSInteger numberOfLines; //行数
@property (nonatomic, strong) UIColor *linkColor; //链接颜色
@property (nonatomic, strong) UIColor *highlightedLinkColor;//默认nil高亮链接颜色
@property (nonatomic, strong) UIColor *highlightedLinkBackgroundColor;//链接高亮背景颜色
@property (nonatomic, assign) CGFloat highlightedLinkBackgroundRadius; // 高亮背景圆角
@property (nonatomic, assign) unichar strokeWidth; // 空心字边框宽
@property (nonatomic, strong) UIColor *strokeColor; // 空心字边框颜色
@property (nonatomic, assign) unichar characterSpacing; // 字距
@property (nonatomic, assign) CGFloat linesSpacing; // 行距
@property (nonatomic, assign) CGFloat paragraphSpacing; // 段落间距
@property (nonatomic, assign) CTTextAlignment textAlignment; // 文本对齐方式 kCTTextAlignmentLeft
@property (nonatomic, assign) CTLineBreakMode lineBreakMode; // 换行模式 kCTLineBreakByCharWrapping
@property (nonatomic, assign) TYVerticalAlignment verticalAlignment; // 垂直对齐方式 默认是向上对齐
@property (nonatomic, strong) TYTextContainer *textContainer;
@property (nonatomic, assign) CGFloat preferredMaxLayoutWidth; // Autolayout
@property (nonatomic, assign) BOOL isWidthToFit; // 宽度自适应 默认NO
/**
* 获取普通文本内容
*/
- (NSString *)text;
/**
* 获取属性文本内容
*/
- (NSAttributedString *)attributedText;
/**
* 设置普通初始化文本内容
*
* @param text 普通文本内容
*/
- (void)setText:(NSString *)text;
/**
* 设置属性初始化文本内容
*
* @param attributedText 属性文本内容
*/
- (void)setAttributedText: (NSAttributedString *)attributedText;
/**
* 添加 textStorage (自定义显示内容)
*
* @param textStorage 自定义
*/
- (void)addTextStorage:(id<TYTextStorageProtocol>)textStorage;
/**
* 添加 textRun数组 (自定义显示内容)
*
*/
- (void)addTextStorageArray:(NSArray *)textStorageArray;
/**
* 调用可以自动计算frame大小(请确定label之前设置了宽度)
*/
- (void)sizeToFit;
/**
* 获取文本真正的高度
*/
- (int)getHeightWithWidth:(CGFloat)width;
/**
* 获取文本真正的size
*/
- (CGSize)getSizeWithWidth:(CGFloat)width;
/**
* 设置文本位置大小 (自动计算高度,根据宽度)
*/
- (void)setFrameWithOrign:(CGPoint)orign Width:(CGFloat)width;
@end
#pragma mark - 扩展追加内容 (Append)
// 追加内容 (添加在AttributedString最后)
@interface TYAttributedLabel (AppendAttributedString)
/**
* 追加(添加到最后) 普通文本
*
* @param text 普通文本
*/
- (void)appendText:(NSString *)text;
/**
* 追加(添加到最后) 属性文本
*
* @param attributedText 属性文本
*/
- (void)appendTextAttributedString: (NSAttributedString *)attributedText;
/**
* 追加(添加到最后) textStorage (自定义显示内容)
*
* @param textStorage 自定义Storage(自定义显示内容)
*/
- (void)appendTextStorage:(id<TYAppendTextStorageProtocol>)textStorage;
/**
* 追加(添加到最后) textStorage 数组
*
* @param textStorageArray 自定义run数组(需遵循协议TYAppendTextStorageProtocol,否则不会添加)
*/
- (void)appendTextStorageArray:(NSArray *)textStorageArray;
@end
#pragma mark - 扩展支持Link链接
@interface TYAttributedLabel (Link)
/**
* 添加 链接LinkTextStorage
*/
- (void)addLinkWithLinkData:(id)linkData range:(NSRange )range;
/**
* 添加 链接LinkTextStorage
*
* @param linkData 链接携带的数据
* @param linkColor 链接颜色
* @param range 范围
*/
- (void)addLinkWithLinkData:(id)linkData linkColor:(UIColor *)linkColor range:(NSRange )range;
- (void)addLinkWithLinkData:(id)linkData linkColor:(UIColor *)linkColor underLineStyle:(CTUnderlineStyle)underLineStyle range:(NSRange )range;
/**
* 追加 链接LinkTextStorage
*/
- (void)appendLinkWithText:(NSString *)linkText linkFont:(UIFont *)linkFont linkData:(id)linkData;
/**
* 追加 链接LinkTextStorage
*
* @param linkText 链接文本
* @param linkData 链接携带的数据
*/
- (void)appendLinkWithText:(NSString *)linkText linkFont:(UIFont *)linkFont linkColor:(UIColor *)linkColor linkData:(id)linkData;
- (void)appendLinkWithText:(NSString *)linkText linkFont:(UIFont *)linkFont linkColor:(UIColor *)linkColor underLineStyle:(CTUnderlineStyle)underLineStyle linkData:(id)linkData;
@end
#pragma mark - 扩展支持UIImage
@interface TYAttributedLabel (UIImage)
#pragma mark - addImageStorage
/**
* 添加 imageStorage image数据
*/
- (void)addImage:(UIImage *)image range:(NSRange)range;
/**
* 添加 imageStorage image数据
*/
- (void)addImage:(UIImage *)image range:(NSRange)range size:(CGSize)size;
/**
* 添加 imageStorage image数据
*
* @param image image
* @param range 所在文本位置
* @param size 图片大小
* @param alignment 图片对齐方式
*/
- (void)addImage:(UIImage *)image range:(NSRange)range
size:(CGSize)size
alignment: (TYDrawAlignment)alignment;
/**
* 添加 imageStorage image数据
*/
- (void)addImageWithName:(NSString *)imageName range:(NSRange)range;
/**
* 添加 imageStorage image数据
*/
- (void)addImageWithName:(NSString *)imageName
range:(NSRange)range
size:(CGSize)size;
/**
* 添加 imageStorage image数据
*
* @param imageName image名
* @param range 所在文本位置
* @param size 图片大小
* @param alignment 图片对齐方式
*/
- (void)addImageWithName:(NSString *)imageName
range:(NSRange)range
size:(CGSize)size
alignment:(TYDrawAlignment)alignment;
#pragma mark - appendImageStorage
/**
* 追加 imageStorage image数据
*/
- (void)appendImage:(UIImage *)image;
/**
* 追加 imageStorage image数据
*/
- (void)appendImage:(UIImage *)image
size:(CGSize)size;
/**
* 追加 imageStorage image数据
*
* @param image image
* @param size 图片大小
* @param alignment 图片对齐
*/
- (void)appendImage:(UIImage *)image
size:(CGSize)size
alignment:(TYDrawAlignment)alignment;
/**
* 追加 imageStorage image数据
*/
- (void)appendImageWithName:(NSString *)imageName;
/**
* 追加 imageStorage image数据
*/
- (void)appendImageWithName:(NSString *)imageName size:(CGSize)size;
/**
* 追加 imageStorage image数据
*
* @param imageName imageName
* @param size 图片大小
* @param alignment 图片对齐
*/
- (void)appendImageWithName:(NSString *)imageName
size:(CGSize)size
alignment:(TYDrawAlignment)alignment;
@end
#pragma mark - 扩展支持UIView
@interface TYAttributedLabel (UIView)
/**
* 添加 viewStorage (添加 UI控件 需要设置frame)
*/
- (void)addView:(UIView *)view range:(NSRange)range;
/**
* 添加 viewStorage (添加 UI控件 需要设置frame)
*
* @param view UIView (UI控件)
* @param range 所在文本位置
* @param alignment view对齐方式
*/
- (void)addView:(UIView *)view
range:(NSRange)range
alignment:(TYDrawAlignment)alignment;
/**
* 追加 viewStorage (添加 UI控件 需要设置frame)
*/
- (void)appendView:(UIView *)view;
/**
* 追加 viewStorage (添加 UI控件 需要设置frame)
*
* @param view UIView (UI控件)
* @param alignment view对齐
*/
- (void)appendView:(UIView *)view alignment:(TYDrawAlignment)alignment;
@end
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,40 @@
//
// TYDrawStorage.h
// TYAttributedLabelDemo
//
// Created by tanyang on 15/4/8.
// Copyright (c) 2015年 tanyang. All rights reserved.
//
#import "TYTextStorageProtocol.h"
@interface TYDrawStorage : NSObject<TYDrawStorageProtocol>
@property (nonatomic, assign) NSInteger tag; // 标识
@property (nonatomic, assign) NSRange range; // 文本范围
@property (nonatomic, assign) NSRange realRange;
@property (nonatomic, assign) UIEdgeInsets margin; // 图片四周间距
@property (nonatomic, assign) CGSize size; // 绘画物大小
@property (nonatomic, assign) TYDrawAlignment drawAlignment; // 对齐方式
/**
* 获取绘画区域上行高度(默认实现)
*/
- (CGFloat)getDrawRunAscentHeight;
/**
* 获取绘画区域下行高度 默认实现为0(一般不需要改写)
*/
- (CGFloat)getDrawRunDescentHeight;
/**
* 获取绘画区域宽度(默认实现)
*/
- (CGFloat)getDrawRunWidth;
/**
* 释放内存 (一般不需要 已注释 需要在打开)
*/
//- (void)DrawRunDealloc;
@end
@@ -0,0 +1,217 @@
//
// TYDrawStorage.m
// TYAttributedLabelDemo
//
// Created by tanyang on 15/4/8.
// Copyright (c) 2015年 tanyang. All rights reserved.
//
#import "TYDrawStorage.h"
#import <CoreText/CoreText.h>
@interface TYDrawStorage (){
CGFloat _fontAscent;
CGFloat _fontDescent;
NSRange _fixRange;
}
@end
@implementation TYDrawStorage
#pragma mark - protocol
- (void)currentReplacedStringNum:(NSInteger)replacedStringNum
{
_fixRange = [self fixRange:_range replaceStringNum:replacedStringNum];
}
- (void)setTextfontAscent:(CGFloat)ascent descent:(CGFloat)descent;
{
_fontAscent = ascent;
_fontDescent = -descent;
}
- (void)addTextStorageWithAttributedString:(NSMutableAttributedString *)attributedString
{
NSRange range = _fixRange;
if (range.location == NSNotFound) {
return;
}else {
// 用空白替换
[attributedString replaceCharactersInRange:range withString:[self spaceReplaceString]];
// 修正range
range = NSMakeRange(range.location, 1);
_realRange = range;
}
// 设置合适的对齐
[self setAppropriateAlignment];
// 添加文本属性和runDelegate
[self addRunDelegateWithAttributedString:attributedString range:range];
}
- (NSAttributedString *)appendTextStorageAttributedString
{
// 创建空字符属性文本
NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:[self spaceReplaceString]];
// 修正range
_range = NSMakeRange(0, 1);
// 设置合适的对齐
[self setAppropriateAlignment];
// 添加文本属性和runDelegate
[self addRunDelegateWithAttributedString:attributedString range:_range];
return attributedString;
}
- (void)drawStorageWithRect:(CGRect)rect
{
}
#pragma mark - public
- (CGFloat)getDrawRunAscentHeight
{
CGFloat ascent = 0;
CGFloat height = self.size.height+_margin.bottom+_margin.top;
switch (_drawAlignment)
{
case TYDrawAlignmentTop:
ascent = height - _fontDescent;
break;
case TYDrawAlignmentCenter:
{
CGFloat baseLine = (_fontAscent + _fontDescent) / 2 - _fontDescent;
ascent = height / 2 + baseLine;
break;
}
case TYDrawAlignmentBottom:
ascent = _fontAscent;
break;
default:
break;
}
return ascent;
}
- (CGFloat)getDrawRunWidth
{
return self.size.width+_margin.left+_margin.right;
}
- (CGFloat)getDrawRunDescentHeight
{
CGFloat descent = 0;
CGFloat height = self.size.height+_margin.bottom+_margin.top;
switch (_drawAlignment)
{
case TYDrawAlignmentTop:
descent = _fontDescent;
break;
case TYDrawAlignmentCenter:
{
CGFloat baseLine = (_fontAscent + _fontDescent) / 2 - _fontDescent;
descent = height / 2 - baseLine;
break;
}
case TYDrawAlignmentBottom:
descent = height - _fontAscent;
break;
default:
break;
}
return descent;
}
- (void)DrawRunDealloc
{
}
#pragma mark - private
- (NSString *)spaceReplaceString
{
// 替换字符
unichar objectReplacementChar = 0xFFFC;
NSString *objectReplacementString = [NSString stringWithCharacters:&objectReplacementChar length:1];
return objectReplacementString;
}
- (void)setAppropriateAlignment
{
// 判断size 大小 小于 _fontAscent 把对齐设为中心 更美观
if (_size.height <= _fontAscent + _fontDescent) {
_drawAlignment = TYDrawAlignmentCenter;
}
}
- (NSRange)fixRange:(NSRange)range replaceStringNum:(NSInteger)replaceStringNum
{
NSRange fixRange = range;
if (range.length <= 1 || replaceStringNum < 0)
return fixRange;
NSInteger location = range.location - replaceStringNum;
NSInteger length = range.length - replaceStringNum;
if (location < 0 && length > 0) {
fixRange = NSMakeRange(range.location, length);
}else if (location < 0 && length <= 0){
fixRange = NSMakeRange(NSNotFound, 0);
}else {
fixRange = NSMakeRange(range.location - replaceStringNum, range.length);
}
return fixRange;
}
// 添加文本属性和runDelegate
- (void)addRunDelegateWithAttributedString:(NSMutableAttributedString *)attributedString range:(NSRange)range
{
// 添加文本属性和runDelegate
[attributedString addAttribute:kTYTextRunAttributedName value:self range:range];
//为图片设置CTRunDelegate,delegate决定留给显示内容的空间大小
CTRunDelegateCallbacks runCallbacks;
runCallbacks.version = kCTRunDelegateVersion1;
runCallbacks.dealloc = TYTextRunDelegateDeallocCallback;
runCallbacks.getAscent = TYTextRunDelegateGetAscentCallback;
runCallbacks.getDescent = TYTextRunDelegateGetDescentCallback;
runCallbacks.getWidth = TYTextRunDelegateGetWidthCallback;
CTRunDelegateRef runDelegate = CTRunDelegateCreate(&runCallbacks, (__bridge void *)(self));
[attributedString addAttribute:(__bridge_transfer NSString *)kCTRunDelegateAttributeName value:(__bridge id)runDelegate range:range];
CFRelease(runDelegate);
}
//CTRun的回调,销毁内存的回调
void TYTextRunDelegateDeallocCallback( void* refCon ){
//TYDrawRun *textRun = (__bridge TYDrawRun *)refCon;
//[textRun DrawRunDealloc];
}
//CTRun的回调,获取高度
CGFloat TYTextRunDelegateGetAscentCallback( void *refCon ){
TYDrawStorage *drawStorage = (__bridge TYDrawStorage *)refCon;
return [drawStorage getDrawRunAscentHeight];
}
CGFloat TYTextRunDelegateGetDescentCallback(void *refCon){
TYDrawStorage *drawStorage = (__bridge TYDrawStorage *)refCon;
return [drawStorage getDrawRunDescentHeight];
}
//CTRun的回调,获取宽度
CGFloat TYTextRunDelegateGetWidthCallback(void *refCon){
TYDrawStorage *drawStorage = (__bridge TYDrawStorage *)refCon;
return [drawStorage getDrawRunWidth];
}
@end
@@ -0,0 +1,44 @@
//
// TYImageCache.h
// TYImageCache
//
// Created by tanyang on 25/08/15.
// Copyright (c) 2015 tanyang. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface TYImageCache : NSObject
@property (nonatomic, strong) NSString *localDirectory;
// 单例cache
+ (instancetype)cache;
// 清除cache
- (void)clearCache;
// 是否在本地找到图片,是否需要缩略图
- (void)imageForURL:(NSString *)imageURL needThumImage:(BOOL)needThumImage found:(void(^)(UIImage* image))found notFound:(void(^)(void))notFound;
// 是否在本地找到图片
- (void) imageForURL:(NSString *)imageURL found:(void(^)(UIImage* image))found notFound:(void(^)(void))notFound;
// 图片是否缓存
- (BOOL)imageIsCacheForURL:(NSString *)imageURL;
// 同步下载保存image
- (BOOL)saveImageFromURL:(NSString *)imageName thumbImageSize:(CGSize)thumbImageSize;
// 保存图片
- (BOOL)saveImageFromURL:(NSString *)imageName data:(NSData *)imageData;
// 保存image和缩略图
- (BOOL)saveImageFromURL:(NSString *)imageName thumbImageSize:(CGSize)thumbImageSize data:(NSData *)imageData;
// 异步下载保存image
- (void)saveAsyncImageFromURL:(NSString *)imageURL thumbImageSize:(CGSize)thumbImageSize completion:(void(^)(BOOL isCache))completionBlock;
// 异步下载保存image数组
- (void)saveAsyncImagesFromURLArray:(NSArray *)imageURLArray thumbImageSize:(CGSize)thumbImageSize completion:(void(^)(BOOL isCache))completionBlock;
@end
@@ -0,0 +1,295 @@
//
// DBImageViewCache.m
// DBImageView
//
// Created by iBo on 25/08/14.
// Copyright (c) 2014 Daniele Bogo. All rights reserved.
//
#import "TYImageCache.h"
#import <CommonCrypto/CommonDigest.h>
@interface TYImageCache (){
NSFileManager *_fileManager;
}
@end
@implementation TYImageCache
static TYImageCache *_instance;
+ (id)allocWithZone:(NSZone *)zone
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_instance = [super allocWithZone:zone];
});
return _instance;
}
+ (instancetype)cache
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_instance = [[self alloc] init];
});
return _instance;
}
#pragma mark - md5加密
+ (NSString *) md5:(NSString *)str
{
const char *cStr = [str UTF8String];
if (cStr == NULL) {
cStr = "";
}
unsigned char result[CC_MD5_DIGEST_LENGTH];
CC_MD5( cStr, (CC_LONG)strlen(cStr), result );
return [NSString stringWithFormat:
@"%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X",
result[0], result[1], result[2], result[3],
result[4], result[5], result[6], result[7],
result[8], result[9], result[10], result[11],
result[12], result[13], result[14], result[15]
];
}
- (id) init
{
self= [super init];
if ( self ) {
_fileManager = [NSFileManager new];
[self createLocalDirectory];
}
return self;
}
#pragma mark - 创建缓存图片文件目录
- (void) createLocalDirectory
{
if ( ![_fileManager fileExistsAtPath:self.localDirectory] ) {
NSError *error;
if ( ![[NSFileManager defaultManager] createDirectoryAtPath:self.localDirectory withIntermediateDirectories:YES attributes:nil error:&error] ) {
NSLog(@"[%@] ERROR: attempting to write create MyFolder directory", [self class]);
NSAssert( FALSE, @"Failed to create directory maybe out of disk space?");
}
}
}
#pragma mark - 存放image的文件夹路径
- (NSString *) localDirectory
{
if (_localDirectory == nil) {
_localDirectory = [NSString stringWithFormat:@"%@/ImageCache", NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES)[0]];
}
return _localDirectory;
}
#pragma mark - image名字MD5加密后的路径
- (NSString *) pathOnDiskForName:(NSString *)imageName
{
return [self.localDirectory stringByAppendingPathComponent:[TYImageCache md5:imageName]];
}
#pragma mark - 保存image和缩略图
- (BOOL) saveImageFromURL:(NSString *)imageName thumbImageSize:(CGSize)thumbImageSize data:(NSData *)imageData
{
// 转换gif 到 image
UIImage *image = [UIImage imageWithData:imageData];
BOOL succeed = [self saveImageFromName:imageName image:image];
if (!CGSizeEqualToSize(thumbImageSize,CGSizeZero)) {
// 保存thumbImage
image = [self scaleImage:image ToSize:thumbImageSize];
succeed = [self saveImageFromName:[NSString stringWithFormat:@"Thumb%@",imageName] image:image];
}
return succeed;
}
- (BOOL)saveImageFromURL:(NSString *)imageName data:(NSData *)imageData
{
return [self saveImageFromURL:imageName thumbImageSize:CGSizeZero data:imageData];
}
#pragma mark - 保存image根据UImage
- (BOOL)saveImageFromName:(NSString *)imageName image:(UIImage *)image
{
if (!image) {
return NO;
}
if ([[imageName lowercaseString] hasSuffix:@".png"] || [[imageName lowercaseString] hasSuffix:@".bmp"]) {
// png图片
[UIImagePNGRepresentation(image) writeToFile:[self pathOnDiskForName:imageName] options:NSAtomicWrite error:nil];
return YES;
} else if ([[imageName lowercaseString] hasSuffix:@".jpg"] || [[imageName lowercaseString] hasSuffix:@".jpeg"] || [[imageName lowercaseString] hasSuffix:@".gif"])
{
//jpg图片
[UIImageJPEGRepresentation(image, 1.0) writeToFile:[self pathOnDiskForName:imageName] options:NSAtomicWrite error:nil];
return YES;
} else {
// 未知图片类型
NSLog(@"文件后缀名未知! CTImageCache ");
return NO;
}
}
#pragma mark - 从网络下载缓存image
// 同步下载保存image
- (BOOL)saveImageFromURL:(NSString *)imageName thumbImageSize:(CGSize)thumbImageSize
{
// 从网络上加载图片
NSData * data = [NSData dataWithContentsOfURL:[NSURL URLWithString:imageName]];
if (!data) {
return NO;
}
// 缓存图片
return [self saveImageFromURL:imageName thumbImageSize:thumbImageSize data:data];
}
// 异步下载保存image
- (void)saveAsyncImageFromURL:(NSString *)imageURL thumbImageSize:(CGSize)thumbImageSize completion:(void(^)(BOOL isCache))completionBlock
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 异步操作
// 从网络上 缓存图片
BOOL isCached = [self saveImageFromURL:imageURL thumbImageSize:thumbImageSize];
if (!completionBlock) {
return ;
}
dispatch_async(dispatch_get_main_queue(), ^{
// 主线程更新
completionBlock(isCached);
});
});
}
- (void)saveAsyncImagesFromURLArray:(NSArray *)imageURLArray thumbImageSize:(CGSize)thumbImageSize completion:(void (^)(BOOL))completionBlock
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 异步操作
// 从网络上 缓存图片
__block BOOL isCached = NO;
[imageURLArray enumerateObjectsUsingBlock:^(NSString *imageURL, NSUInteger idx, BOOL *stop) {
isCached = [self saveImageFromURL:imageURL thumbImageSize:thumbImageSize];
}];
if (!completionBlock) {
return ;
}
dispatch_async(dispatch_get_main_queue(), ^{
// 主线程更新
completionBlock(isCached);
});
});
}
// 同步获取image
- (UIImage *)imageForURL:(NSString *)imageURL
{
NSString *path = [self pathOnDiskForName:imageURL];
return [[UIImage alloc] initWithContentsOfFile:path];
}
#pragma mark - image是否存在
- (void) imageForURL:(NSString *)imageURL found:(void(^)(UIImage* image))found notFound:(void(^)(void))notFound
{
[self imageForURL:imageURL needThumImage:NO found:found notFound:notFound];
}
#pragma mark - image是否存在,是否需要缩略图,如果知道返回缩略图 否则返回原图
- (void) imageForURL:(NSString *)imageURL needThumImage:(BOOL)needThumImage found:(void(^)(UIImage* image))found notFound:(void(^)(void))notFound
{
if ( !imageURL ) {
return;
}
NSString *imageName = imageURL;
if (needThumImage ) {
imageName = [NSString stringWithFormat:@"Thumb%@",imageURL];
}
// 图片路径
UIImage* image = [self imageForURL:imageName];
if (!image && needThumImage) {
image = [self imageForURL:imageURL];
}
if (image) {
found(image);
}else {
notFound();
}
}
#pragma mark - 是否缓存图片
- (BOOL) imageIsCacheForURL:(NSString *)imageURL {
return [_fileManager fileExistsAtPath:[self pathOnDiskForName:imageURL]];
}
#pragma mark - 清除内存
- (void) clearCache
{
NSError *error;
[_fileManager removeItemAtPath:self.localDirectory error:&error];
if ( ![_fileManager createDirectoryAtPath:self.localDirectory withIntermediateDirectories:NO attributes:nil error:&error] )
return;
}
#pragma mark - scale image
// 返回适应到targetSize的合适图片image
- (UIImage *)scaleImage:(UIImage *)sourceImage ToSize:(CGSize)targetSize
{
CGFloat width = sourceImage.size.width;
CGFloat height = sourceImage.size.height;
CGFloat targetWidth = targetSize.width;
CGFloat targetHeight = targetSize.height;
CGFloat scaleFactor = 0.0;
CGFloat scaledWidth = targetWidth;
CGFloat scaledHeight = targetHeight;
CGPoint thumbnailPoint = CGPointMake(0.0,0.0);
if (CGSizeEqualToSize(sourceImage.size, targetSize) == NO) {
CGFloat widthFactor = targetWidth / width;
CGFloat heightFactor = targetHeight / height;
if (widthFactor < heightFactor)
scaleFactor = widthFactor;
else
scaleFactor = heightFactor;
scaledWidth = width * scaleFactor;
scaledHeight = height * scaleFactor;
// center the image
if (widthFactor < heightFactor) {
thumbnailPoint.y = (targetHeight - scaledHeight) * 0.5;
} else if (widthFactor > heightFactor) {
thumbnailPoint.x = (targetWidth - scaledWidth) * 0.5;
}
}
// this is actually the interesting part:
UIGraphicsBeginImageContext(targetSize);
CGRect thumbnailRect = CGRectZero;
thumbnailRect.origin = thumbnailPoint;
thumbnailRect.size.width = scaledWidth;
thumbnailRect.size.height = scaledHeight;
[sourceImage drawInRect:thumbnailRect];
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
if(newImage == nil)
NSLog(@"could not scale image");
return newImage ;
}
@end
@@ -0,0 +1,31 @@
//
// TYDrawImageStorage.h
// TYAttributedLabelDemo
//
// Created by tanyang on 15/4/8.
// Copyright (c) 2015年 tanyang. All rights reserved.
//
#import "TYDrawStorage.h"
typedef enum : NSUInteger {
TYImageAlignmentCenter, // 图片居中
TYImageAlignmentLeft, // 图片左对齐
TYImageAlignmentRight, // 图片右对齐
TYImageAlignmentFill // 图片拉伸填充
} TYImageAlignment;
@interface TYImageStorage : TYDrawStorage<TYViewStorageProtocol>
@property (nonatomic, strong) UIImage *image;
@property (nonatomic, strong) NSString *imageName;
@property (nonatomic, strong) NSURL *imageURL;
@property (nonatomic, strong) NSString *placeholdImageName;
@property (nonatomic, assign) TYImageAlignment imageAlignment; // default center
@property (nonatomic, assign) BOOL cacheImageOnMemory; // default NO ,if YES can improve performancebut increase memory
@end
@@ -0,0 +1,120 @@
//
// TYDrawImageStorage.m
// TYAttributedLabelDemo
//
// Created by tanyang on 15/4/8.
// Copyright (c) 2015年 tanyang. All rights reserved.
//
#import "TYImageStorage.h"
#import "TYImageCache.h"
@interface TYImageStorage ()
@property (nonatomic, weak) UIView *ownerView;
@property (nonatomic, assign) BOOL isNeedUpdateFrame;
@end
@implementation TYImageStorage
- (instancetype)init
{
if (self = [super init]) {
_cacheImageOnMemory = NO;
}
return self;
}
#pragma mark - protocol
- (void)setOwnerView:(UIView *)ownerView
{
_ownerView = ownerView;
if (!ownerView || !_imageURL) {
return;
}
if ([_imageURL isKindOfClass:[NSURL class]]
&& ![[TYImageCache cache] imageIsCacheForURL:_imageURL.absoluteString]) {
[[TYImageCache cache]saveAsyncImageFromURL:_imageURL.absoluteString thumbImageSize:self.size completion:^(BOOL isCache) {
if (self.isNeedUpdateFrame) {
if (ownerView && isCache) {
[ownerView setNeedsDisplay];
}
self.isNeedUpdateFrame = NO;
}
}];
}
}
- (void)drawStorageWithRect:(CGRect)rect
{
__block UIImage *image = nil;
if (_image) {
// 本地图片名
image = _image;
}else if (_imageName){
// 图片网址
image = [UIImage imageNamed:_imageName];
if (_cacheImageOnMemory) {
_image = image;
}
} else if (_imageURL){
// 图片数据
[[TYImageCache cache] imageForURL:_imageURL.absoluteString needThumImage:NO found:^(UIImage *loaceImage) {
image = loaceImage;
if (self.cacheImageOnMemory) {
self.image = image;
}
} notFound:^{
image = self.placeholdImageName ? [UIImage imageNamed:self.placeholdImageName] : nil;
self.isNeedUpdateFrame = YES;
}];
}
if (image) {
CGRect fitRect = [self rectFitOriginSize:image.size byRect:rect];
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextDrawImage(context, fitRect, image.CGImage);
}
}
- (CGRect)rectFitOriginSize:(CGSize)size byRect:(CGRect)byRect{
if (_imageAlignment == TYImageAlignmentFill) {
return byRect;
}
CGRect scaleRect = byRect;
CGFloat targetWidth = byRect.size.width;
CGFloat targetHeight = byRect.size.height;
CGFloat widthFactor = targetWidth / size.width;
CGFloat heightFactor = targetHeight / size.height;
CGFloat scaleFactor = MIN(widthFactor, heightFactor);
CGFloat scaledWidth = size.width * scaleFactor;
CGFloat scaledHeight = size.height * scaleFactor;
scaleRect.size = CGSizeMake(scaledWidth, scaledHeight);
// center the image
if (widthFactor < heightFactor) {
scaleRect.origin.y += (targetHeight - scaledHeight) * 0.5;
} else if (widthFactor > heightFactor) {
switch (_imageAlignment) {
case TYImageAlignmentCenter:
scaleRect.origin.x += (targetWidth - scaledWidth) * 0.5;
break;
case TYImageAlignmentRight:
scaleRect.origin.x += (targetWidth - scaledWidth);
default:
break;
}
}
return scaleRect;
}
// override
- (void)didNotDrawRun
{
}
@end
@@ -0,0 +1,20 @@
//
// TYLinkTextStorage.h
// TYAttributedLabelDemo
//
// Created by tanyang on 15/4/8.
// Copyright (c) 2015年 tanyang. All rights reserved.
//
#import "TYTextStorage.h"
@interface TYLinkTextStorage : TYTextStorage<TYLinkStorageProtocol>
// textColor 链接颜色 如未设置就是TYAttributedLabel的linkColor
// TYAttributedLabel的 highlightedLinkBackgroundColor 高亮背景颜色
// underLineStyle 下划线样式(无,单 双) 默认单
// modifier 下划线样式 (点 线)默认线
@property (nonatomic, strong) id linkData; // 链接携带的数据
@end
@@ -0,0 +1,32 @@
//
// TYLinkTextStorage.m
// TYAttributedLabelDemo
//
// Created by tanyang on 15/4/8.
// Copyright (c) 2015年 tanyang. All rights reserved.
//
#import "TYLinkTextStorage.h"
@implementation TYLinkTextStorage
- (instancetype)init
{
if (self = [super init]) {
self.underLineStyle = kCTUnderlineStyleSingle;
self.modifier = kCTUnderlinePatternSolid;
}
return self;
}
#pragma mark - protocol
- (void)addTextStorageWithAttributedString:(NSMutableAttributedString *)attributedString
{
[super addTextStorageWithAttributedString:attributedString];
[attributedString addAttribute:kTYTextRunAttributedName value:self range:self.range];
self.text = [attributedString.string substringWithRange:self.range];
}
@end
@@ -0,0 +1,197 @@
//
// TYTextContainer+Extended.m
// TYAttributedLabelDemo
//
// Created by tanyang on 15/6/7.
// Copyright (c) 2015年 tanyang. All rights reserved.
//
#import "TYTextContainer.h"
@implementation TYTextContainer (Link)
#pragma mark - addLink
- (void)addLinkWithLinkData:(id)linkData range:(NSRange)range
{
[self addLinkWithLinkData:linkData linkColor:nil range:range];
}
- (void)addLinkWithLinkData:(id)linkData linkColor:(UIColor *)linkColor range:(NSRange )range;
{
[self addLinkWithLinkData:linkData linkColor:linkColor underLineStyle:kCTUnderlineStyleSingle range:range];
}
- (void)addLinkWithLinkData:(id)linkData linkColor:(UIColor *)linkColor underLineStyle:(CTUnderlineStyle)underLineStyle range:(NSRange )range
{
TYLinkTextStorage *linkTextStorage = [[TYLinkTextStorage alloc] init];
linkTextStorage.range = range;
linkTextStorage.textColor = linkColor;
linkTextStorage.linkData = linkData;
linkTextStorage.underLineStyle = underLineStyle;
[self addTextStorage:linkTextStorage];
}
#pragma mark - appendLink
- (void)appendLinkWithText:(NSString *)linkText linkFont:(UIFont *)linkFont linkData:(id)linkData
{
[self appendLinkWithText:linkText linkFont:linkFont linkColor:nil linkData:linkData];
}
- (void)appendLinkWithText:(NSString *)linkText linkFont:(UIFont *)linkFont linkColor:(UIColor *)linkColor linkData:(id)linkData
{
[self appendLinkWithText:linkText linkFont:linkFont linkColor:linkColor underLineStyle:kCTUnderlineStyleSingle linkData:linkData];
}
- (void)appendLinkWithText:(NSString *)linkText linkFont:(UIFont *)linkFont linkColor:(UIColor *)linkColor underLineStyle:(CTUnderlineStyle)underLineStyle linkData:(id)linkData
{
TYLinkTextStorage *linkTextStorage = [[TYLinkTextStorage alloc] init];
linkTextStorage.text = linkText;
linkTextStorage.font = linkFont;
linkTextStorage.textColor = linkColor;
linkTextStorage.linkData = linkData;
linkTextStorage.underLineStyle = underLineStyle;
[self appendTextStorage:linkTextStorage];
}
@end
@implementation TYTextContainer (UIImage)
#pragma mark addImage
- (void)addImageContent:(id)imageContent range:(NSRange)range size:(CGSize)size alignment:(TYDrawAlignment)alignment
{
TYImageStorage *imageStorage = [[TYImageStorage alloc] init];
if ([imageContent isKindOfClass:[UIImage class]]) {
imageStorage.image = imageContent;
}else if ([imageContent isKindOfClass:[NSString class]]){
imageStorage.imageName = imageContent;
} else {
return;
}
imageStorage.drawAlignment = alignment;
imageStorage.range = range;
imageStorage.size = size;
[self addTextStorage:imageStorage];
}
- (void)addImage:(UIImage *)image range:(NSRange)range size:(CGSize)size alignment:(TYDrawAlignment)alignment
{
[self addImageContent:image range:range size:size alignment:alignment];
}
- (void)addImage:(UIImage *)image range:(NSRange)range size:(CGSize)size
{
[self addImage:image range:range size:size alignment:TYDrawAlignmentTop];
}
- (void)addImage:(UIImage *)image range:(NSRange)range
{
[self addImage:image range:range size:image.size];
}
- (void)addImageWithName:(NSString *)imageName range:(NSRange)range size:(CGSize)size alignment:(TYDrawAlignment)alignment
{
[self addImageContent:imageName range:range size:size alignment:alignment];
}
- (void)addImageWithName:(NSString *)imageName range:(NSRange)range size:(CGSize)size
{
[self addImageWithName:imageName range:range size:size alignment:TYDrawAlignmentTop];
}
- (void)addImageWithName:(NSString *)imageName range:(NSRange)range
{
[self addImageWithName:imageName range:range size:CGSizeMake(self.font.pointSize, self.font.ascender)];
}
#pragma mark - appendImage
- (void)appendImageContent:(id)imageContent size:(CGSize)size alignment:(TYDrawAlignment)alignment
{
TYImageStorage *imageStorage = [[TYImageStorage alloc] init];
if ([imageContent isKindOfClass:[UIImage class]]) {
imageStorage.image = imageContent;
}else if ([imageContent isKindOfClass:[NSString class]]){
imageStorage.imageName = imageContent;
} else {
return;
}
imageStorage.drawAlignment = alignment;
imageStorage.size = size;
[self appendTextStorage:imageStorage];
}
- (void)appendImage:(UIImage *)image size:(CGSize)size alignment:(TYDrawAlignment)alignment
{
[self appendImageContent:image size:size alignment:alignment];
}
- (void)appendImage:(UIImage *)image size:(CGSize)size
{
[self appendImage:image size:size alignment:TYDrawAlignmentTop];
}
- (void)appendImage:(UIImage *)image
{
[self appendImage:image size:image.size];
}
- (void)appendImageWithName:(NSString *)imageName size:(CGSize)size alignment:(TYDrawAlignment)alignment
{
[self appendImageContent:imageName size:size alignment:alignment];
}
- (void)appendImageWithName:(NSString *)imageName size:(CGSize)size
{
[self appendImageWithName:imageName size:size alignment:TYDrawAlignmentTop];
}
- (void)appendImageWithName:(NSString *)imageName
{
[self appendImageWithName:imageName size:CGSizeMake(self.font.pointSize, self.font.ascender)];
}
@end
@implementation TYTextContainer (UIView)
#pragma mark - addView
- (void)addView:(UIView *)view range:(NSRange)range alignment:(TYDrawAlignment)alignment
{
TYViewStorage *viewStorage = [[TYViewStorage alloc] init];
viewStorage.drawAlignment = alignment;
viewStorage.view = view;
viewStorage.range = range;
[self addTextStorage:viewStorage];
}
- (void)addView:(UIView *)view range:(NSRange)range
{
[self addView:view range:range alignment:TYDrawAlignmentTop];
}
#pragma mark - appendView
- (void)appendView:(UIView *)view alignment:(TYDrawAlignment)alignment
{
TYViewStorage *viewStorage = [[TYViewStorage alloc] init];
viewStorage.drawAlignment = alignment;
viewStorage.view = view;
[self appendTextStorage:viewStorage];
}
- (void)appendView:(UIView *)view
{
[self appendView:view alignment:TYDrawAlignmentTop];
}
@end
@@ -0,0 +1,297 @@
//
// TYTextContainer.h
// TYAttributedLabelDemo
//
// Created by tanyang on 15/6/4.
// Copyright (c) 2015年 tanyang. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "TYTextStorageProtocol.h"
#import "NSMutableAttributedString+TY.h"
#import "TYTextStorage.h"
#import "TYLinkTextStorage.h"
#import "TYDrawStorage.h"
#import "TYImageStorage.h"
#import "TYViewStorage.h"
@interface TYTextContainer : NSObject
@property (nonatomic, strong) NSString *text;
@property (nonatomic, strong) NSAttributedString *attributedText;
@property (nonatomic, assign) NSInteger numberOfLines; //行数
@property (nonatomic, strong) UIColor *textColor; // 文字颜色
@property (nonatomic, strong) UIColor *linkColor; //链接颜色
@property (nonatomic, strong) UIFont *font; // 文字大小
@property (nonatomic, assign) unichar strokeWidth; // 空心字边框宽
@property (nonatomic, strong) UIColor *strokeColor; // 空心字边框颜色
@property (nonatomic, assign) unichar characterSpacing; // 字距
@property (nonatomic, assign) CGFloat linesSpacing; // 行距
@property (nonatomic, assign) CGFloat paragraphSpacing; // 段落间距
@property (nonatomic, assign) CTTextAlignment textAlignment; // 文本对齐方式 kCTTextAlignmentLeft
@property (nonatomic, assign) CTLineBreakMode lineBreakMode; // 换行模式 kCTLineBreakByCharWrapping
@property (nonatomic, assign) BOOL isWidthToFit; // 宽度自适应
// after createTextContainer, have value
@property (nonatomic, assign, readonly) CGFloat textHeight;
@property (nonatomic, assign, readonly) CGFloat textWidth;
// after createTextContainer, have value
@property (nonatomic, strong, readonly) NSArray *textStorages;
/**
* 生成文本容器textContainer
*/
- (instancetype)createTextContainerWithTextWidth:(CGFloat)textWidth;
- (instancetype)createTextContainerWithContentSize:(CGSize)contentSize;
/**
* 生成属性字符串
*/
- (NSAttributedString *)createAttributedString;
/**
* 获取文本的size
*/
- (CGSize)getSuggestedSizeWithFramesetter:(CTFramesetterRef)framesetter width:(CGFloat)width;
/**
* 获取文本高度
*/
- (CGFloat)getHeightWithFramesetter:(CTFramesetterRef)framesetter width:(CGFloat)width;
@end
@interface TYTextContainer (Add)
/**
* 添加 textStorage (自定义显示内容)
*
* @param textStorage 自定义
*/
- (void)addTextStorage:(id<TYTextStorageProtocol>)textStorage;
/**
* 添加 textRun数组 (自定义显示内容)
*
*/
- (void)addTextStorageArray:(NSArray *)textStorageArray;
@end
#pragma mark - append
@interface TYTextContainer (Append)
/**
* 追加(添加到最后) 普通文本
*
* @param text 普通文本
*/
- (void)appendText:(NSString *)text;
/**
* 追加(添加到最后) 属性文本
*
* @param attributedText 属性文本
*/
- (void)appendTextAttributedString: (NSAttributedString *)attributedText;
/**
* 追加(添加到最后) textStorage (自定义显示内容)
*
* @param textStorage 自定义Storage(自定义显示内容)
*/
- (void)appendTextStorage:(id<TYAppendTextStorageProtocol>)textStorage;
/**
* 追加(添加到最后) textStorage 数组
*
* @param textStorageArray 自定义run数组(需遵循协议TYAppendTextStorageProtocol,否则不会添加)
*/
- (void)appendTextStorageArray:(NSArray *)textStorageArray;
@end
#pragma mark - Link
@interface TYTextContainer (Link)
/**
* 添加 链接LinkTextStorage
*/
- (void)addLinkWithLinkData:(id)linkData range:(NSRange )range;
/**
* 添加 链接LinkTextStorage
*/
- (void)addLinkWithLinkData:(id)linkData linkColor:(UIColor *)linkColor range:(NSRange )range;
/**
* 添加 链接LinkTextStorage
*
* @param linkData 链接携带的数据
* @param linkColor 链接颜色
* @param underLineStyle 下划线样式(无,单 双) 默认单
* @param range 范围
*/
- (void)addLinkWithLinkData:(id)linkData linkColor:(UIColor *)linkColor underLineStyle:(CTUnderlineStyle)underLineStyle range:(NSRange )range;
/**
* 追加 链接LinkTextStorage
*/
- (void)appendLinkWithText:(NSString *)linkText linkFont:(UIFont *)linkFont linkData:(id)linkData;
/**
* 追加 链接LinkTextStorage
*/
- (void)appendLinkWithText:(NSString *)linkText linkFont:(UIFont *)linkFont linkColor:(UIColor *)linkColor linkData:(id)linkData;
/**
* 追加 链接LinkTextStorage
*
* @param linkText 链接文本
* @param linkData 链接携带的数据
* @param underLineStyle 下划线样式(无,单 双) 默认单
*/
- (void)appendLinkWithText:(NSString *)linkText linkFont:(UIFont *)linkFont linkColor:(UIColor *)linkColor underLineStyle:(CTUnderlineStyle)underLineStyle linkData:(id)linkData;
@end
#pragma mark - 扩展支持UIImage
@interface TYTextContainer (UIImage)
#pragma mark - addImageStorage
/**
* 添加 imageStorage image数据
*/
- (void)addImage:(UIImage *)image range:(NSRange)range;
/**
* 添加 imageStorage image数据
*/
- (void)addImage:(UIImage *)image range:(NSRange)range size:(CGSize)size;
/**
* 添加 imageStorage image数据
*
* @param image image
* @param range 所在文本位置
* @param size 图片大小
* @param alignment 图片对齐方式
*/
- (void)addImage:(UIImage *)image range:(NSRange)range
size:(CGSize)size
alignment: (TYDrawAlignment)alignment;
/**
* 添加 imageStorage image数据
*/
- (void)addImageWithName:(NSString *)imageName range:(NSRange)range;
/**
* 添加 imageStorage image数据
*/
- (void)addImageWithName:(NSString *)imageName
range:(NSRange)range
size:(CGSize)size;
/**
* 添加 imageStorage image数据
*
* @param imageName image名
* @param range 所在文本位置
* @param size 图片大小
* @param alignment 图片对齐方式
*/
- (void)addImageWithName:(NSString *)imageName
range:(NSRange)range
size:(CGSize)size
alignment:(TYDrawAlignment)alignment;
#pragma mark - appendImageStorage
/**
* 追加 imageStorage image数据
*/
- (void)appendImage:(UIImage *)image;
/**
* 追加 imageStorage image数据
*/
- (void)appendImage:(UIImage *)image
size:(CGSize)size;
/**
* 追加 imageStorage image数据
*
* @param image image
* @param size 图片大小
* @param alignment 图片对齐
*/
- (void)appendImage:(UIImage *)image
size:(CGSize)size
alignment:(TYDrawAlignment)alignment;
/**
* 追加 imageStorage image数据
*/
- (void)appendImageWithName:(NSString *)imageName;
/**
* 追加 imageStorage image数据
*/
- (void)appendImageWithName:(NSString *)imageName size:(CGSize)size;
/**
* 追加 imageStorage image数据
*
* @param imageName imageName
* @param size 图片大小
* @param alignment 图片对齐
*/
- (void)appendImageWithName:(NSString *)imageName
size:(CGSize)size
alignment:(TYDrawAlignment)alignment;
@end
#pragma mark - 扩展支持UIView
@interface TYTextContainer (UIView)
/**
* 添加 viewStorage (添加 UI控件 需要设置frame)
*/
- (void)addView:(UIView *)view range:(NSRange)range;
/**
* 添加 viewStorage (添加 UI控件 需要设置frame)
*
* @param view UIView (UI控件)
* @param range 所在文本位置
* @param alignment view对齐方式
*/
- (void)addView:(UIView *)view
range:(NSRange)range
alignment:(TYDrawAlignment)alignment;
/**
* 追加 viewStorage (添加 UI控件 需要设置frame)
*/
- (void)appendView:(UIView *)view;
/**
* 追加 viewStorage (添加 UI控件 需要设置frame)
*
* @param view UIView (UI控件)
* @param alignment view对齐
*/
- (void)appendView:(UIView *)view alignment:(TYDrawAlignment)alignment;
@end
@@ -0,0 +1,678 @@
//
// TYTextContainer.m
// TYAttributedLabelDemo
//
// Created by tanyang on 15/6/4.
// Copyright (c) 2015年 tanyang. All rights reserved.
//
#import "TYTextContainer.h"
#define kTextColor [UIColor colorWithRed:51/255.0 green:51/255.0 blue:51/255.0 alpha:1]
#define kLinkColor [UIColor colorWithRed:0/255.0 green:91/255.0 blue:255/255.0 alpha:1]
// this code quote TTTAttributedLabel
static inline CGSize CTFramesetterSuggestFrameSizeForAttributedStringWithConstraints(CTFramesetterRef framesetter, NSAttributedString *attributedString, CGSize size, NSUInteger numberOfLines) {
CFRange rangeToSize = CFRangeMake(0, (CFIndex)[attributedString length]);
CGSize constraints = CGSizeMake(size.width, MAXFLOAT);
if (numberOfLines > 0) {
// If the line count of the label more than 1, limit the range to size to the number of lines that have been set
CGMutablePathRef path = CGPathCreateMutable();
CGPathAddRect(path, NULL, CGRectMake(0.0f, 0.0f, constraints.width, MAXFLOAT));
CTFrameRef frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, 0), path, NULL);
CFArrayRef lines = CTFrameGetLines(frame);
if (CFArrayGetCount(lines) > 0) {
NSInteger lastVisibleLineIndex = MIN((CFIndex)numberOfLines, CFArrayGetCount(lines)) - 1;
CTLineRef lastVisibleLine = CFArrayGetValueAtIndex(lines, lastVisibleLineIndex);
CFRange rangeToLayout = CTLineGetStringRange(lastVisibleLine);
rangeToSize = CFRangeMake(0, rangeToLayout.location + rangeToLayout.length);
}
CFRelease(frame);
CFRelease(path);
}
CGSize suggestedSize = CTFramesetterSuggestFrameSizeWithConstraints(framesetter, rangeToSize, NULL, constraints, NULL);
return CGSizeMake(ceil(suggestedSize.width), ceil(suggestedSize.height));
}
@interface TYTextContainer()
@property (nonatomic, strong) NSMutableArray *textStorageArray; // run数组
@property (nonatomic, strong) NSArray *textStorages; // run array copy
@property (nonatomic, strong) NSDictionary *drawRectDictionary;
@property (nonatomic, strong) NSDictionary *runRectDictionary; // runRect字典
@property (nonatomic, strong) NSDictionary *linkRectDictionary; // linkRect字典
@property (nonatomic, assign) NSInteger replaceStringNum; // 图片替换字符数
@property (nonatomic, strong) NSMutableAttributedString *attString;
@property (nonatomic, assign) CTFrameRef frameRef;
@property (nonatomic, assign) CGFloat textHeight;
@property (nonatomic, assign) CGFloat textWidth;
@end
@implementation TYTextContainer
- (instancetype)init
{
if (self = [super init]) {
[self setupProperty];
}
return self;
}
#pragma mark - getter
- (NSMutableArray *)textStorageArray
{
if (_textStorageArray == nil) {
_textStorageArray = [NSMutableArray array];
}
return _textStorageArray;
}
- (NSString *)text{
return _attString.string;
}
- (NSAttributedString *)attributedText
{
return [_attString copy];
}
- (NSAttributedString *)createAttributedString
{
[self addTextStoragesWithAtrributedString:_attString];
if (_attString == nil) {
_attString = [[NSMutableAttributedString alloc] init];
}
return [_attString copy];
}
#pragma mark - setter
- (void)setupProperty
{
_font = [UIFont systemFontOfSize:15];
_characterSpacing = 1;
_linesSpacing = 2;
_paragraphSpacing = 0;
#if __IPHONE_OS_VERSION_MAX_ALLOWED <= 9000
_textAlignment = kCTLeftTextAlignment;
#else
_textAlignment = kCTTextAlignmentLeft;
#endif
_lineBreakMode = kCTLineBreakByCharWrapping;
_textColor = kTextColor;
_linkColor = kLinkColor;
_replaceStringNum = 0;
}
- (void)resetAllAttributed
{
[self resetRectDictionary];
_textStorageArray = nil;
_textStorages = nil;
_replaceStringNum = 0;
}
- (void)resetRectDictionary
{
_drawRectDictionary = nil;
_linkRectDictionary = nil;
_runRectDictionary = nil;
}
- (void)resetFrameRef
{
if (_frameRef) {
CFRelease(_frameRef);
_frameRef = nil;
}
_textHeight = 0;
}
- (void)setText:(NSString *)text
{
_attString = [self createTextAttibuteStringWithText:text];
[self resetAllAttributed];
[self resetFrameRef];
}
- (void)setAttributedText:(NSAttributedString *)attributedText
{
if (attributedText == nil) {
_attString = [[NSMutableAttributedString alloc] init];
}else if ([attributedText isKindOfClass:[NSMutableAttributedString class]]) {
_attString = (NSMutableAttributedString *)attributedText;
}else {
_attString = [[NSMutableAttributedString alloc] initWithAttributedString:attributedText];
}
[self resetAllAttributed];
[self resetFrameRef];
}
- (void)setTextColor:(UIColor *)textColor
{
if (textColor && _textColor != textColor){
_textColor = textColor;
[_attString addAttributeTextColor:textColor];
[self resetFrameRef];
}
}
- (void)setFont:(UIFont *)font
{
if (font && _font != font){
_font = font;
[_attString addAttributeFont:font];
[self resetFrameRef];
}
}
- (void)setStrokeWidth:(unichar)strokeWidth
{
if (_strokeWidth != strokeWidth) {
_strokeWidth = strokeWidth;
[_attString addAttributeStrokeWidth:strokeWidth strokeColor:_strokeColor];
[self resetFrameRef];
}
}
- (void)setStrokeColor:(UIColor *)strokeColor
{
if (strokeColor && _strokeColor != strokeColor) {
_strokeColor = strokeColor;
[_attString addAttributeStrokeWidth:_strokeWidth strokeColor:strokeColor];
[self resetFrameRef];
}
}
- (void)setCharacterSpacing:(unichar)characterSpacing
{
if (_characterSpacing != characterSpacing) {
_characterSpacing = characterSpacing;
[_attString addAttributeCharacterSpacing:characterSpacing];
[self resetFrameRef];
}
}
- (void)setLinesSpacing:(CGFloat)linesSpacing
{
if (_linesSpacing != linesSpacing) {
_linesSpacing = linesSpacing;
[self addAttributeAlignmentStyle:_textAlignment lineSpaceStyle:linesSpacing paragraphSpaceStyle:_paragraphSpacing lineBreakStyle:_lineBreakMode];
[self resetFrameRef];
}
}
- (void)setParagraphSpacing:(CGFloat)paragraphSpacing
{
if (_paragraphSpacing != paragraphSpacing) {
_paragraphSpacing = paragraphSpacing;
[self addAttributeAlignmentStyle:_textAlignment lineSpaceStyle:_linesSpacing paragraphSpaceStyle:_paragraphSpacing lineBreakStyle:_lineBreakMode];
[self resetFrameRef];
}
}
- (void)setTextAlignment:(CTTextAlignment)textAlignment
{
if (_textAlignment != textAlignment) {
_textAlignment = textAlignment;
[self addAttributeAlignmentStyle:textAlignment lineSpaceStyle:_linesSpacing paragraphSpaceStyle:_paragraphSpacing lineBreakStyle:_lineBreakMode];
[self resetFrameRef];
}
}
- (void)setLineBreakMode:(CTLineBreakMode)lineBreakMode
{
if (_lineBreakMode != lineBreakMode) {
_lineBreakMode = lineBreakMode;
[self addAttributeAlignmentStyle:_textAlignment lineSpaceStyle:_linesSpacing paragraphSpaceStyle:_paragraphSpacing lineBreakStyle:_lineBreakMode];
[self resetFrameRef];
}
}
#pragma mark - create text attibuteString
- (NSMutableAttributedString *)createTextAttibuteStringWithText:(NSString *)text
{
if (text.length <= 0) {
return [[NSMutableAttributedString alloc] init];
}
// 创建属性文本
NSMutableAttributedString *attString = [[NSMutableAttributedString alloc] initWithString:text];
// 添加文本颜色 字体属性
[self addTextColorAndFontWithAtrributedString:attString];
// 添加文本段落样式
[self addTextParaphStyleWithAtrributedString:attString];
return attString;
}
// 添加文本颜色 字体属性
- (void)addTextColorAndFontWithAtrributedString:(NSMutableAttributedString *)attString
{
// 添加文本字体
[attString addAttributeFont:_font];
// 添加文本颜色
[attString addAttributeTextColor:_textColor];
// 添加空心字体
if (_strokeWidth > 0) {
[attString addAttributeStrokeWidth:_strokeWidth strokeColor:_strokeColor];
}
}
// 添加文本段落样式
- (void)addTextParaphStyleWithAtrributedString:(NSMutableAttributedString *)attString
{
// 字体间距
if (_characterSpacing)
{
[attString addAttributeCharacterSpacing:_characterSpacing];
}
// 添加文本段落样式
[self addAttributeAlignmentStyle:_textAlignment lineSpaceStyle:_linesSpacing paragraphSpaceStyle:_paragraphSpacing lineBreakStyle:_lineBreakMode];
}
- (void)addAttributeAlignmentStyle:(CTTextAlignment)textAlignment
lineSpaceStyle:(CGFloat)linesSpacing
paragraphSpaceStyle:(CGFloat)paragraphSpacing
lineBreakStyle:(CTLineBreakMode)lineBreakMode
{
if (lineBreakMode == kCTLineBreakByTruncatingTail)
{
lineBreakMode = _numberOfLines == 1 ? kCTLineBreakByCharWrapping : kCTLineBreakByWordWrapping;
}
[_attString addAttributeAlignmentStyle:_textAlignment lineSpaceStyle:_linesSpacing paragraphSpaceStyle:_paragraphSpacing lineBreakStyle:lineBreakMode];
}
#pragma mark - add text storage atrributed
- (void)addTextStoragesWithAtrributedString:(NSMutableAttributedString *)attString
{
if (attString && _textStorageArray.count > 0) {
// 排序range
[self sortTextStorageArray:_textStorageArray];
for (id<TYTextStorageProtocol> textStorage in _textStorageArray) {
// 修正图片替换字符来的误差
if ([textStorage conformsToProtocol:@protocol(TYDrawStorageProtocol) ]) {
continue;
}
if ([textStorage conformsToProtocol:@protocol(TYLinkStorageProtocol)]) {
if (!((id<TYLinkStorageProtocol>)textStorage).textColor) {
((id<TYLinkStorageProtocol>)textStorage).textColor = _linkColor;
}
}
// 验证范围
if (NSMaxRange(textStorage.range) <= attString.length) {
[textStorage addTextStorageWithAttributedString:attString];
}
}
for (id<TYTextStorageProtocol> textStorage in _textStorageArray) {
textStorage.realRange = NSMakeRange(textStorage.range.location-_replaceStringNum, textStorage.range.length);
if ([textStorage conformsToProtocol:@protocol(TYDrawStorageProtocol)]) {
id<TYDrawStorageProtocol> drawStorage = (id<TYDrawStorageProtocol>)textStorage;
NSInteger currentLenght = attString.length;
[drawStorage setTextfontAscent:_font.ascender descent:_font.descender];
[drawStorage currentReplacedStringNum:_replaceStringNum];
[drawStorage addTextStorageWithAttributedString:attString];
_replaceStringNum += currentLenght - attString.length;
}
}
_textStorages = [_textStorageArray copy];
[_textStorageArray removeAllObjects];
}
}
- (void)sortTextStorageArray:(NSMutableArray *)textStorageArray
{
[textStorageArray sortUsingComparator:^NSComparisonResult(id<TYTextStorageProtocol> obj1, id<TYTextStorageProtocol> obj2) {
if (obj1.range.location < obj2.range.location) {
return NSOrderedAscending;
} else if (obj1.range.location > obj2.range.location){
return NSOrderedDescending;
}else {
return obj1.range.length > obj2.range.length ? NSOrderedAscending:NSOrderedDescending;
}
}];
}
- (void)saveTextStorageRectWithFrame:(CTFrameRef)frame
{
if (!frame) {
return;
}
// 获取每行
CFArrayRef lines = CTFrameGetLines(frame);
CGPoint lineOrigins[CFArrayGetCount(lines)];
CTFrameGetLineOrigins(frame, CFRangeMake(0, 0), lineOrigins);
CGFloat viewWidth = _textWidth;
NSInteger numberOfLines = _numberOfLines > 0 ? MIN(_numberOfLines, CFArrayGetCount(lines)) : CFArrayGetCount(lines);
NSMutableDictionary *runRectDictionary = [NSMutableDictionary dictionary];
NSMutableDictionary *linkRectDictionary = [NSMutableDictionary dictionary];
NSMutableDictionary *drawRectDictionary = [NSMutableDictionary dictionary];
// 获取每行有多少run
for (int i = 0; i < numberOfLines; i++) {
CTLineRef line = CFArrayGetValueAtIndex(lines, i);
CGFloat lineAscent;
CGFloat lineDescent;
CGFloat lineLeading;
CTLineGetTypographicBounds(line, &lineAscent, &lineDescent, &lineLeading);
CFArrayRef runs = CTLineGetGlyphRuns(line);
// 获得每行的run
for (int j = 0; j < CFArrayGetCount(runs); j++) {
CGFloat runAscent;
CGFloat runDescent;
CGPoint lineOrigin = lineOrigins[i];
CTRunRef run = CFArrayGetValueAtIndex(runs, j);
// run的属性字典
NSDictionary* attributes = (NSDictionary*)CTRunGetAttributes(run);
id<TYTextStorageProtocol> textStorage = [attributes objectForKey:kTYTextRunAttributedName];
if (textStorage) {
CGFloat runWidth = CTRunGetTypographicBounds(run, CFRangeMake(0,0), &runAscent, &runDescent, NULL);
if (viewWidth > 0 && runWidth > viewWidth) {
runWidth = viewWidth;
}
CGRect runRect = CGRectMake(lineOrigin.x + CTLineGetOffsetForStringIndex(line, CTRunGetStringRange(run).location, NULL), lineOrigin.y - runDescent, runWidth, runAscent + runDescent);
if ([textStorage conformsToProtocol:@protocol(TYDrawStorageProtocol)]) {
[drawRectDictionary setObject:textStorage forKey:[NSValue valueWithCGRect:runRect]];
} else if ([textStorage conformsToProtocol:@protocol(TYLinkStorageProtocol)]) {
[linkRectDictionary setObject:textStorage forKey:[NSValue valueWithCGRect:runRect]];
}
[runRectDictionary setObject:textStorage forKey:[NSValue valueWithCGRect:runRect]];
}
}
}
if (drawRectDictionary.count > 0) {
_drawRectDictionary = [drawRectDictionary copy];
}else {
_drawRectDictionary = nil;
}
if (runRectDictionary.count > 0) {
// 添加响应点击rect
[self addRunRectDictionary:[runRectDictionary copy]];
}
if (linkRectDictionary.count > 0) {
_linkRectDictionary = [linkRectDictionary copy];
}else {
_linkRectDictionary = nil;
}
}
// 添加响应点击rect
- (void)addRunRectDictionary:(NSDictionary *)runRectDictionary
{
if (runRectDictionary.count < _runRectDictionary.count) {
NSMutableArray *drawStorageArray = [[_runRectDictionary allValues]mutableCopy];
// 剔除已经画出来的
[drawStorageArray removeObjectsInArray:[runRectDictionary allValues]];
// 遍历不会画出来的
for (id<TYTextStorageProtocol>drawStorage in drawStorageArray) {
if ([drawStorage conformsToProtocol:@protocol(TYViewStorageProtocol)]) {
[(id<TYViewStorageProtocol>)drawStorage didNotDrawRun];
}
}
}
_runRectDictionary = runRectDictionary;
}
- (CGSize)getSuggestedSizeWithFramesetter:(CTFramesetterRef)framesetter width:(CGFloat)width
{
if (_attString == nil || width <= 0) {
return CGSizeZero;
}
if (_textHeight > 0) {
return CGSizeMake(_textWidth > 0 ? _textWidth : width, _textHeight);
}
// 是否需要更新frame
if (framesetter == nil) {
framesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)[self createAttributedString]);
}else {
CFRetain(framesetter);
}
// 获得建议的size
CGSize suggestedSize = CTFramesetterSuggestFrameSizeForAttributedStringWithConstraints(framesetter, _attString, CGSizeMake(width,MAXFLOAT), _numberOfLines);
CFRelease(framesetter);
return CGSizeMake(_isWidthToFit ? suggestedSize.width : width, suggestedSize.height+1);
}
- (CGFloat)getHeightWithFramesetter:(CTFramesetterRef)framesetter width:(CGFloat)width
{
return [self getSuggestedSizeWithFramesetter:framesetter width:width].height;
}
- (CTFrameRef)createFrameRefWithFramesetter:(CTFramesetterRef)framesetter textSize:(CGSize)textSize
{
// 这里你需要创建一个用于绘制文本的路径区域,通过 self.bounds 使用整个视图矩形区域创建 CGPath 引用。
CGMutablePathRef path = CGPathCreateMutable();
CGFloat textHeight = [self getHeightWithFramesetter:framesetter width:textSize.width];
CGPathAddRect(path, NULL, CGRectMake(0, 0, textSize.width, MAX(textHeight, textSize.height)));
CTFrameRef frameRef = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, [_attString length]), path, NULL);
CFRelease(path);
return frameRef;
}
- (instancetype)createTextContainerWithTextWidth:(CGFloat)textWidth
{
return [self createTextContainerWithContentSize:CGSizeMake(textWidth, 0)];
}
- (instancetype)createTextContainerWithContentSize:(CGSize)contentSize
{
if (_frameRef) {
return self;
}
NSAttributedString *attStr = [self createAttributedString];
// 创建CTFramesetter
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)attStr);
// 获得建议的size
CGSize size = [self getSuggestedSizeWithFramesetter:framesetter width:contentSize.width];
_textWidth = size.width;
_textHeight = size.height;
// 创建CTFrameRef
_frameRef = [self createFrameRefWithFramesetter:framesetter textSize:CGSizeMake(_textWidth, contentSize.height > 0 ? contentSize.height : _textHeight)];
// 释放内存
CFRelease(framesetter);
// 保存run rect
[self saveTextStorageRectWithFrame:_frameRef];
return self;
}
#pragma mark - enumerate runRect
- (BOOL)existRunRectDictionary
{
return _runRectDictionary.count != 0;
}
- (BOOL)existLinkRectDictionary
{
return _linkRectDictionary.count != 0;
}
- (BOOL)existDrawRectDictionary
{
return _drawRectDictionary.count != 0;
}
- (void)enumerateDrawRectDictionaryUsingBlock:(void (^)(id<TYDrawStorageProtocol> drawStorage, CGRect rect))block
{
[_drawRectDictionary enumerateKeysAndObjectsUsingBlock:^(NSValue *rectValue, id<TYDrawStorageProtocol> drawStorage, BOOL * stop) {
if (block) {
block(drawStorage,[rectValue CGRectValue]);
}
}];
}
- (BOOL)enumerateRunRectContainPoint:(CGPoint)point viewHeight:(CGFloat)viewHeight successBlock:(void (^)(id<TYTextStorageProtocol> textStorage))successBlock
{
return [self enumerateRunRect:_runRectDictionary ContainPoint:point viewHeight:viewHeight successBlock:successBlock];
}
- (BOOL)enumerateLinkRectContainPoint:(CGPoint)point viewHeight:(CGFloat)viewHeight successBlock:(void (^)(id<TYLinkStorageProtocol> textStorage))successBlock
{
return [self enumerateRunRect:_linkRectDictionary ContainPoint:point viewHeight:viewHeight successBlock:successBlock];
}
- (BOOL)enumerateRunRect:(NSDictionary *)runRectDic ContainPoint:(CGPoint)point viewHeight:(CGFloat)viewHeight successBlock:(void (^)(id textStorage))successBlock
{
if (runRectDic.count == 0) {
return NO;
}
// CoreText context coordinates are the opposite to UIKit so we flip the bounds
CGAffineTransform transform = CGAffineTransformScale(CGAffineTransformMakeTranslation(0, viewHeight), 1.f, -1.f);
__block BOOL find = NO;
// 遍历run位置字典
[runRectDic enumerateKeysAndObjectsUsingBlock:^(NSValue *keyRectValue, id<TYTextStorageProtocol> textStorage, BOOL *stop) {
CGRect imgRect = [keyRectValue CGRectValue];
CGRect rect = CGRectApplyAffineTransform(imgRect, transform);
if ([textStorage conformsToProtocol:@protocol(TYDrawStorageProtocol) ]) {
rect = UIEdgeInsetsInsetRect(rect,((id<TYDrawStorageProtocol>)textStorage).margin);
}
// point 是否在rect里
if(CGRectContainsPoint(rect, point)){
find = YES;
*stop = YES;
if (successBlock) {
successBlock(textStorage);
}
}
}];
return find;
}
- (void)dealloc{
[self resetFrameRef];
}
@end
#pragma mark - add textStorage
@implementation TYTextContainer (Add)
- (void)addTextStorage:(id<TYTextStorageProtocol>)textStorage
{
if (textStorage) {
[self.textStorageArray addObject:textStorage];
[self resetFrameRef];
}
}
- (void)addTextStorageArray:(NSArray *)textStorageArray
{
if (textStorageArray) {
for (id<TYTextStorageProtocol> textStorage in textStorageArray) {
if ([textStorage conformsToProtocol:@protocol(TYTextStorageProtocol)]) {
[self addTextStorage:textStorage];
}
}
}
}
@end
#pragma mark - append textStorage
@implementation TYTextContainer (Append)
- (void)appendText:(NSString *)text
{
NSAttributedString *attributedText = [self createTextAttibuteStringWithText:text];
[self appendTextAttributedString:attributedText];
[self resetFrameRef];
}
- (void)appendTextAttributedString:(NSAttributedString *)attributedText
{
if (attributedText == nil) {
return;
}
if (_attString == nil) {
_attString = [[NSMutableAttributedString alloc] init];
}
if ([attributedText isKindOfClass:[NSMutableAttributedString class]]) {
[self addTextParaphStyleWithAtrributedString:(NSMutableAttributedString *)attributedText];
}
[_attString appendAttributedString:attributedText];
[self resetFrameRef];
}
- (void)appendTextStorage:(id<TYAppendTextStorageProtocol>)textStorage
{
if (textStorage) {
if ([textStorage conformsToProtocol:@protocol(TYDrawStorageProtocol)]) {
[(id<TYDrawStorageProtocol>)textStorage setTextfontAscent:_font.ascender descent:_font.descender];
} else if ([textStorage conformsToProtocol:@protocol(TYLinkStorageProtocol)]) {
if (!((id<TYLinkStorageProtocol>)textStorage).textColor) {
((id<TYLinkStorageProtocol>)textStorage).textColor = _linkColor;
}
}
NSAttributedString *attAppendString = [textStorage appendTextStorageAttributedString];
textStorage.realRange = NSMakeRange(_attString.length, attAppendString.length);
[self appendTextAttributedString:attAppendString];
[self resetFrameRef];
}
}
- (void)appendTextStorageArray:(NSArray *)textStorageArray
{
if (textStorageArray) {
for (id<TYAppendTextStorageProtocol> textStorage in textStorageArray) {
if ([textStorage conformsToProtocol:@protocol(TYAppendTextStorageProtocol)]) {
[self appendTextStorage:textStorage];
}
}
}
}
@end
@@ -0,0 +1,24 @@
//
// TYTextStorage.h
// TYAttributedLabelDemo
//
// Created by tanyang on 15/4/8.
// Copyright (c) 2015年 tanyang. All rights reserved.
//
#import "TYTextStorageProtocol.h"
#import <CoreText/CoreText.h>
@interface TYTextStorage : NSObject<TYAppendTextStorageProtocol>
@property (nonatomic, assign) NSInteger tag; // 标识
@property (nonatomic, assign) NSRange range; //如果appendStorage, range只针对追加的文本
@property (nonatomic, assign) NSRange realRange; // label文本中实际位置,因为某些文本被替换,会导致位置偏移
@property (nonatomic, strong) NSString *text; // 只针对追加text文本
@property (nonatomic, strong) UIColor *textColor; // 文本颜色
@property (nonatomic, strong) UIFont *font; // 字体
@property (nonatomic, assign) CTUnderlineStyle underLineStyle;// 下划线样式(单 双)(默认没有)
@property (nonatomic, assign) CTUnderlineStyleModifiers modifier;// 下划线样式 (点 线)(默认线)
@end
@@ -0,0 +1,47 @@
//
// TYTextStorage.m
// TYAttributedLabelDemo
//
// Created by tanyang on 15/4/8.
// Copyright (c) 2015年 tanyang. All rights reserved.
//
#import "TYTextStorage.h"
#import "NSMutableAttributedString+TY.h"
@implementation TYTextStorage
#pragma mark - protocol
- (void)addTextStorageWithAttributedString:(NSMutableAttributedString *)attributedString
{
// 颜色
if (_textColor) {
[attributedString addAttributeTextColor:_textColor range:_range];
}
// 字体
if (_font) {
[attributedString addAttributeFont:_font range:_range];
}
// 下划线
if (_underLineStyle) {
[attributedString addAttributeUnderlineStyle:_underLineStyle modifier:_modifier range:_range];
}
}
- (NSAttributedString *)appendTextStorageAttributedString
{
NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:_text];
// 验证范围
if (NSEqualRanges(_range, NSMakeRange(0, 0))) {
_range = NSMakeRange(0, attributedString.length);
}
[self addTextStorageWithAttributedString:attributedString];
return [attributedString copy];
}
@end
@@ -0,0 +1,94 @@
//
// TYTextStorageProtocol.h
// TYAttributedLabelDemo
//
// Created by tanyang on 15/4/8.
// Copyright (c) 2015年 tanyang. All rights reserved.
//
#import <UIKit/UIKit.h>
typedef enum : NSUInteger {
TYDrawAlignmentTop, // 底部齐平 向上伸展
TYDrawAlignmentCenter, // 中心齐平
TYDrawAlignmentBottom, // 顶部齐平 向下伸展
} TYDrawAlignment;
extern NSString *const kTYTextRunAttributedName;
@protocol TYTextStorageProtocol <NSObject>
@required
/**
* 范围(如果是appendStorage,range只针对追加的文本)
*/
@property (nonatomic,assign) NSRange range;
/**
* 文本中实际位置,因为某些文本被替换,会导致位置偏移
*/
@property (nonatomic,assign) NSRange realRange;
/**
* 添加属性到全文attributedString addTextStorage调用
*
* @param attributedString 属性字符串
*/
- (void)addTextStorageWithAttributedString:(NSMutableAttributedString *)attributedString;
@end
@protocol TYAppendTextStorageProtocol <TYTextStorageProtocol>
@required
/**
* 追加attributedString属性 appendTextStorage调用
*
* @return 返回需要追加的attributedString属性
*/
- (NSAttributedString *)appendTextStorageAttributedString;
@end
@protocol TYLinkStorageProtocol <TYAppendTextStorageProtocol>
@property (nonatomic, strong) UIColor *textColor; // 文本颜色
@end
@protocol TYDrawStorageProtocol <TYAppendTextStorageProtocol>
@property (nonatomic, assign) UIEdgeInsets margin; // 四周间距
/**
* 添加View 或 绘画 到该区域
*
* @param rect 绘画区域
*/
- (void)drawStorageWithRect:(CGRect)rect;
/**
* 设置字体高度 当前字符串替换数
*/
- (void)setTextfontAscent:(CGFloat)ascent descent:(CGFloat)descent;
// 当前替换字符数
- (void)currentReplacedStringNum:(NSInteger)replacedStringNum;
@end
@protocol TYViewStorageProtocol <NSObject>
/**
* 设置所属的view
*/
- (void)setOwnerView:(UIView *)ownerView;
/**
* 不会把你绘画出来
*/
- (void)didNotDrawRun;
@end
@@ -0,0 +1,15 @@
//
// TYDrawViewStorage.h
// TYAttributedLabelDemo
//
// Created by tanyang on 15/4/9.
// Copyright (c) 2015年 tanyang. All rights reserved.
//
#import "TYDrawStorage.h"
@interface TYViewStorage : TYDrawStorage<TYViewStorageProtocol>
@property (nonatomic, strong) UIView *view; // 添加view
@end
@@ -0,0 +1,66 @@
//
// TYDrawViewStorage.m
// TYAttributedLabelDemo
//
// Created by tanyang on 15/4/9.
// Copyright (c) 2015年 tanyang. All rights reserved.
//
#import "TYViewStorage.h"
@interface TYViewStorage ()
@property (nonatomic, weak) UIView *superView;
@end
@implementation TYViewStorage
#pragma mark - protocol
- (void)setView:(UIView *)view
{
_view = view;
if (CGSizeEqualToSize(self.size, CGSizeZero)) {
if ([NSThread isMainThread]) {
self.size = view.frame.size;
} else {
dispatch_semaphore_t signal = dispatch_semaphore_create(0);
dispatch_sync(dispatch_get_main_queue(), ^{
self.size = view.frame.size;
dispatch_semaphore_signal(signal);
});
dispatch_semaphore_wait(signal, DISPATCH_TIME_FOREVER);
}
}
}
- (void)setOwnerView:(UIView *)ownerView
{
if (_view.superview) {
[_view removeFromSuperview];
}
_superView = ownerView;
}
- (void)didNotDrawRun
{
[_view removeFromSuperview];
}
- (void)drawStorageWithRect:(CGRect)rect
{
if (_view == nil || _superView == nil) return;
// 设置frame 注意 转换rect CoreText context coordinates are the opposite to UIKit so we flip the bounds
CGAffineTransform transform = CGAffineTransformScale(CGAffineTransformMakeTranslation(0, _superView.bounds.size.height), 1.f, -1.f);
rect = CGRectApplyAffineTransform(rect, transform);
[_view setFrame:rect];
[_superView addSubview:_view];
}
- (void)dealloc {
[self.view performSelectorOnMainThread:@selector(removeFromSuperview) withObject:nil waitUntilDone:[NSThread isMainThread]];
}
@end
@@ -0,0 +1,96 @@
//
// WXYZ_GCDTimer.h
// WXReader
//
// Created by Andrew on 2020/6/18.
// Copyright © 2020 Andrew. All rights reserved.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
typedef NS_ENUM(NSUInteger, WXYZ_GCDTimerState) {
WXYZ_GCDTimerStateStoped,
WXYZ_GCDTimerStateRunning,
WXYZ_GCDTimerStatePausing
};
@interface WXYZ_GCDTimer : NSObject
@property (nonatomic, copy) void (^timerStartBlock)(void); // 定时器开始回调
@property (nonatomic, copy) void (^timerSuspendBlock)(void); // 定时器暂停回调
@property (nonatomic, copy) void (^timerResumeBlock)(void); // 定时器恢复回调
@property (nonatomic, copy) void (^timerTerminateBlock)(void); // 定时器终止回调
@property (nonatomic, copy) void (^timerFinishedBlock)(void); // 定时器完成回调
@property (nonatomic, copy) void (^timerRunningBlock)(NSUInteger runTimes, CGFloat currentTime); // 定时器运行回调
@property (nonatomic, assign, readonly) WXYZ_GCDTimerState timerState; // 定时器状态
/**
循环定时器(每秒循环一次)
@param immediatelyCallBack 开启定时器时是否立即进行运行回调
*/
- (instancetype)initCycleTimerWithImmediatelyCallBack:(BOOL)immediatelyCallBack;
/**
循环计时器
@param interval 计时间隔
@param immediatelyCallBack 开启定时器时是否立即进行运行回调
*/
- (instancetype)initCycleTimerWithInterval:(double)interval immediatelyCallBack:(BOOL)immediatelyCallBack;
/**
倒计时计时器(每秒循环一次)
@param timeDuration 倒计时时长
@param immediatelyCallBack 开启定时器时是否立即进行运行回调
*/
- (instancetype)initCountdownTimerWithTimeDuration:(double)timeDuration immediatelyCallBack:(BOOL)immediatelyCallBack;
/**
倒计时计时器
@param timeDuration 倒计时时长
@param interval 计时间隔
@param immediatelyCallBack 开启定时器时是否立即进行运行回调
*/
- (instancetype)initCountdownTimerWithTimeDuration:(double)timeDuration interval:(double)interval immediatelyCallBack:(BOOL)immediatelyCallBack;
- (instancetype)init NS_UNAVAILABLE;
/**
* 定时器开启
*/
- (void)startTimer;
/**
* 定时器开启
*/
- (void)startTimerWithTimeDuration:(double)timeDuration;
/**
* 定时器停止
*/
- (void)stopTimer;
/**
* 定时器恢复
*/
- (void)resumeTimer;
/**
* 定时器暂停
*/
- (void)suspendTimer;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,214 @@
//
// WXYZ_GCDTimer.m
// WXReader
//
// Created by Andrew on 2020/6/18.
// Copyright © 2020 Andrew. All rights reserved.
//
#import "WXYZ_GCDTimer.h"
@interface WXYZ_GCDTimer ()
{
dispatch_source_t __block timer;
WXYZ_GCDTimerState _timerState;
double _timeDuration;
double _interval;
BOOL _immediatelyCallBack;
}
@end
@implementation WXYZ_GCDTimer
// 循环定时器(每秒循环一次)
- (instancetype)initCycleTimerWithImmediatelyCallBack:(BOOL)immediatelyCallBack
{
if (self = [super init]) {
_timerState = WXYZ_GCDTimerStateStoped;
_immediatelyCallBack = immediatelyCallBack;
_timeDuration = 0;
_interval = 1;
}
return self;
}
// 循环计时器
- (instancetype)initCycleTimerWithInterval:(double)interval immediatelyCallBack:(BOOL)immediatelyCallBack
{
if (self = [super init]) {
_timerState = WXYZ_GCDTimerStateStoped;
_immediatelyCallBack = immediatelyCallBack;
_timeDuration = 0;
_interval = interval;
}
return self;
}
// 倒计时计时器(每秒循环一次)
- (instancetype)initCountdownTimerWithTimeDuration:(double)timeDuration immediatelyCallBack:(BOOL)immediatelyCallBack
{
if (self = [super init]) {
_timerState = WXYZ_GCDTimerStateStoped;
_immediatelyCallBack = immediatelyCallBack;
_timeDuration = timeDuration;
_interval = 1;
}
return self;
}
// 设定时间间隔倒计时计时器
- (instancetype)initCountdownTimerWithTimeDuration:(double)timeDuration interval:(double)interval immediatelyCallBack:(BOOL)immediatelyCallBack
{
if (self = [super init]) {
_timerState = WXYZ_GCDTimerStateStoped;
_immediatelyCallBack = immediatelyCallBack;
_timeDuration = timeDuration;
_interval = interval;
}
return self;
}
/**
* 定时器开启
*/
- (void)startTimer
{
if (timer && _timerState == WXYZ_GCDTimerStatePausing) {
[self resumeTimer];
}
[self stopTimer];
[self createzhishudaliTimer];
}
- (void)startTimerWithTimeDuration:(double)timeDuration
{
_timeDuration = timeDuration;
[self startTimer];
}
/**
* 定时器停止
*/
- (void)stopTimer
{
if(timer) {
if (_timerState == WXYZ_GCDTimerStateRunning) {
dispatch_source_cancel(timer);
timer = nil;
_timerState = WXYZ_GCDTimerStateStoped;
if (self.timerTerminateBlock) {
self.timerTerminateBlock();
}
}
}
}
/**
* 定时器恢复
*/
- (void)resumeTimer
{
if(timer) {
if (_timerState == WXYZ_GCDTimerStatePausing) {
dispatch_resume(timer);
_timerState = WXYZ_GCDTimerStateRunning;
if (self.timerResumeBlock) {
self.timerResumeBlock();
}
}
}
}
/**
* 定时器暂停
*/
- (void)suspendTimer
{
if(timer){
if (_timerState == WXYZ_GCDTimerStateRunning) {
dispatch_suspend(timer);
_timerState = WXYZ_GCDTimerStatePausing;
if (self.timerSuspendBlock) {
self.timerSuspendBlock();
}
}
}
}
#pragma privite
/**
* 定时器任务完成
*/
- (void)finishTimer
{
if(timer) {
if (_timerState == WXYZ_GCDTimerStateRunning) {
dispatch_source_cancel(timer);
timer = nil;
_timerState = WXYZ_GCDTimerStateStoped;
if (self.timerFinishedBlock) {
self.timerFinishedBlock();
}
}
}
}
- (void)createzhishudaliTimer
{
WS(weakSelf)
BOOL __block kImmediatelyCallBack = _immediatelyCallBack;
double __block kTimeDuration = _timeDuration <= 0 ? HUGE_VAL : _timeDuration;
// 计时器时间不正确
if (kTimeDuration <= 0) {
if (self.timerFinishedBlock) {
self.timerFinishedBlock();
}
return;
}
if (_interval == 0) {
_interval = 1;
}
NSUInteger __block runTimes = 0;
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
dispatch_source_set_timer(timer,dispatch_walltime(NULL, 0), _interval * NSEC_PER_SEC, 0);
dispatch_source_set_event_handler(timer, ^{
runTimes ++;
_timerState = WXYZ_GCDTimerStateRunning;
if (kTimeDuration <= 0) {
[weakSelf finishTimer];
} else {
kTimeDuration = kTimeDuration - _interval;
if (kImmediatelyCallBack) {
dispatch_async(dispatch_get_main_queue(), ^{
if (weakSelf.timerRunningBlock) {
weakSelf.timerRunningBlock(runTimes, kTimeDuration != INFINITY ?kTimeDuration:0.f);
}
});
}
kImmediatelyCallBack = YES;
}
});
dispatch_resume(timer);
if (self.timerStartBlock) {
self.timerStartBlock();
}
}
@end
@@ -0,0 +1,15 @@
//
// TFCollectionViewFlowLayout.h
// TFPhotoBrowser
//
// Created by zhengwenming on 2018/6/11.
// Copyright © 2018年 zhengwenming. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface TFCollectionViewFlowLayout : UICollectionViewFlowLayout
@property (nonatomic ,assign) CGFloat imgaeGap;
@end
@@ -0,0 +1,55 @@
//
// TFCollectionViewFlowLayout.m
// TFPhotoBrowser
//
// Created by zhengwenming on 2018/6/11.
// Copyright © 2018年 zhengwenming. All rights reserved.
//
#import "TFCollectionViewFlowLayout.h"
@implementation TFCollectionViewFlowLayout
- (void)prepareLayout
{
[super prepareLayout];
self.scrollDirection = UICollectionViewScrollDirectionHorizontal;
CGSize size = self.collectionView.bounds.size;
self.itemSize = CGSizeMake(size.width, size.height);
self.minimumLineSpacing = 0;
self.minimumInteritemSpacing = 10;
}
- (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect
{
NSArray<UICollectionViewLayoutAttributes *> *layoutAttsArray = [[NSArray alloc] initWithArray:[super layoutAttributesForElementsInRect:rect] copyItems:YES];
CGFloat centerX = self.collectionView.contentOffset.x + self.collectionView.bounds.size.width/2.0;
__block CGFloat min = CGFLOAT_MAX;
__block NSUInteger minIdx;
[layoutAttsArray enumerateObjectsUsingBlock:^(UICollectionViewLayoutAttributes * _Nonnull obj, NSUInteger index, BOOL * _Nonnull stop) {
if (ABS(centerX - obj.center.x) < min) {
min = ABS(centerX - obj.center.x);
minIdx = index;
}
}];
[layoutAttsArray enumerateObjectsUsingBlock:^(UICollectionViewLayoutAttributes * _Nonnull obj, NSUInteger index, BOOL * _Nonnull stop) {
if (minIdx - 1 == index) {
obj.center = CGPointMake(obj.center.x - self.imgaeGap, obj.center.y);
}
if (minIdx + 1 == index) {
obj.center = CGPointMake(obj.center.x + self.imgaeGap, obj.center.y);
}
}];
return layoutAttsArray;
}
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds
{
return YES;
}
@end
@@ -0,0 +1,47 @@
//
// TFPhotoBrowser.h
// TFPhotoBrowser
//
// Created by zhengwenming on 2018/1/2.
// Copyright © 2018年 zhengwenming. All rights reserved.
//
#import <UIKit/UIKit.h>
typedef void(^DeleteBlock)(NSMutableArray *dataSource, NSUInteger currentIndex, UICollectionView *collectionView);
typedef void(^DownLoadBlock)(NSMutableArray *dataSource, UIImage *image, NSError *error);
@interface TFPhotoBrowser : UIViewController
/**
* 需要预览的照片数组
*/
@property (nonatomic ,strong) NSMutableArray *dataSource;
/**
* 需要展示的当前的图片index
*/
@property (nonatomic ,assign) NSInteger currentPhotoIndex;
/**
* 是否需要下载
*/
@property (nonatomic ,assign) BOOL downLoadNeeded;
/**
* 是否需要删除
*/
@property (nonatomic ,assign) BOOL deleteNeeded;
/**
* 下载回调
*/
@property (nonatomic ,copy) DownLoadBlock downLoadBlock;
/**
* 删除回调
*/
@property (nonatomic ,copy) DeleteBlock deleteBlock;
@end
@@ -0,0 +1,275 @@
//
// TFPhotoBrowser.m
// TFPhotoBrowser
//
// Created by zhengwenming on 2018/1/2.
// Copyright © 2018年 zhengwenming. All rights reserved.
//
#import "TFPhotoBrowser.h"
#import "TFPhotoBrowserCell.h"
#import "TFCollectionViewFlowLayout.h"
@interface TFPhotoBrowser ()<UICollectionViewDataSource ,UICollectionViewDelegate ,UIScrollViewDelegate>
@property(nonatomic ,assign) BOOL isHideNaviBar;
@property(nonatomic ,strong) UICollectionView *collectionView;
@property(nonatomic ,strong) UIPageControl *pageControl;
@end
@implementation TFPhotoBrowser
- (BOOL)fullScreenGestureShouldBegin
{
return NO;
}
// 是否支持自动转屏
- (BOOL)shouldAutorotate
{
return NO;
}
// 支持哪些屏幕方向
- (UIInterfaceOrientationMask)supportedInterfaceOrientations
{
return UIInterfaceOrientationMaskAllButUpsideDown;
}
// 默认的屏幕方向(当前ViewController必须是通过模态出来的UIViewController(模态带导航的无效)方式展现出来的,才会调用这个方法)
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation
{
return UIInterfaceOrientationPortrait;
}
- (instancetype)init
{
if (self = [super init]) {
if (@available(ios 11.0,*)) {
// UIScrollView.appearance.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
// UITableView.appearance.estimatedRowHeight = 0;
// UITableView.appearance.estimatedSectionFooterHeight = 0;
// UITableView.appearance.estimatedSectionHeaderHeight = 0;
} else {
if ([self respondsToSelector:@selector(automaticallyAdjustsScrollViewInsets)]) {
self.automaticallyAdjustsScrollViewInsets = NO;
}
}
}
return self;
}
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
[self.navigationController setNavigationBarHidden:NO animated:YES];
}
- (void)deleteTheImage:(UIBarButtonItem *)sender
{
if (self.dataSource.count == 1) {
[self.dataSource removeObjectAtIndex:self.currentPhotoIndex];
[self.navigationController popViewControllerAnimated:YES];
} else {
[self.dataSource removeObjectAtIndex:self.currentPhotoIndex];
self.title = [NSString stringWithFormat:@"%@/%@", [TFUtilsHelper formatStringWithInteger:self.currentPhotoIndex + 1], [TFUtilsHelper formatStringWithInteger:self.dataSource.count]];
[self.collectionView reloadData];
}
__weak typeof(self) weakSelf = self;
if (self.deleteBlock) {
self.deleteBlock(weakSelf.dataSource, weakSelf.currentPhotoIndex, weakSelf.collectionView);
}
}
- (UICollectionView *)collectionView
{
if (_collectionView == nil) {
TFCollectionViewFlowLayout *layout = [[TFCollectionViewFlowLayout alloc] init];
layout.imgaeGap = 20;
_collectionView = [[UICollectionView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height) collectionViewLayout:layout];
_collectionView.backgroundColor = [UIColor blackColor];
_collectionView.dataSource = self;
_collectionView.delegate = self;
_collectionView.pagingEnabled = YES;
_collectionView.scrollsToTop = NO;
[_collectionView registerClass:[TFPhotoBrowserCell class] forCellWithReuseIdentifier:@"TFPhotoBrowserCell"];
_collectionView.showsHorizontalScrollIndicator = NO;
_collectionView.contentOffset = CGPointMake(0, 0);
_collectionView.contentSize = CGSizeMake(self.view.frame.size.width * self.dataSource.count, self.view.frame.size.height);
}
return _collectionView;
}
// 视图发生了大小改变的时候会调用此方法 大小改变 == 横竖切换
- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator
{
if ([UIDevice currentDevice].orientation == UIDeviceOrientationLandscapeLeft || [UIDevice currentDevice].orientation == UIDeviceOrientationLandscapeRight) {
self.collectionView.frame = CGRectMake(0, 0, size.width, size.height);
self.pageControl.frame = CGRectMake(0, size.height - 30, size.width, 30);
self.pageControl.centerX = self.view.centerX;
} else {
self.collectionView.frame = CGRectMake(0, 0, size.width, size.height);
self.pageControl.frame = CGRectMake(0, size.height - 30, size.width, 30);
self.pageControl.centerX = self.view.centerX;
}
}
- (void)viewDidLoad
{
[super viewDidLoad];
if (self.downLoadNeeded) {
UIButton *saveImageBtn = [UIButton buttonWithType:UIButtonTypeCustom];
saveImageBtn.frame = CGRectMake(0, 0, 40, 40);
saveImageBtn.autoresizingMask = UIViewAutoresizingFlexibleHeight;
[saveImageBtn setImage:[UIImage imageNamed:@"savePicture"] forState:UIControlStateNormal];
[saveImageBtn setImage:[UIImage imageNamed:@"savePicture"] forState:UIControlStateHighlighted];
[saveImageBtn addTarget:self action:@selector(saveImage) forControlEvents:UIControlEventTouchUpInside];
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:saveImageBtn];
} else if(self.deleteNeeded) {
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemTrash target:self action:@selector(deleteTheImage:)];
}
self.title = self.title ? self.title : [NSString stringWithFormat:@"%@/%@", [TFUtilsHelper formatStringWithInteger:self.currentPhotoIndex + 1], [TFUtilsHelper formatStringWithInteger:self.dataSource.count]];
self.view.backgroundColor = [UIColor blackColor];
self.isHideNaviBar = NO;
[self.view addSubview:self.collectionView];
if (self.dataSource.count) {
[self.collectionView scrollToItemAtIndexPath:[NSIndexPath indexPathForRow:(self.currentPhotoIndex) inSection:0] atScrollPosition:UICollectionViewScrollPositionLeft animated:NO];
}
[self.view addSubview:self.pageControl];
self.pageControl.numberOfPages = self.dataSource.count;
self.pageControl.currentPage = self.currentPhotoIndex;
}
- (UIPageControl *)pageControl
{
if (!_pageControl) {
_pageControl = [[UIPageControl alloc] initWithFrame:CGRectMake(0, self.view.frame.size.height-30, self.view.frame.size.width, 30)];
_pageControl.numberOfPages = 5;
_pageControl.pageIndicatorTintColor = [UIColor darkGrayColor];
_pageControl.currentPageIndicatorTintColor = [UIColor whiteColor];
_pageControl.backgroundColor = [UIColor clearColor];
}
return _pageControl;
}
- (void)saveImage
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:self.currentPhotoIndex inSection:0];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
TFPhotoBrowserCell *currentCell = (TFPhotoBrowserCell *)[self.collectionView cellForItemAtIndexPath:indexPath];
UIImageWriteToSavedPhotosAlbum(currentCell.imageView.image, self, @selector(image:didFinishSavingWithError:contextInfo:), nil);
});
});
}
- (void)image:(UIImage *)image didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo
{
if (error) {
[TFPromptManager showPromptViewWithStatus:TFPromptStatusError promptTitle:TFLocalizedString(@"保存失败")];
} else {
[TFPromptManager showPromptViewWithStatus:TFPromptStatusSuccess promptTitle:TFLocalizedString(@"保存成功")];
}
__weak typeof(self) weakSelf = self;
if (self.downLoadBlock) {
self.downLoadBlock(weakSelf.dataSource,image,error);
}
}
#pragma mark - UIScrollViewDelegate
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
// if (self.currentPhotoIndex==0) {
// scrollView.bounces = NO;
// }else{
// scrollView.bounces = YES;
// }
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
if ([self.title isEqualToString:TFLocalizedString(@"图片预览")]) {
} else {
CGPoint offSet = scrollView.contentOffset;
self.currentPhotoIndex = offSet.x / self.view.width;
self.title = [NSString stringWithFormat:@"%@/%@", [TFUtilsHelper formatStringWithInteger:self.currentPhotoIndex + 1], [TFUtilsHelper formatStringWithInteger:self.dataSource.count]];
self.pageControl.currentPage = self.currentPhotoIndex;
}
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
return self.dataSource.count;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
TFPhotoBrowserCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"TFPhotoBrowserCell" forIndexPath:indexPath];
cell.model = self.dataSource[indexPath.row];
WS(weakSelf)
if (!cell.singleTapGestureBlock) {
cell.singleTapGestureBlock = ^(){
if (weakSelf.isHideNaviBar == YES) {
[weakSelf.navigationController setNavigationBarHidden:NO animated:YES];
} else {
[weakSelf.navigationController setNavigationBarHidden:YES animated:YES];
}
weakSelf.isHideNaviBar = !weakSelf.isHideNaviBar;
[weakSelf dismissViewControllerAnimated:YES completion:^{
}];
};
}
if (!cell.longPressGestureBlock) {
__weak typeof(self) weakSelf = self;
cell.longPressGestureBlock = ^(TFPhotoBrowserCell *cell) {
UIAlertController *actionSheet = [UIAlertController alertControllerWithTitle:nil message:nil preferredStyle:UIAlertControllerStyleActionSheet];
if (is_iPad) {
UIPopoverPresentationController *popover = actionSheet.popoverPresentationController;
if (popover) {
popover.sourceView = weakSelf.view;
popover.sourceRect = weakSelf.view.bounds;
popover.permittedArrowDirections = UIPopoverArrowDirectionDown;
}
}
[actionSheet addAction:[UIAlertAction actionWithTitle:TFLocalizedString(@"保存到相册") style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
UIImageWriteToSavedPhotosAlbum(cell.imageView.image, weakSelf,
@selector(image:didFinishSavingWithError:contextInfo:), nil);
}]];
[actionSheet addAction:[UIAlertAction actionWithTitle:TFLocalizedString(@"取消") style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
NSLog(@"取消");
}]];
[weakSelf presentViewController:actionSheet animated:YES completion:nil];
};
}
cell.currentIndexPath = indexPath;
self.title = [NSString stringWithFormat:@"%@/%@", [TFUtilsHelper formatStringWithInteger:self.currentPhotoIndex + 1], [TFUtilsHelper formatStringWithInteger:self.dataSource.count]];
return cell;
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
}
@end
@@ -0,0 +1,19 @@
//
// TFPhotoBrowserCell.h
// TFPhotoBrowser
//
// Created by zhengwenming on 2018/1/2.
// Copyright © 2018年 zhengwenming. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface TFPhotoBrowserCell : UICollectionViewCell
@property (nonatomic ,copy) NSIndexPath *currentIndexPath;
@property (nonatomic ,strong) UIImageView *imageView;
@property (nonatomic ,retain) id model;
@property (nonatomic ,copy) void (^singleTapGestureBlock)(void);
@property (nonatomic ,copy) void (^longPressGestureBlock)(TFPhotoBrowserCell *cell);
@end
@@ -0,0 +1,192 @@
//
// TFPhotoBrowserCell.m
// TFPhotoBrowser
//
// Created by zhengwenming on 2018/1/2.
// Copyright © 2018年 zhengwenming. All rights reserved.
//
#import "TFPhotoBrowserCell.h"
@interface TFPhotoBrowserCell ()<UIGestureRecognizerDelegate, UIScrollViewDelegate> {
CGFloat _aspectRatio;
}
@property (nonatomic ,strong) UIScrollView *scrollView;
@property (nonatomic ,strong) UIView *imageContainerView;
@end
@implementation TFPhotoBrowserCell
- (instancetype)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame]) {
self.backgroundColor = [UIColor blackColor];
self.scrollView = [[UIScrollView alloc] init];
self.scrollView.frame = CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height);
self.scrollView.bouncesZoom = YES;
self.scrollView.backgroundColor = [UIColor blackColor];
self.scrollView.maximumZoomScale = 2.5;//放大比例
self.scrollView.minimumZoomScale = 1.0;//缩小比例
self.scrollView.multipleTouchEnabled = YES;
self.scrollView.delegate = self;
self.scrollView.scrollsToTop = NO;
self.scrollView.showsHorizontalScrollIndicator = NO;
self.scrollView.showsVerticalScrollIndicator = NO;
self.scrollView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
self.scrollView.delaysContentTouches = NO;
self.scrollView.canCancelContentTouches = YES;
self.scrollView.alwaysBounceVertical = NO;
[self.contentView addSubview:self.scrollView];
self.imageContainerView = [[UIView alloc] init];
self.imageContainerView.clipsToBounds = YES;
[self.scrollView addSubview:self.imageContainerView];
self.imageView = [[UIImageView alloc] init];
self.imageView.backgroundColor = [UIColor colorWithWhite:1.000 alpha:0.500];
self.imageView.clipsToBounds = YES;
self.imageView.userInteractionEnabled = YES;
[self.imageContainerView addSubview:self.imageView];
// 单击
UITapGestureRecognizer *singleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(singleTap:)];
[self.contentView addGestureRecognizer:singleTap];
// 双击
UITapGestureRecognizer *doubleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(doubleTap:)];
doubleTap.numberOfTapsRequired = 2;
[singleTap requireGestureRecognizerToFail:doubleTap];
[self.contentView addGestureRecognizer:doubleTap];
// 长按
UILongPressGestureRecognizer *longPressGes = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(longPressToSavePhoto:)];
[self.contentView addGestureRecognizer:longPressGes];
}
return self;
}
- (void)longPressToSavePhoto:(UILongPressGestureRecognizer *)sender
{
if (sender.state == UIGestureRecognizerStateBegan) {
if (self.longPressGestureBlock) {
self.longPressGestureBlock(self);
}
}
}
- (void)resizeSubviews
{
self.imageContainerView.origin = CGPointZero;
self.imageContainerView.width = self.width;
UIImage *image = self.imageView.image;
if (image.size.height/image.size.width > self.height/self.width) {
self.imageContainerView.height = floor(image.size.height / (image.size.width / self.width));
} else {
CGFloat height = image.size.height / image.size.width * self.width;
if (height < 1 || isnan(height)) height = self.height;
height = floor(height);
self.imageContainerView.height = height;
self.imageContainerView.centerY = self.height / 2;
}
if (self.imageContainerView.height > self.height && self.imageContainerView.height - self.height <= 1) {
self.imageContainerView.height = self.height;
}
self.scrollView.contentSize = CGSizeMake(self.width, MAX(self.imageContainerView.height, self.height));
[self.scrollView scrollRectToVisible:self.bounds animated:NO];
self.scrollView.alwaysBounceVertical = self.imageContainerView.height <= self.height ? NO : YES;
self.imageView.frame = self.imageContainerView.bounds;
}
- (void)layoutSubviews
{
[super layoutSubviews];
[self resizeSubviews];
}
- (void)doubleTap:(UITapGestureRecognizer *)tap
{
if (self.scrollView.zoomScale > 1.0) {
[self.scrollView setZoomScale:1.0 animated:YES];
} else {
CGPoint touchPoint = [tap locationInView:self.imageView];
CGFloat newZoomScale = self.scrollView.maximumZoomScale;
CGFloat xsize = self.frame.size.width / newZoomScale;
CGFloat ysize = self.frame.size.height / newZoomScale;
[self.scrollView zoomToRect:CGRectMake(touchPoint.x - xsize/2, touchPoint.y - ysize/2, xsize, ysize) animated:YES];
}
}
- (void)setModel:(id)model
{
_model = model;
[self.scrollView setZoomScale:1.0 animated:NO];
if ([model isKindOfClass:[UIImage class]]) {
UIImage *aImage = (UIImage *)model;
self.imageView.image = aImage;
} else if ([model isKindOfClass:[NSString class]]) {
NSString *aString = (NSString *)model;
if ([aString rangeOfString:@"http"].location != NSNotFound) {
WS(weakSelf)
[self.imageView setImageWithURL:[NSURL URLWithString:aString] placeholder:HoldImage options:YYWebImageOptionSetImageWithFadeAnimation completion:^(UIImage * _Nullable image, NSURL * _Nonnull url, YYWebImageFromType from, YYWebImageStage stage, NSError * _Nullable error) {
[weakSelf resizeSubviews];
}];
} else {
self.imageView.image = [UIImage imageNamed:aString];
}
} else if ([model isKindOfClass:[NSURL class]]) {
NSURL *aURL = (NSURL *)model;
WS(weakSelf)
[self.imageView setImageWithURL:aURL placeholder:HoldImage options:YYWebImageOptionSetImageWithFadeAnimation completion:^(UIImage * _Nullable image, NSURL * _Nonnull url, YYWebImageFromType from, YYWebImageStage stage, NSError * _Nullable error) {
[weakSelf resizeSubviews];
}];
}
[self resizeSubviews];
}
- (void)singleTap:(UITapGestureRecognizer *)tap
{
if (self.singleTapGestureBlock) {
self.singleTapGestureBlock();
}
}
- (nullable UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView
{
return self.imageContainerView;
}
- (void)scrollViewDidZoom:(UIScrollView *)scrollView
{
CGFloat offsetX = (scrollView.width > scrollView.contentSize.width) ? (scrollView.width - scrollView.contentSize.width) * 0.5 : 0.0;
CGFloat offsetY = (scrollView.height > scrollView.contentSize.height) ? (scrollView.height - scrollView.contentSize.height) * 0.5 : 0.0;
self.imageContainerView.center = CGPointMake(scrollView.contentSize.width * 0.5 + offsetX, scrollView.contentSize.height * 0.5 + offsetY);
}
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
return YES;
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
if (self.scrollView.contentOffset.x <= 0) {
if ([otherGestureRecognizer.delegate isKindOfClass:NSClassFromString(@"_FDFullscreenPopGestureRecognizerDelegate")]) {
return YES;
}
}
return NO;
}
@end
@@ -0,0 +1,91 @@
//
// DPImagePicker.h
//
// Created by Andrew on 2017/9/11.
// Copyright © 2017年 Andrew. All rights reserved.
//
#import <UIKit/UIKit.h>
typedef enum : NSUInteger {
DPCameraDeviceFront, //前置摄像头
DPCameraDeviceRear, //后置摄像头
} DPCameraDevice;
typedef enum : NSUInteger {
DPCameraFlashModeOff, //关闭闪光灯
DPCameraFlashModeAuto, //自动闪光灯
DPCameraFlashModeOn, //开启闪光灯
} DPCameraFlashMode;
typedef enum : NSUInteger {
DPChooseImageTypeCamera,
DPChooseImageTypeLibrary,
DPChooseImageTypeUnknow
} DPChooseImageType;
typedef void(^ChooseImageStyleBlock)(DPChooseImageType type);
@protocol DPImagePickerDelegate <NSObject>
@optional
/**
图片选择完毕
@param originalImage 未编辑原图
@param editedImage 编辑后图片
*/
- (void)imagePickerDidFinishPickingWithOriginalImage:(UIImage *)originalImage editedImage:(UIImage *)editedImage;
- (void)imagePickerDidCancel;
@end
@interface WXYZ_ImagePicker : UIView <UIActionSheetDelegate, UIImagePickerControllerDelegate, UINavigationControllerDelegate>
@property (nonatomic, weak) id <DPImagePickerDelegate> delegate;
@property (nonatomic, copy) ChooseImageStyleBlock chooseImageStyleBlock;
/**
是否可以编辑图片
default is NO
*/
@property (nonatomic, assign) BOOL editPhoto;
/**
前置/后置摄像头
default is Rear
*/
@property (nonatomic, assign) DPCameraDevice cameraDevice;
/**
闪光灯类型
default is Auto
*/
@property (nonatomic, assign) DPCameraFlashMode cameraFlashMode;
interface_singleton
/**
显示图片选择器
@param showController 承载的Controller
*/
- (void)showInController:(UIViewController *)showController;
/**
只显示相机
@param showController 承载Controller
*/
- (void)showCameraInController:(UIViewController *)showController;
/**
只显示相册
@param showController 承载Controller
*/
- (void)showLibraryInController:(UIViewController *)showController;
@end
@@ -0,0 +1,222 @@
//
// DPImagePicker.m
//
// Created by Andrew on 2017/9/11.
// Copyright © 2017年 Andrew. All rights reserved.
//
#import "WXYZ_ImagePicker.h"
#import <CoreServices/CoreServices.h>
#import <AVFoundation/AVFoundation.h>
#import <Photos/Photos.h>
#import <Photos/Photos.h>
@class UIActionSheetDelegateImpl;
static UIActionSheetDelegateImpl * delegateImpl;
@implementation WXYZ_ImagePicker
{
UIViewController *_showController;
}
implementation_singleton(WXYZ_ImagePicker)
- (instancetype)init
{
if (self = [super init]) {
_cameraFlashMode = DPCameraFlashModeAuto;
_cameraDevice = DPCameraDeviceFront;
}
return self;
}
- (void)showInController:(UIViewController *)showController
{
_showController = showController;
WS(weakSelf)
UIAlertController *actionSheet = [UIAlertController alertControllerWithTitle:nil message:nil preferredStyle:UIAlertControllerStyleActionSheet];
if (is_iPad) {
UIPopoverPresentationController *popover = actionSheet.popoverPresentationController;
if (popover) {
popover.sourceView = self;
popover.sourceRect = self.bounds;
popover.permittedArrowDirections = UIPopoverArrowDirectionDown;
}
}
[actionSheet addAction:[UIAlertAction actionWithTitle:TFLocalizedString(@"拍照") style:UIAlertActionStyleDestructive handler:^(UIAlertAction *action) {
if (weakSelf.chooseImageStyleBlock) {
weakSelf.chooseImageStyleBlock(DPChooseImageTypeCamera);
}
//相机
[weakSelf turnOnCamera];
}]];
[actionSheet addAction:[UIAlertAction actionWithTitle:TFLocalizedString(@"从相册中选择") style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
if (weakSelf.chooseImageStyleBlock) {
weakSelf.chooseImageStyleBlock(DPChooseImageTypeLibrary);
}
//相册
[weakSelf turnOnLibrary];
}]];
[actionSheet addAction:[UIAlertAction actionWithTitle:TFLocalizedString(@"取消") style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) {
}]];
[[TFViewHelper getWindowRootController] presentViewController:actionSheet animated:YES completion:nil];
}
- (void)showCameraInController:(UIViewController *)showController
{
_showController = showController;
[self turnOnCamera];
}
- (void)showLibraryInController:(UIViewController *)showController
{
_showController = showController;
[self turnOnLibrary];
}
- (void)turnOnCamera
{
//当前设备没有摄像头
if (![UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) {
UIAlertController *alertView = [UIAlertController alertControllerWithTitle:TFLocalizedString(@"提示") message:TFLocalizedString(@"当前设备没有摄像头") preferredStyle:UIAlertControllerStyleAlert];
[alertView addAction:[UIAlertAction actionWithTitle:TFLocalizedString(@"确定") style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) {
}]];
[[TFViewHelper getWindowRootController] presentViewController:alertView animated:YES completion:nil];
return;
}
//无法获取相机权限
if([AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo] == kCLAuthorizationStatusDenied){
UIAlertController *alertView = [UIAlertController alertControllerWithTitle:TFLocalizedString(@"提示") message:TFLocalizedString(@"请前往设置->隐私->相机授权应用拍照权限") preferredStyle:UIAlertControllerStyleAlert];
[alertView addAction:[UIAlertAction actionWithTitle:TFLocalizedString(@"确定") style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) {
}]];
[[TFViewHelper getWindowRootController] presentViewController:alertView animated:YES completion:nil];
return;
}
UIImagePickerController *imagePickerController = [[UIImagePickerController alloc] init];
imagePickerController.delegate = self;
imagePickerController.allowsEditing = _editPhoto;
imagePickerController.sourceType = UIImagePickerControllerSourceTypeCamera;
imagePickerController.cameraDevice = _cameraDevice == DPCameraDeviceFront?UIImagePickerControllerCameraDeviceFront:UIImagePickerControllerCameraDeviceRear;
switch (_cameraFlashMode) {
case DPCameraFlashModeAuto:
imagePickerController.cameraFlashMode = UIImagePickerControllerCameraFlashModeAuto;
break;
case DPCameraFlashModeOn:
imagePickerController.cameraFlashMode = UIImagePickerControllerCameraFlashModeOn;
break;
case DPCameraFlashModeOff:
imagePickerController.cameraFlashMode = UIImagePickerControllerCameraFlashModeOff;
break;
default:
break;
}
imagePickerController.mediaTypes = @[(NSString *)kUTTypeImage];
imagePickerController.modalPresentationStyle = UIModalPresentationFullScreen;
[_showController presentViewController:imagePickerController animated:YES completion:nil];
}
- (void)turnOnLibrary
{
//当前设备没有相册
if (![UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypePhotoLibrary]) {
UIAlertController *alertView = [UIAlertController alertControllerWithTitle:TFLocalizedString(@"提示") message:TFLocalizedString(@"当前设备没有相册") preferredStyle:UIAlertControllerStyleAlert];
[alertView addAction:[UIAlertAction actionWithTitle:TFLocalizedString(@"确定") style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) {
}]];
[[TFViewHelper getWindowRootController] presentViewController:alertView animated:YES completion:nil];
return;
}
[PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) {
switch (status) {
case PHAuthorizationStatusAuthorized:
break;
case PHAuthorizationStatusDenied:
break;
case PHAuthorizationStatusNotDetermined:
break;
case PHAuthorizationStatusRestricted:
break;
}
}];
//无法访问相册
if ([PHPhotoLibrary authorizationStatus] == PHAuthorizationStatusDenied) {
UIAlertController *alertView = [UIAlertController alertControllerWithTitle:TFLocalizedString(@"提示") message:TFLocalizedString(@"请前往设置->隐私->相册授权应用访问相册权限") preferredStyle:UIAlertControllerStyleAlert];
[alertView addAction:[UIAlertAction actionWithTitle:TFLocalizedString(@"确定") style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) {
}]];
[[TFViewHelper getWindowRootController] presentViewController:alertView animated:YES completion:nil];
return;
}
UIImagePickerController *imagePickerController = [[UIImagePickerController alloc] init];
imagePickerController.delegate = self;
imagePickerController.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
imagePickerController.allowsEditing = _editPhoto;
imagePickerController.mediaTypes = @[(NSString *)kUTTypeImage];
imagePickerController.modalPresentationStyle = UIModalPresentationFullScreen;
[_showController presentViewController:imagePickerController animated:YES completion:nil];
}
#pragma mark - UIImagePickerControllerDelegate
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info
{
picker.delegate = nil;
[picker dismissViewControllerAnimated:YES completion:nil];
UIImage *originalImage = info[UIImagePickerControllerOriginalImage];
UIImage *editedImage = info[UIImagePickerControllerEditedImage];
if ([self.delegate respondsToSelector:@selector(imagePickerDidFinishPickingWithOriginalImage:editedImage:)]) {
[self.delegate imagePickerDidFinishPickingWithOriginalImage:originalImage editedImage:editedImage];
}
//如果是拍摄的照片,将自动保存到相册
if ([[info objectForKey:UIImagePickerControllerMediaType] isEqualToString:(NSString *)kUTTypeImage] && picker.sourceType == UIImagePickerControllerSourceTypeCamera) {
UIImageWriteToSavedPhotosAlbum(originalImage, self, @selector(image:didFinishSavingWithError:contextInfo:), nil);
}
}
//点击Cancel按钮后执行方法
- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker
{
[_showController dismissViewControllerAnimated:YES completion:nil];
if ([self.delegate respondsToSelector:@selector(imagePickerDidCancel)]) {
[self.delegate imagePickerDidCancel];
}
}
- (void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated {
if ([UIDevice currentDevice].systemVersion.floatValue < 11) {
return;
}
if ([viewController isKindOfClass:NSClassFromString(@"PUPhotoPickerHostViewController")]) {
[viewController.view.subviews enumerateObjectsUsingBlock:^(__kindof UIView * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
if (obj.bounds.size.width < SCREEN_WIDTH / 9 && !obj.isHidden) {
obj.hidden = YES;
*stop = YES;
}
}];
}
}
//保存照片成功后的回调
- (void)image:(UIImage *)image didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo
{
}
@end
@@ -0,0 +1,44 @@
//
// WXReaderAnimationLayer.h
// WXReader
//
// Created by Andrew on 2018/6/8.
// Copyright © 2018年 Andrew. All rights reserved.
//
#import <Foundation/Foundation.h>
typedef NS_ENUM(NSUInteger, WXReaderAnimationState) {
WXReaderAnimationStateStoped = 0,
WXReaderAnimationStateRunning = 1,
WXReaderAnimationStatePausing = 2
};
typedef void(^ReaderAutoReadBlock)(void);
@interface WXYZ_ReaderAnimationLayer : NSObject
// 触发自动翻页
@property (nonatomic, copy) ReaderAutoReadBlock readerAutoReadBlock;
- (instancetype)initWithView:(UIView *)keyView;
// 开始动画
- (void)startReadingAnimation;
//暂停动画
- (void)pauseAnimation;
//继续动画
- (void)resumeAnimation;
// 停止动画
- (void)stopAnimation;
// 重启动画
- (void)resetAnimation;
// 重置时间
- (void)resetDuration:(CFTimeInterval)animationDuration;
@end
@@ -0,0 +1,183 @@
//
// WXReaderAnimationLayer.m
// WXReader
//
// Created by Andrew on 2018/6/8.
// Copyright © 2018年 Andrew. All rights reserved.
//
#import "WXYZ_ReaderAnimationLayer.h"
#import "TFReaderSettingHelper.h"
#define animationKey @"wxreader_animation_layer_key"
@interface WXYZ_ReaderAnimationLayer () <CAAnimationDelegate>
{
CFTimeInterval _animationDuration;
UIView *_layerView;
}
@property (nonatomic, assign) WXReaderAnimationState state;
@property (nonatomic, strong) CAShapeLayer *shapeLayer;
@property (nonatomic, strong) CABasicAnimation *pathAnimation;
@end
@implementation WXYZ_ReaderAnimationLayer
- (instancetype)initWithView:(UIView *)keyView
{
if (self = [super init]) {
_layerView = keyView;
_animationDuration = [[TFReaderSettingHelper sharedManager] getReadSpeed];
}
return self;
}
// 开始动画
- (void)startReadingAnimation
{
self.state = WXReaderAnimationStateRunning;
[self.shapeLayer addAnimation:self.pathAnimation forKey:animationKey];
[_layerView.layer addSublayer:self.shapeLayer];
}
//暂停动画
- (void)pauseAnimation
{
self.state = WXReaderAnimationStatePausing;
CFTimeInterval pausedTime = [self.shapeLayer convertTime:CACurrentMediaTime() fromLayer:nil];
self.shapeLayer.speed = 0.0;
self.shapeLayer.timeOffset = pausedTime;
}
//继续动画
- (void)resumeAnimation
{
if (self.state == WXReaderAnimationStateStoped) {
[self startReadingAnimation];
}
self.state = WXReaderAnimationStateRunning;
CFTimeInterval pausedTime = [self.shapeLayer timeOffset];
self.shapeLayer.speed = 1.0;
self.shapeLayer.timeOffset = 0.0;
self.shapeLayer.beginTime = 0.0;
CFTimeInterval timeSincePause = [self.shapeLayer convertTime:CACurrentMediaTime() fromLayer:nil] - pausedTime;
self.shapeLayer.beginTime = timeSincePause;
}
// 停止动画
- (void)stopAnimation
{
self.state = WXReaderAnimationStateStoped;
self.pathAnimation = nil;
[self.shapeLayer removeAnimationForKey:animationKey];
[self.shapeLayer removeFromSuperlayer];
self.shapeLayer = nil;
}
// 重启动画
- (void)resetAnimation
{
if (self.state == WXReaderAnimationStateRunning) {
[self resetDuration:_animationDuration];
}
}
// 重置时间
- (void)resetDuration:(CFTimeInterval)animationDuration
{
_animationDuration = animationDuration;
if (self.state == WXReaderAnimationStatePausing || self.state == WXReaderAnimationStateStoped) {
[self stopAnimation];
return;
}
[self startReadingAnimation];
}
// 动画代理回调
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag
{
if (flag == YES) {
[self.shapeLayer addAnimation:self.pathAnimation forKey:animationKey];
if (self.state == WXReaderAnimationStateRunning && self.readerAutoReadBlock) {
self.readerAutoReadBlock();
}
}
}
- (UIBezierPath *)layerPath
{
UIBezierPath *path = [UIBezierPath bezierPath];
if (is_iPhoneX) {
CGFloat lineWidth = 2;
// 屏幕左上角圆角半径
CGFloat radius1 = 44;
// 刘海顶部圆角半径
CGFloat radius2 = 6;
// 刘海底部圆角半径
CGFloat radius3 = 20;
// 刘海高度
CGFloat hairHeight = 30;
// 刘海顶部宽度
CGFloat hairTopWidth = 209;
// 刘海左侧空隙
CGFloat hairLeftWidth = (SCREEN_WIDTH - hairTopWidth) / 2;
// 刘海右侧空隙
CGFloat hairRightWidth = hairLeftWidth;
[path moveToPoint:CGPointMake(lineWidth, radius1)];
[path addQuadCurveToPoint:CGPointMake(radius1, lineWidth) controlPoint:CGPointMake(lineWidth, lineWidth)];
[path addLineToPoint:CGPointMake(hairLeftWidth - radius2, lineWidth)];
[path addQuadCurveToPoint:CGPointMake(hairLeftWidth - lineWidth, radius2) controlPoint:CGPointMake(hairLeftWidth - lineWidth, lineWidth)];
[path addLineToPoint:CGPointMake(hairLeftWidth - lineWidth, hairHeight - radius3)];
[path addQuadCurveToPoint:CGPointMake(hairLeftWidth + radius3, hairHeight + lineWidth) controlPoint:CGPointMake(hairLeftWidth, hairHeight)];
[path addLineToPoint:CGPointMake(SCREEN_WIDTH - hairRightWidth - radius3, hairHeight + lineWidth)];
[path addQuadCurveToPoint:CGPointMake(SCREEN_WIDTH - hairRightWidth + lineWidth, hairHeight - radius3) controlPoint:CGPointMake(SCREEN_WIDTH - hairRightWidth, hairHeight)];
[path addLineToPoint:CGPointMake(SCREEN_WIDTH - hairRightWidth + lineWidth, radius2)];
[path addQuadCurveToPoint:CGPointMake(SCREEN_WIDTH - hairRightWidth + radius2, lineWidth) controlPoint:CGPointMake(SCREEN_WIDTH - hairRightWidth + lineWidth, lineWidth)];
[path addLineToPoint:CGPointMake(SCREEN_WIDTH - radius1, lineWidth)];
[path addQuadCurveToPoint:CGPointMake(SCREEN_WIDTH - lineWidth, radius1) controlPoint:CGPointMake(SCREEN_WIDTH - lineWidth, lineWidth)];
} else {
[path moveToPoint:CGPointMake(0, 2)];
[path addLineToPoint:CGPointMake(SCREEN_WIDTH,2)];
}
return path;
}
- (CAShapeLayer *)shapeLayer
{
if (!_shapeLayer) {
_shapeLayer = [CAShapeLayer layer];
_shapeLayer.lineWidth = 4;
_shapeLayer.fillColor = [[UIColor clearColor] CGColor];
_shapeLayer.strokeColor = kColorRGBA(255, 102, 0, 1).CGColor;
_shapeLayer.path = [self layerPath].CGPath;
}
return _shapeLayer;
}
- (CABasicAnimation *)pathAnimation
{
if (!_pathAnimation) {
_pathAnimation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
_pathAnimation.duration = _animationDuration;
_pathAnimation.repeatCount = 1;
_pathAnimation.delegate = self;
_pathAnimation.fromValue = [NSNumber numberWithFloat:0.0f];
_pathAnimation.toValue = [NSNumber numberWithFloat:1.0f];
}
return _pathAnimation;
}
@end
@@ -0,0 +1,14 @@
//
// KeyChainStore.h
//
// Created by Andrew on 2017/8/5.
// Copyright © 2017年 Andrew. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface KeyChainStore : NSObject
+ (void)save:(NSString *)service data:(id)data;
+ (id)load:(NSString *)service;
+ (void)deleteKeyData:(NSString *)service;
@end
@@ -0,0 +1,57 @@
//
// KeyChainStore.m
//
// Created by Andrew on 2017/8/5.
// Copyright © 2017年 Andrew. All rights reserved.
//
#import "KeyChainStore.h"
@implementation KeyChainStore
+ (NSMutableDictionary *)getKeychainQuery:(NSString *)service {
return [NSMutableDictionary dictionaryWithObjectsAndKeys:
(id)kSecClassGenericPassword,(id)kSecClass,
service, (id)kSecAttrService,
service, (id)kSecAttrAccount,
(id)kSecAttrAccessibleAfterFirstUnlock,(id)kSecAttrAccessible,
nil];
}
+ (void)save:(NSString *)service data:(id)data {
//Get search dictionary
NSMutableDictionary *keychainQuery = [self getKeychainQuery:service];
//Delete old item before add new item
SecItemDelete((CFDictionaryRef)keychainQuery);
//Add new object to search dictionary(Attention:the data format)
[keychainQuery setObject:[NSKeyedArchiver archivedDataWithRootObject:data] forKey:(id)kSecValueData];
//Add item to keychain with the search dictionary
SecItemAdd((CFDictionaryRef)keychainQuery, NULL);
}
+ (id)load:(NSString *)service {
id ret = nil;
NSMutableDictionary *keychainQuery = [self getKeychainQuery:service];
//Configure the search setting
//Since in our simple case we are expecting only a single attribute to be returned (the password) we can set the attribute kSecReturnData to kCFBooleanTrue
[keychainQuery setObject:(id)kCFBooleanTrue forKey:(id)kSecReturnData];
[keychainQuery setObject:(id)kSecMatchLimitOne forKey:(id)kSecMatchLimit];
CFDataRef keyData = NULL;
if (SecItemCopyMatching((CFDictionaryRef)keychainQuery, (CFTypeRef *)&keyData) == noErr) {
@try {
ret = [NSKeyedUnarchiver unarchiveObjectWithData:(__bridge NSData *)keyData];
} @catch (NSException *e) {
// TILog(@"Unarchive of %@ failed: %@", service, e);
} @finally {
}
}
if (keyData)
CFRelease(keyData);
return ret;
}
+ (void)deleteKeyData:(NSString *)service {
NSMutableDictionary *keychainQuery = [self getKeychainQuery:service];
SecItemDelete((CFDictionaryRef)keychainQuery);
}
@end
@@ -0,0 +1,14 @@
//
// UUID.h
//
// Created by Andrew on 2017/8/5.
// Copyright © 2017年 Andrew. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface UUID : NSObject
+ (NSString *)getUUID;
@end
@@ -0,0 +1,58 @@
//
// UUID.m
//
// Created by Andrew on 2017/8/5.
// Copyright © 2017年 Andrew. All rights reserved.
//
#import "UUID.h"
#import "KeyChainStore.h"
#define UUID_KEY(bunid) [NSString stringWithFormat:@"cn.property.uuidkey%@",bunid]
#define Pasteboard(bunid) [NSString stringWithFormat:@"cn.property.PublicPasteBord%@",bunid]
@implementation UUID
+ (NSString *)getUUID
{
NSString *bunid = [[NSBundle mainBundle]bundleIdentifier];
NSString *strUUID;
NSUserDefaults *userDefault = [NSUserDefaults standardUserDefaults];
strUUID = [userDefault objectForKey:UUID_KEY(bunid)];
if (!strUUID) {
UIPasteboard *pp = [UIPasteboard generalPasteboard];
NSData *data = [pp valueForPasteboardType:Pasteboard(bunid)];
strUUID = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
if (!strUUID || [strUUID isEqualToString:@""]) {
strUUID = (NSString *)[KeyChainStore load:UUID_KEY(bunid)];
if (strUUID) {
[userDefault setObject:strUUID forKey:UUID_KEY(bunid)];
[userDefault synchronize];
}
} else {
[userDefault setObject:strUUID forKey:UUID_KEY(bunid)];
[userDefault synchronize];
}
}
//首次执行该方法时,uuid为空
if ([strUUID isEqualToString:@""] || !strUUID) {
//生成一个uuid的方法
CFUUIDRef uuidRef = CFUUIDCreate(kCFAllocatorDefault);
strUUID = (NSString *)CFBridgingRelease(CFUUIDCreateString(kCFAllocatorDefault,uuidRef));
//将该uuid保存到keychain
[KeyChainStore save:UUID_KEY(bunid) data:strUUID];
[userDefault setObject:strUUID forKey:UUID_KEY(bunid)];
[userDefault synchronize];
UIPasteboard *p = [UIPasteboard generalPasteboard];
[p setValue:strUUID forPasteboardType:Pasteboard(bunid)];
CFRelease(uuidRef);
}
return strUUID;
}
@end
@@ -0,0 +1,19 @@
//
// WXYZ_ADPangoinVideo.h
// WXReader
//
// Created by LL on 2020/7/29.
// Copyright © 2020 Andrew. All rights reserved.
//
#import "TFAdvertisementBasicView.h"
NS_ASSUME_NONNULL_BEGIN
@interface WXYZ_ADPangolinVideo : TFAdvertisementBasicView
//- (void)show;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,64 @@
//
// WXYZ_ADPangoinVideo.m
// WXReader
//
// Created by LL on 2020/7/29.
// Copyright © 2020 Andrew. All rights reserved.
//
#import "WXYZ_ADPangolinVideo.h"
#import <MediaPlayer/MediaPlayer.h>
#import <AVFoundation/AVFoundation.h>
@interface WXYZ_ADPangolinVideo ()
// 观看视频前的音量大小
@property (nonatomic ,assign) CGFloat initialVolume;
@property (nonatomic ,weak) MPVolumeView *volumeView;
@end
@implementation WXYZ_ADPangolinVideo
- (void)createSubviews
{
[super createSubviews];
self.backgroundColor = [UIColor clearColor];
}
- (void)hide
{
[TFPromptManager hideLoading];
[self removeFromSuperview];
// 穿山甲广告视频播放结束后会设置成AVAudioSessionCategorySoloAmbient模式导致有声或听书被停止播放
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil];
[self restoreVolume];
});
}
// 恢复音量
- (void)restoreVolume {
UISlider *volumeSlider = nil;
for (UIView *obj in self.volumeView.subviews) {
if ([obj.class.description isEqualToString:@"MPVolumeSlider"]) {
volumeSlider = (UISlider *)obj;
break;
}
}
self.volumeView.showsVolumeSlider = YES;
[volumeSlider setValue:self.initialVolume animated:NO];
[volumeSlider sendActionsForControlEvents:UIControlEventTouchUpInside];
[self.volumeView sizeToFit];
}
- (MPVolumeView *)volumeView {
if (!_volumeView) {
MPVolumeView *volumeView = [[MPVolumeView alloc] initWithFrame:CGRectMake(-100, -100, 40, 40)];
_volumeView = volumeView;
[self.window addSubview:volumeView];
}
return _volumeView;
}
@end
@@ -0,0 +1,19 @@
//
// WXYZ_ADPangolinView.h
// WXReader
//
// Created by Andrew on 2019/7/25.
// Copyright © 2019 Andrew. All rights reserved.
//
#import "TFAdvertisementBasicView.h"
NS_ASSUME_NONNULL_BEGIN
@interface WXYZ_ADPangolinView : TFAdvertisementBasicView
// 广告关闭时的回调
@property (nonatomic ,copy) void(^closeBlock)(void);
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,85 @@
//
// WXYZ_ADPangolinView.m
// WXReader
//
// Created by Andrew on 2019/7/25.
// Copyright © 2019 Andrew. All rights reserved.
//
#import "WXYZ_ADPangolinView.h"
#import "WXYZ_ADPangolinVideo.h"
#import "TFReaderSettingHelper.h"
#import "AppDelegate.h"
#import "UIView+LayoutCallback.h"
@interface WXYZ_ADPangolinView ()
@property (nonatomic, weak) UILabel *titleLabel;
@property (nonatomic, weak) UILabel *subtitle;
/// 高度比例
@property (nonatomic, assign) CGFloat heightScale;
@end
@implementation WXYZ_ADPangolinView
- (instancetype)initWithFrame:(CGRect)frame advertisementType:(TFAdvertisementType)type advertisementPosition:(TFAdvertisementPosition)position
{
if (self = [super initWithFrame:frame advertisementType:type advertisementPosition:position]) {
[self createSubviews];
}
return self;
}
- (void)createSubviews {
self.backgroundColor = [UIColor clearColor];
if (self.position == TFAdvertisementPositionEnd && self.type == TFAdvertisementTypeNovel) {
UILabel *subtitle = [[UILabel alloc] init];
self.subtitle = subtitle;
subtitle.backgroundColor = [UIColor clearColor];
UIColor *textColor = [TFReaderSettingHelper sharedManager].getReaderTextColor;
subtitle.textColor = textColor;
subtitle.font = kFont14;
AppDelegate *delegate = (AppDelegate *)kRCodeSync([UIApplication sharedApplication].delegate);
subtitle.text = [NSString stringWithFormat:@"%@%@", delegate.checkSettingModel.ad_status_setting.video_ad_text ?: @"", @">"];
[self addSubview:subtitle];
subtitle.frameBlock = ^(UIView * _Nonnull view) {
[view addBorderLineWithBorderWidth:1.0 borderColor:textColor cornerRadius:0.0 borderType:UIBorderSideTypeBottom];
};
subtitle.userInteractionEnabled = YES;
[subtitle addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(watchVideo)]];
UILabel *titleLabel = [[UILabel alloc] init];
self.titleLabel = titleLabel;
titleLabel.backgroundColor = [UIColor clearColor];
titleLabel.text = TFLocalizedString(@"点击/滑动可继续阅读");
textColor = [textColor colorWithAlphaComponent:0.4];
titleLabel.textColor = textColor;
titleLabel.textAlignment = NSTextAlignmentCenter;
titleLabel.font = kFont17;
[self addSubview:titleLabel];
}
}
- (void)setAdvertModel:(TFAdvertModel *)advertModel
{
[super setAdvertModel:advertModel];
self.heightScale = 1.0 / 1.2;
if (advertModel.ad_width > 0 && advertModel.ad_height > 0) {
self.heightScale = (CGFloat)advertModel.ad_height / advertModel.ad_width;
}
if (self.position == TFAdvertisementPositionEnd) {
self.heightScale = 1.0 / 1.2;
}
}
/// 观看激励视频
- (void)watchVideo
{
WXYZ_ADPangolinVideo *video = [[WXYZ_ADPangolinVideo alloc] initWithFrame:CGRectZero advertisementType:self.type advertisementPosition:self.position];
// [video show];
}
@end
@@ -0,0 +1,31 @@
//
// WXYZ_AnnouncementView.h
// GKADRollingView
//
// Created by Gao on 2017/2/16.
// Copyright © 2017年 gao. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "WXYZ_AnnouncementViewCollectionViewCell.h"
@interface WXYZ_AnnouncementView : UIView
typedef void(^WXYZ_AnnouncementViewTapBlock) (NSString *path, NSUInteger index);
@property (nonatomic, copy) WXYZ_AnnouncementViewTapBlock clickAdBlock;
@property (nonatomic, strong) NSArray<TFAnnouncementModel *> *modelArr;
@property (nonatomic, strong) UIColor *titleColor; // default is orange
@property (nonatomic, strong) UIFont *textFont; // default is 12
@property (nonatomic, strong) UIColor *textColor; // default is black
@property (nonatomic, assign) NSTimeInterval duration; // default is 3s
/// 是否居中,默认靠左
@property (nonatomic, assign) BOOL isCenter;
@end
@@ -0,0 +1,132 @@
//
// WXYZ_AnnouncementView.m
// GKADRollingView
//
// Created by Gao on 2017/2/16.
// Copyright © 2017年 gao. All rights reserved.
//
#import "WXYZ_AnnouncementView.h"
@interface WXYZ_AnnouncementView () <UICollectionViewDelegate,UICollectionViewDataSource>
{
NSTimer *_timer;
UICollectionView *_collectionView;
}
@property (nonatomic, assign) NSInteger visibleItems;
@end
@implementation WXYZ_AnnouncementView
- (instancetype)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame]) {
[self createSubviews];
}
return self;
}
- (void)createSubviews
{
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
layout.minimumLineSpacing = 0;
layout.minimumInteritemSpacing = 0;
layout.scrollDirection = UICollectionViewScrollDirectionVertical;
layout.itemSize = CGSizeMake(SCREEN_WIDTH - 2 * kMargin, kLabelHeight);
_collectionView = [[UICollectionView alloc] initWithFrame:CGRectMake(0, 0, self.frame.size.width, self.frame.size.height) collectionViewLayout:layout];
_collectionView.showsVerticalScrollIndicator = NO;
_collectionView.pagingEnabled = YES;
_collectionView.scrollEnabled = NO;
_collectionView.delegate = self;
_collectionView.dataSource = self;
_collectionView.backgroundColor = [UIColor clearColor];
[self addSubview:_collectionView];
[_collectionView registerClass:[WXYZ_AnnouncementViewCollectionViewCell class] forCellWithReuseIdentifier:@"WXYZ_AnnouncementViewCollectionViewCell"];
[_collectionView mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.mas_equalTo(self);
}];
}
- (void)setModelArr:(NSArray<TFAnnouncementModel *> *)modelArr
{
if (_modelArr != modelArr) {
_modelArr = modelArr;
[_collectionView reloadData];
if (_timer == nil) {
_timer = [NSTimer timerWithTimeInterval:_duration?:5 target:self selector:@selector(nextPage) userInfo:nil repeats:YES];
NSRunLoop *runloop = [NSRunLoop currentRunLoop];
[runloop addTimer:_timer forMode:NSRunLoopCommonModes];
}
}
}
- (void)starTimer
{
//开启定时器
[_timer setFireDate:[NSDate distantPast]];
}
- (void)stopTimer
{
//暂停定时器
[_timer setFireDate:[NSDate distantFuture]];
}
- (void)nextPage
{
@try {
if (self.visibleItems == _modelArr.count) {
self.visibleItems = 0;
[self->_collectionView scrollToItemAtIndexPath:[NSIndexPath indexPathForRow:self.visibleItems inSection:0] atScrollPosition:UICollectionViewScrollPositionNone animated:NO];
}
self.visibleItems++;
dispatch_async(dispatch_get_main_queue(), ^{
if (self.visibleItems < _modelArr.count) {
[self->_collectionView scrollToItemAtIndexPath:[NSIndexPath indexPathForRow:self.visibleItems inSection:0] atScrollPosition:UICollectionViewScrollPositionNone animated:YES];
}
});
} @catch (NSException *exception) {
} @finally {
}
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
return _modelArr.count + 1;
}
- (WXYZ_AnnouncementViewCollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
WXYZ_AnnouncementViewCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"WXYZ_AnnouncementViewCollectionViewCell" forIndexPath:indexPath];
cell.textColor = self.textColor;
cell.isCenter = self.isCenter;
if (indexPath.row == 0) {
cell.announcementModel = _modelArr.lastObject;
} else {
if (indexPath.row - 1 >= 0 && indexPath.row - 1 < _modelArr.count) {
cell.announcementModel = _modelArr[indexPath.row - 1];
} else {
cell.announcementModel = _modelArr.firstObject;
}
}
return cell;
}
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
if (indexPath.row == 0) {
self.clickAdBlock(_modelArr.lastObject.content, indexPath.row);
} else {
self.clickAdBlock(_modelArr[indexPath.row - 1].content, indexPath.row - 1);
}
}
@end
@@ -0,0 +1,25 @@
//
// WXYZ_AnnouncementViewCollectionViewCell.h
// GKADRollingView
//
// Created by Gao on 2017/2/16.
// Copyright © 2017年 gao. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "TFBookRackModel.h"
@interface WXYZ_AnnouncementViewCollectionViewCell : UICollectionViewCell
@property (nonatomic, strong) TFAnnouncementModel *announcementModel;
@property (nonatomic, strong) UIColor *titleColor; // default is orange
@property (nonatomic, strong) UIFont *textFont; // default is 12
@property (nonatomic, strong) UIColor *textColor; // default is black
/// 文字是否居中
@property (nonatomic, assign) BOOL isCenter;
@end
@@ -0,0 +1,83 @@
//
// WXYZ_AnnouncementViewCollectionViewCell.m
// GKADRollingView
//
// Created by Gao on 2017/2/16.
// Copyright © 2017年 gao. All rights reserved.
//
#import "WXYZ_AnnouncementViewCollectionViewCell.h"
@interface WXYZ_AnnouncementViewCollectionViewCell ()
@property (nonatomic, strong) UILabel *titleLabel;
@property (nonatomic, strong) UIImageView *headerImg;
@end
@implementation WXYZ_AnnouncementViewCollectionViewCell
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[self createSubviews];
}
return self;
}
- (void)createSubviews
{
_titleLabel = [[UILabel alloc] init];
_titleLabel.font = _textFont?:kFont12;
_titleLabel.textColor = _textColor?:[UIColor blackColor];
_titleLabel.numberOfLines = 1;
[self addSubview:_titleLabel];
_headerImg = [[UIImageView alloc] init];
[_headerImg setImage:[[UIImage imageNamed:@"rack_notice"] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]];
_headerImg.tintColor = kMainColor;
[self addSubview:_headerImg];
[_headerImg mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(0);
make.top.mas_equalTo(0);
make.bottom.mas_equalTo(self.mas_bottom);
make.width.mas_equalTo(self.mas_height);
}];
[_titleLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(_headerImg.mas_right).with.offset(kHalfMargin);
make.top.mas_equalTo(0);
make.bottom.mas_equalTo(self.mas_bottom);
#if TF_Sign_Mode
make.right.mas_equalTo(self.mas_right).with.offset(- kHalfMargin - 100);
#else
make.right.mas_equalTo(self.mas_right).with.offset(- kHalfMargin);
#endif
}];
}
- (void)setAnnouncementModel:(TFAnnouncementModel *)announcementModel
{
_announcementModel = announcementModel;
_titleLabel.text = announcementModel.title;
}
- (void)setTextColor:(UIColor *)textColor {
_titleLabel.textColor = textColor;
}
- (void)setIsCenter:(BOOL)isCenter {
if (isCenter) {
[_titleLabel mas_remakeConstraints:^(MASConstraintMaker *make) {
make.center.equalTo(self);
}];
_headerImg.hidden = YES;
}
}
@end
@@ -0,0 +1,28 @@
//
// UIView+CKViewCategory.h
// CKAudioProgress
//
// Created by guo on 2019/5/14.
// Copyright © 2019 guo. All rights reserved.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface CALayer (CKLayerCategory)
@property (nonatomic) CGFloat ckOriginY;
@property (nonatomic) CGFloat ckOriginX;
@property (nonatomic) CGFloat ckWidth;
@end
@interface UIView (CKViewCategory)
@property (nonatomic) CGFloat ckOriginY;
@property (nonatomic) CGFloat ckCenterX;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,66 @@
//
// UIView+CKViewCategory.m
// CKAudioProgress
//
// Created by guo on 2019/5/14.
// Copyright © 2019 guo. All rights reserved.
//
#import "CALayer+CKViewCategory.h"
@implementation CALayer (CKLayerCategory)
- (CGFloat)ckOriginX {
return self.frame.origin.x;
}
- (void)setCkOriginX:(CGFloat)ckOriginX {
CGRect frame = self.frame;
frame.origin.x = ckOriginX;
self.frame = frame;
}
- (CGFloat)ckOriginY {
return self.frame.origin.y;
}
- (void)setCkOriginY:(CGFloat)ckOriginY {
CGRect frame = self.frame;
frame.origin.y = ckOriginY;
self.frame = frame;
}
- (CGFloat)ckWidth {
return self.frame.size.width;
}
- (void)setCkWidth:(CGFloat)ckWidth {
CGRect frame = self.frame;
frame.size.width = ckWidth;
self.frame = frame;
}
@end
@implementation UIView (CKViewCategory)
- (CGFloat)ckOriginY {
return self.frame.origin.y;
}
- (void)setCkOriginY:(CGFloat)ckOriginY {
CGRect frame = self.frame;
frame.origin.y = ckOriginY;
self.frame = frame;
}
- (CGFloat)ckCenterX {
return self.center.x;
}
- (void)setCkCenterX:(CGFloat)ckCenterX {
CGPoint center = self.center;
center.x = ckCenterX;
self.center = center;
}
@end
@@ -0,0 +1,59 @@
//
// CKAudioProgressView.h
// CKAudioProgress
//
// Created by guo on 2019/5/14.
// Copyright © 2019 guo. All rights reserved.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@protocol CKAudioProgressViewDelegate <NSObject>
@optional
- (void)audioProgressTouchBegin;
- (void)audioProgresstouchEndhCurrentTime:(NSInteger)currentTime totalTime:(NSInteger)totalTime;
@end
typedef NS_ENUM(NSInteger, CKAudioProgressType) {
CKAudioProgressTypeTimeline ///表示拖拽view是个时间进度
};
@interface CKAudioProgressView : UIView
@property (nonatomic, strong) UIColor *cachedBgColor; ///缓冲进度条背景颜色
@property (nonatomic, strong) UIColor *progressBgColor; ///进度条默认填充背景色
/**
@desc 已经播放的进度条渐变色, 存储 CGColorRef 对象的数组
@note 该属性和 playedBgColor 二选一
*/
@property (nonatomic, copy ) NSArray *colors;
/**
@desc 已经播放的进度条背景颜色
@note 该属性和colors 二选一
*/
@property (nonatomic, strong) UIColor *playedBgColor;
@property (nonatomic, assign) CGFloat cornerRadius;
@property (nonatomic, assign) CGRect slideViewBounds; ///拖拽view(圆点或时间进度)的大小
@property (nonatomic, assign) NSInteger totalTimeLength;
@property (nonatomic, weak ) id<CKAudioProgressViewDelegate> delegate;
- (instancetype)initWithFrame:(CGRect)frame type:(CKAudioProgressType)progressType;
/**
@note 修改当前进度,可以根据后面参数计算出当前播放时长并显示在时间lable上
@param progress 进度百分比
@param audioLength 总时间
*/
- (void)updateProgress:(CGFloat)progress audioLength:(NSInteger)audioLength;
- (void)updateCacheProgress:(CGFloat)progress;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,324 @@
//
// CKAudioProgressView.m
// CKAudioProgress
//
// Created by guo on 2019/5/14.
// Copyright © 2019 guo. All rights reserved.
//
#import "CKAudioProgressView.h"
#import "CALayer+CKViewCategory.h"
@interface CKAudioProgressView()
@property (nonatomic, assign) BOOL isSliding;
@property (nonatomic, assign) NSInteger audioLength;
@property (nonatomic, strong) UIView *slideView;
@property (nonatomic, strong) UILabel *lb_time;
// 进度指示器
@property (nonatomic, strong) UILabel *lb_indicator;
@property (nonatomic, strong) CALayer *bgLayer;
@property (nonatomic, strong) CALayer *cachedLayer;
@property (nonatomic, strong) UIView *progressLineView;
@end
@implementation CKAudioProgressView
- (instancetype)initWithFrame:(CGRect)frame type:(CKAudioProgressType)progressType {
self = [super initWithFrame:frame];
if (self) {
[self initViews];
}
return self;
}
- (void)initViews {
[self.layer addSublayer:self.bgLayer];
[self.layer addSublayer:self.cachedLayer];
[self addSubview:self.progressLineView];
[self addSubview:self.lb_indicator];
[self addSubview:self.lb_time];
[self addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapGesture:)]];
[self setNeedsLayout];
}
- (void)layoutSubviews
{
[super layoutSubviews];
CGFloat originY = (self.bounds.size.height-2)/2;
self.bgLayer.ckOriginY = originY;
self.bgLayer.ckWidth = self.frame.size.width;
self.cachedLayer.ckOriginY = originY;
self.progressLineView.ckOriginY = originY;
_lb_time.ckOriginY = (self.bounds.size.height-_lb_time.bounds.size.height)/2;
}
#pragma mark - Action
static CGFloat percent = 0.0;
- (void)timeLabelPanGesture:(UIPanGestureRecognizer *)gesture
{
UIGestureRecognizerState state = gesture.state;
if (UIGestureRecognizerStateBegan == state) {
_isSliding = YES;
percent = 0.0;
if (_delegate && [_delegate respondsToSelector:@selector(audioProgressTouchBegin)]) {
[_delegate audioProgressTouchBegin];
}
[UIView animateWithDuration:kAnimatedDurationFast animations:^{
self.lb_indicator.alpha = 1;
}];
} else if (UIGestureRecognizerStateChanged == state) {
CGPoint translation = [gesture translationInView:self];
CGPoint slideViewCenter = CGPointMake(gesture.view.center.x+ translation.x, gesture.view.center.y);
slideViewCenter.x = MAX(gesture.view.bounds.size.width/2, slideViewCenter.x);
slideViewCenter.x = MIN(self.bounds.size.width-gesture.view.bounds.size.width/2, slideViewCenter.x);
gesture.view.center = slideViewCenter;
[gesture setTranslation:CGPointZero inView:self];
_progressLineView.width = gesture.view.frame.origin.x;
CGFloat totalWith = self.bounds.size.width - _lb_time.bounds.size.width;
NSInteger audioProgress = gesture.view.frame.origin.x / totalWith * self.audioLength;
percent = gesture.view.frame.origin.x / totalWith;
[self setProgress:audioProgress total:self.audioLength];
self.lb_indicator.ckOriginY = _lb_time.origin.y - 45;
self.lb_indicator.ckCenterX = _lb_time.center.x;
} else if (UIGestureRecognizerStateEnded == state || UIGestureRecognizerStateCancelled == state) {
_isSliding = NO;
NSInteger audioProgress = gesture.view.frame.origin.x / (self.bounds.size.width - _lb_time.bounds.size.width) * self.audioLength;
[UIView animateWithDuration:kAnimatedDurationFast animations:^{
self.lb_indicator.alpha = 0;
}];
if (_delegate && [_delegate respondsToSelector:@selector(audioProgresstouchEndhCurrentTime:totalTime:)]) {
[_delegate audioProgresstouchEndhCurrentTime:audioProgress totalTime:self.audioLength];
}
}
}
- (void)tapGesture:(UITapGestureRecognizer *)gesture
{
CGPoint translation = [gesture locationInView:self];
NSInteger audioProgress = translation.x / self.bounds.size.width * self.audioLength;
[self setProgress:audioProgress total:self.audioLength];
if (_delegate && [_delegate respondsToSelector:@selector(audioProgresstouchEndhCurrentTime:totalTime:)]) {
[_delegate audioProgresstouchEndhCurrentTime:audioProgress totalTime:self.audioLength];
}
}
- (void)setProgress:(NSInteger)progress total:(NSInteger)total
{
NSString *title = @"";
if (total >= 3600) {
title = [NSString stringWithFormat:@"%@/%@", [TFUtilsHelper getHourTimeTransformationWithTotalTimeLenght:progress], [TFUtilsHelper getHourTimeTransformationWithTotalTimeLenght:total]];
} else {
title = [NSString stringWithFormat:@"%@/%@", [TFUtilsHelper getMinuteTimeTransformationWithTotalTimeLenght:progress], [TFUtilsHelper getMinuteTimeTransformationWithTotalTimeLenght:total]];
}
_lb_time.text = title;
if (self.lb_indicator.alpha > 0) {
self.lb_indicator.text = title;
}
}
- (void)updateProgress:(CGFloat)progress audioLength:(NSInteger)audioLength
{
if (isnan(percent) || isinf(percent)) {
percent = 0;
}
if (audioLength <= 0) {
_lb_time.text = @"00:00/00:00";
self.lb_indicator.text = @"00:00/00:00";
_lb_time.xtfei_x = 0;
_progressLineView.xtfei_width = 0;
return;
}
if (progress > 1) {
percent = 1;
} else if (progress < 0) {
percent = 0;
} else {
percent = progress;
}
if (!_isSliding) {
self.audioLength = audioLength;
[self setProgress:(NSInteger)(percent*audioLength) total:audioLength];
CGFloat totalWith = self.bounds.size.width-_lb_time.bounds.size.width;
CGFloat playedWidth = totalWith*percent;
_progressLineView.width = playedWidth;
_lb_time.ckCenterX = playedWidth+_lb_time.bounds.size.width/2;
}
}
- (void)updateCacheProgress:(CGFloat)progress
{
if (!isnan(progress)) {
self.cachedLayer.ckWidth = self.frame.size.width * progress;
}
}
#pragma mark - Getter & Setter
- (void)setCachedBgColor:(UIColor *)cachedBgColor {
if (cachedBgColor) {
_cachedBgColor = cachedBgColor;
self.cachedLayer.backgroundColor = cachedBgColor.CGColor;
}
}
- (void)setProgressBgColor:(UIColor *)progressBgColor {
if (progressBgColor) {
_progressBgColor = progressBgColor;
self.bgLayer.backgroundColor = progressBgColor.CGColor;
}
}
- (void)setCornerRadius:(CGFloat)cornerRadius {
_cornerRadius = cornerRadius;
self.cornerRadius = cornerRadius;
self.bgLayer.cornerRadius = cornerRadius;
self.cachedLayer.cornerRadius = cornerRadius;
self.progressLineView.layer.cornerRadius = cornerRadius;
}
- (void)setColors:(NSArray *)colors {
if (!_playedBgColor && colors) {
_colors = colors;
}
}
- (void)setPlayedBgColor:(UIColor *)playedBgColor {
if (!_colors && playedBgColor) {
_playedBgColor = playedBgColor;
self.progressLineView.backgroundColor = playedBgColor;
}
}
- (void)setSlideViewBounds:(CGRect)slideViewBounds {
if (slideViewBounds.size.width > 0) {
_slideViewBounds = slideViewBounds;
self.lb_time.bounds = slideViewBounds;
[self setNeedsLayout];
}
}
- (void)setTotalTimeLength:(NSInteger)totalTimeLength
{
_totalTimeLength = totalTimeLength;
if (totalTimeLength >= 0) {
NSString *title = @"";
if (totalTimeLength >= 3600) {
title = [NSString stringWithFormat:@"%@/%@", [TFUtilsHelper getHourTimeTransformationWithTotalTimeLenght:totalTimeLength], [TFUtilsHelper getHourTimeTransformationWithTotalTimeLenght:totalTimeLength]];
} else {
title = [NSString stringWithFormat:@"%@/%@", [TFUtilsHelper getMinuteTimeTransformationWithTotalTimeLenght:totalTimeLength], [TFUtilsHelper getMinuteTimeTransformationWithTotalTimeLenght:totalTimeLength]];
}
_lb_time.xtfei_width = [TFViewHelper getDynamicWidthWithLabelFont:[UIFont systemFontOfSize:10] labelHeight:20 labelText:title] + kHalfMargin;
_lb_indicator.xtfei_width = [TFViewHelper getDynamicWidthWithLabelFont:kFont12 labelHeight:30 labelText:title] + kHalfMargin;
}
}
- (CALayer *)bgLayer {
if (!_bgLayer) {
_bgLayer = [CALayer layer];
_bgLayer.backgroundColor = _progressBgColor ? _progressBgColor.CGColor : [UIColor colorWithRed:242/255.0 green:242/255.0 blue:242/255.0 alpha:1].CGColor;
_bgLayer.frame = CGRectMake(0, (self.bounds.size.height-2)/2, self.bounds.size.width, 2);
}
return _bgLayer;
}
- (CALayer *)cachedLayer {
if (!_cachedLayer) {
_cachedLayer = [CALayer layer];
_cachedLayer.backgroundColor = [UIColor colorWithRed:230/255.0 green:230/255.0 blue:230/255.0 alpha:1.0].CGColor;
_cachedLayer.frame = CGRectMake(0, (self.bounds.size.height-2)/2, 0, 2);
}
return _cachedLayer;
}
- (UIView *)progressLineView {
if (!_progressLineView) {
_progressLineView = [[UIView alloc] init];
_progressLineView.frame = CGRectMake(0, (self.bounds.size.height - 2 ) / 2, 0, 2);
}
return _progressLineView;
}
- (UIView *)slideView {
if (!_slideView) {
_slideView = [UIView new];
_slideView.frame = CGRectMake(-(24-24/2-12/2), 0, 24, 24);
CAShapeLayer *dotLayer = [CAShapeLayer layer];
dotLayer.fillColor = [UIColor whiteColor].CGColor;
dotLayer.frame = CGRectMake((24-12)/2, (24-12)/2, 12, 12);
dotLayer.cornerRadius = 6;
dotLayer.shadowColor = [UIColor colorWithRed:255/255.0 green:120/255.0 blue:2/255.0 alpha:1.0].CGColor;
dotLayer.shadowOffset = CGSizeMake(0,0);
dotLayer.shadowOpacity = 1;
dotLayer.shadowRadius = 10;
UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:dotLayer.bounds];
dotLayer.path = path.CGPath;
[_slideView.layer addSublayer:dotLayer];
}
return _slideView;
}
- (UILabel *)lb_indicator
{
if (!_lb_indicator) {
_lb_indicator = [[UILabel alloc] init];
_lb_indicator.font = kFont12;
_lb_indicator.textAlignment = NSTextAlignmentCenter;
_lb_indicator.textColor = kWhiteColor;
_lb_indicator.frame = CGRectMake(0, 0, 92, 30);
_lb_indicator.alpha = 0;
_lb_indicator.layer.cornerRadius = 15;
_lb_indicator.layer.backgroundColor = kColorRGBA(0, 0, 0, 0.7).CGColor;
_lb_indicator.userInteractionEnabled = YES;
}
return _lb_indicator;
}
- (UILabel *)lb_time {
if (!_lb_time) {
_lb_time = [UILabel new];
_lb_time.font = [UIFont systemFontOfSize:10];
_lb_time.textAlignment = NSTextAlignmentCenter;
_lb_time.textColor = kGrayTextDeepColor;
_lb_time.frame = CGRectMake(0, 0, 72, 20);
_lb_time.layer.cornerRadius = 10;
_lb_time.layer.shadowColor = [UIColor colorWithRed:87/255.0 green:92/255.0 blue:111/255.0 alpha:0.5].CGColor;
_lb_time.layer.shadowOffset = CGSizeMake(0,0);
_lb_time.layer.shadowOpacity = 3;
_lb_time.layer.shadowRadius = 1;
_lb_time.layer.backgroundColor = kWhiteColor.CGColor;
_lb_time.userInteractionEnabled = YES;
[_lb_time addGestureRecognizer:[[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(timeLabelPanGesture:)]];
}
return _lb_time;
}
@end
@@ -0,0 +1,51 @@
//
// WXYZ_BadgeView.h
// WXReader
//
// Created by Andrew on 2020/8/7.
// Copyright © 2020 Andrew. All rights reserved.
//
#import <Foundation/Foundation.h>
FOUNDATION_EXPORT CGFloat const RKNotificationHubDefaultDiameter;
NS_ASSUME_NONNULL_BEGIN
@interface WXYZ_BadgeView : NSObject
//%%% setup
- (id)initWithView:(UIView *)view;
- (id)initWithBarButtonItem:(UIBarButtonItem *)barButtonItem;
//%%% adjustment methods
- (void)setView:(UIView *)view andCount:(int)startCount;
- (void)setCircleAtFrame:(CGRect)frame;
- (void)setCircleColor:(UIColor*)circleColor labelColor:(UIColor*)labelColor;
- (void)setCircleBorderColor:(UIColor *)color borderWidth:(CGFloat)width;
- (void)moveCircleByX:(CGFloat)x Y:(CGFloat)y;
- (void)scaleCircleSizeBy:(CGFloat)scale;
@property (nonatomic, strong) UIFont *countLabelFont;
//%%% changing the count
- (void)increment;
- (void)incrementBy:(int)amount;
- (void)decrement;
- (void)decrementBy:(int)amount;
@property (nonatomic, assign) int count;
@property (nonatomic, assign) int maxCount;
//%%% hiding / showing the count
- (void)hideCount;
- (void)showCount;
//%%% animations
- (void)pop;
- (void)blink;
- (void)bump;
@property (nonatomic)UIView *hubView;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,409 @@
//
// WXYZ_BadgeView.m
// WXReader
//
// Created by Andrew on 2020/8/7.
// Copyright © 2020 Andrew. All rights reserved.
//
#import "WXYZ_BadgeView.h"
#import <QuartzCore/QuartzCore.h>
//%%% default diameter
CGFloat const RKNotificationHubDefaultDiameter = 30;
static CGFloat const kCountMagnitudeAdaptationRatio = 0.3;
//%%% pop values
static CGFloat const kPopStartRatio = .85;
static CGFloat const kPopOutRatio = 1.05;
static CGFloat const kPopInRatio = .95;
//%%% blink values
static CGFloat const kBlinkDuration = 0.1;
static CGFloat const kBlinkAlpha = 0.1;
//%%% bump values
static CGFloat const kFirstBumpDistance = 8.0;
static CGFloat const kBumpTimeSeconds = 0.13;
static CGFloat const SECOND_BUMP_DIST = 4.0;
static CGFloat const kBumpTimeSeconds2 = 0.1;
@interface RKView : UIView
@property (nonatomic) BOOL isUserChangingBackgroundColor;
@end
@implementation RKView
- (void)setBackgroundColor:(UIColor *)backgroundColor
{
if (self.isUserChangingBackgroundColor) {
super.backgroundColor = backgroundColor;
self.isUserChangingBackgroundColor = NO;
}
}
@end
@implementation WXYZ_BadgeView{
int curOrderMagnitude;
UILabel *countLabel;
RKView *redCircle;
CGPoint initialCenter;
CGRect baseFrame;
CGRect initialFrame;
BOOL isIndeterminateMode;
}
@synthesize hubView;
#pragma mark - SETUP
- (id)initWithView:(UIView *)view
{
self = [super init];
if (!self) return nil;
_maxCount = 100000;
[self setView:view andCount:0];
return self;
}
- (id)initWithBarButtonItem:(UIBarButtonItem *)barButtonItem
{
self = [self initWithView:[barButtonItem valueForKey:@"view"]];
[self scaleCircleSizeBy:0.7];
[self moveCircleByX:-5.0 Y:0];
return self;
}
//%%% give this a view and an initial count (0 hides the notification circle)
// and it will make a hub for you
- (void)setView:(UIView *)view andCount:(int)startCount
{
curOrderMagnitude = 0;
CGRect frame = view.frame;
isIndeterminateMode = NO;
redCircle = [[RKView alloc]init];
redCircle.userInteractionEnabled = NO;
redCircle.isUserChangingBackgroundColor = YES;
redCircle.backgroundColor = [UIColor redColor];
countLabel = [[UILabel alloc]initWithFrame:redCircle.frame];
countLabel.userInteractionEnabled = NO;
self.count = startCount;
[countLabel setTextAlignment:NSTextAlignmentCenter];
countLabel.textColor = [UIColor whiteColor];
countLabel.backgroundColor = [UIColor clearColor];
[self setCircleAtFrame:CGRectMake(frame.size.width- (RKNotificationHubDefaultDiameter*2/3), -RKNotificationHubDefaultDiameter/3, RKNotificationHubDefaultDiameter, RKNotificationHubDefaultDiameter)];
[view addSubview:redCircle];
[view addSubview:countLabel];
[view bringSubviewToFront:redCircle];
[view bringSubviewToFront:countLabel];
hubView = view;
[self checkZero];
}
//%%% set the frame of the notification circle relative to the button
- (void)setCircleAtFrame:(CGRect)frame
{
[redCircle setFrame:frame];
initialCenter = CGPointMake(frame.origin.x+frame.size.width/2, frame.origin.y+frame.size.height/2);
baseFrame = frame;
initialFrame = frame;
countLabel.frame = redCircle.frame;
redCircle.layer.cornerRadius = frame.size.height/2;
[countLabel setFont:[UIFont fontWithName:@"HelveticaNeue" size:frame.size.width/2]];
[self expandToFitLargerDigits];
}
//%%% moves the circle by x amount on the x axis and y amount on the y axis
- (void)moveCircleByX:(CGFloat)x Y:(CGFloat)y
{
CGRect frame = redCircle.frame;
frame.origin.x += x;
frame.origin.y += y;
[self setCircleAtFrame:frame];
}
//%%% changes the size of the circle. setting a scale of 1 has no effect
- (void)scaleCircleSizeBy:(CGFloat)scale
{
CGRect fr = initialFrame;
CGFloat width = fr.size.width * scale;
CGFloat height = fr.size.height * scale;
CGFloat wdiff = (fr.size.width - width) / 2;
CGFloat hdiff = (fr.size.height - height) / 2;
CGRect frame = CGRectMake(fr.origin.x + wdiff, fr.origin.y + hdiff, width, height);
[self setCircleAtFrame:frame];
}
//%%% change the color of the notification circle
- (void)setCircleColor:(UIColor*)circleColor labelColor:(UIColor*)labelColor
{
redCircle.isUserChangingBackgroundColor = YES;
redCircle.backgroundColor = circleColor;
[countLabel setTextColor:labelColor];
}
- (void)setCircleBorderColor:(UIColor *)color borderWidth:(CGFloat)width {
redCircle.layer.borderColor = color.CGColor;
redCircle.layer.borderWidth = width;
}
- (void)hideCount
{
countLabel.hidden = YES;
isIndeterminateMode = YES;
}
- (void)showCount
{
isIndeterminateMode = NO;
[self checkZero];
}
#pragma mark - ATTRIBUTES
//%%% increases count by 1
- (void)increment
{
[self incrementBy:1];
}
//%%% increases count by amount
- (void)incrementBy:(int)amount
{
self.count += amount;
}
//%%% decreases count
- (void)decrement
{
[self decrementBy:1];
}
//%%% decreases count by amount
- (void)decrementBy:(int)amount
{
if (amount >= self.count) {
self.count = 0;
return;
}
self.count -= amount;
}
//%%% set the count yourself
- (void)setCount:(int)newCount
{
_count = newCount;
NSString *labelText = [NSString stringWithFormat:@"%@", @(self.count)];
if (_count > self.maxCount){
labelText = [NSString stringWithFormat:@"%@+", @(self.maxCount)];
}
countLabel.text = labelText;
[self checkZero];
[self expandToFitLargerDigits];
}
//%% set the font of the label
- (void)setCountLabelFont:(UIFont *)font
{
[countLabel setFont:font];
}
- (UIFont *)countLabelFont
{
return countLabel.font;
}
#pragma mark - ANIMATION
//%%% animation that resembles facebook's pop
- (void)pop
{
const float height = baseFrame.size.height;
const float width = baseFrame.size.width;
const float pop_start_h = height * kPopStartRatio;
const float pop_start_w = width * kPopStartRatio;
const float time_start = 0.05;
const float pop_out_h = height * kPopOutRatio;
const float pop_out_w = width * kPopOutRatio;
const float time_out = .2;
const float pop_in_h = height * kPopInRatio;
const float pop_in_w = width * kPopInRatio;
const float time_in = .05;
const float pop_end_h = height;
const float pop_end_w = width;
const float time_end = 0.05;
CABasicAnimation *startSize = [CABasicAnimation animationWithKeyPath:@"cornerRadius"];
startSize.duration = time_start;
startSize.beginTime = 0;
startSize.fromValue = @(pop_end_h / 2);
startSize.toValue = @(pop_start_h / 2);
startSize.removedOnCompletion = FALSE;
CABasicAnimation *outSize = [CABasicAnimation animationWithKeyPath:@"cornerRadius"];
outSize.duration = time_out;
outSize.beginTime = time_start;
outSize.fromValue = startSize.toValue;
outSize.toValue = @(pop_out_h / 2);
outSize.removedOnCompletion = FALSE;
CABasicAnimation *inSize = [CABasicAnimation animationWithKeyPath:@"cornerRadius"];
inSize.duration = time_in;
inSize.beginTime = time_start+time_out;
inSize.fromValue = outSize.toValue;
inSize.toValue = @(pop_in_h / 2);
inSize.removedOnCompletion = FALSE;
CABasicAnimation *endSize = [CABasicAnimation animationWithKeyPath:@"cornerRadius"];
endSize.duration = time_end;
endSize.beginTime = time_in+time_out+time_start;
endSize.fromValue = inSize.toValue;
endSize.toValue = @(pop_end_h / 2);
endSize.removedOnCompletion = FALSE;
CAAnimationGroup *group = [CAAnimationGroup animation];
[group setDuration: time_start+time_out+time_in+time_end];
[group setAnimations:@[startSize, outSize, inSize, endSize]];
[redCircle.layer addAnimation:group forKey:nil];
[UIView animateWithDuration:time_start animations:^{
CGRect frame = redCircle.frame;
CGPoint center = redCircle.center;
frame.size.height = pop_start_h;
frame.size.width = pop_start_w;
redCircle.frame = frame;
redCircle.center = center;
}completion:^(BOOL complete){
[UIView animateWithDuration:time_out animations:^{
CGRect frame = redCircle.frame;
CGPoint center = redCircle.center;
frame.size.height = pop_out_h;
frame.size.width = pop_out_w;
redCircle.frame = frame;
redCircle.center = center;
}completion:^(BOOL complete){
[UIView animateWithDuration:time_in animations:^{
CGRect frame = redCircle.frame;
CGPoint center = redCircle.center;
frame.size.height = pop_in_h;
frame.size.width = pop_in_w;
redCircle.frame = frame;
redCircle.center = center;
}completion:^(BOOL complete){
[UIView animateWithDuration:time_end animations:^{
CGRect frame = redCircle.frame;
CGPoint center = redCircle.center;
frame.size.height = pop_end_h;
frame.size.width = pop_end_w;
redCircle.frame = frame;
redCircle.center = center;
}];
}];
}];
}];
}
//%%% animation that flashes on an off
- (void)blink
{
[self setAlpha:kBlinkAlpha];
[UIView animateWithDuration:kBlinkDuration animations:^{
[self setAlpha:1];
}completion:^(BOOL complete){
[UIView animateWithDuration:kBlinkDuration animations:^{
[self setAlpha:kBlinkAlpha];
}completion:^(BOOL complete){
[UIView animateWithDuration:kBlinkDuration animations:^{
[self setAlpha:1];
}];
}];
}];
}
//%%% animation that jumps similar to OSX dock icons
- (void)bump
{
if (!CGPointEqualToPoint(initialCenter,redCircle.center)) {
//%%% canel previous animation
}
[self bumpCenterY:0];
[UIView animateWithDuration:kBumpTimeSeconds animations:^{
[self bumpCenterY:kFirstBumpDistance];
}completion:^(BOOL complete){
[UIView animateWithDuration:kBumpTimeSeconds animations:^{
[self bumpCenterY:0];
}completion:^(BOOL complete){
[UIView animateWithDuration:kBumpTimeSeconds2 animations:^{
[self bumpCenterY:SECOND_BUMP_DIST];
}completion:^(BOOL complete){
[UIView animateWithDuration:kBumpTimeSeconds2 animations:^{
[self bumpCenterY:0];
}];
}];
}];
}];
}
#pragma mark - HELPERS
//%%% changes the Y origin of the notification circle
- (void)bumpCenterY:(float)yVal
{
CGPoint center = redCircle.center;
center.y = initialCenter.y-yVal;
redCircle.center = center;
countLabel.center = center;
}
- (void)setAlpha:(float)alpha
{
redCircle.alpha = alpha;
countLabel.alpha = alpha;
}
//%%% hides the notification if the value is 0
- (void)checkZero
{
if (self.count <= 0) {
redCircle.hidden = YES;
countLabel.hidden = YES;
} else {
redCircle.hidden = NO;
if (!isIndeterminateMode) {
countLabel.hidden = NO;
}
}
}
- (void)expandToFitLargerDigits {
int orderOfMagnitude = log10((double)self.count);
orderOfMagnitude = (orderOfMagnitude >= 2) ? orderOfMagnitude : 1;
CGRect frame = initialFrame;
frame.size.width = initialFrame.size.width * (1 + kCountMagnitudeAdaptationRatio * (orderOfMagnitude - 1));
frame.origin.x = initialFrame.origin.x - (frame.size.width - initialFrame.size.width) / 2;
[redCircle setFrame:frame];
initialCenter = CGPointMake(frame.origin.x+frame.size.width/2, frame.origin.y+frame.size.height/2);
baseFrame = frame;
countLabel.frame = redCircle.frame;
curOrderMagnitude = orderOfMagnitude;
}
@end
@@ -0,0 +1,15 @@
//
// DPBatteryView.h
// WXReader
//
// Created by Andrew on 2018/6/10.
// Copyright © 2018年 Andrew. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface WXYZ_BatteryView : UIView
@property (nonatomic, strong) UIColor *batteryTintColor;
@end
@@ -0,0 +1,216 @@
//
// DPBatteryView.m
// WXReader
//
// Created by Andrew on 2018/6/10.
// Copyright © 2018年 Andrew. All rights reserved.
//
#import "WXYZ_BatteryView.h"
typedef enum : NSUInteger {
WXBatteryStateColorNormal,
WXBatteryStateColorCharging,
WXBatteryStateColorWarning,
} WXBatteryStateColor;
@interface WXYZ_BatteryView ()
{
UIView *batteryView;
UILabel *batteryLabel;
CAShapeLayer *batteryLayer;
CAShapeLayer *layer2;
CGFloat w;
CGFloat lineW;
NSTimer *time;
}
@end
@implementation WXYZ_BatteryView
- (instancetype)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame]) {
[self initialize];
[self createSubViews];
[self batteryLevelChanged];
[self updateTime];
}
return self;
}
- (void)initialize
{
UIDevice *device = [UIDevice currentDevice];
device.batteryMonitoringEnabled = YES;
WS(weakSelf)
[[NSNotificationCenter defaultCenter]
addObserverForName:UIDeviceBatteryLevelDidChangeNotification
object:nil queue:[NSOperationQueue mainQueue]
usingBlock:^(NSNotification *notification) {
[weakSelf batteryLevelChanged];
}];
[[NSNotificationCenter defaultCenter]
addObserverForName:UIDeviceBatteryStateDidChangeNotification
object:nil queue:[NSOperationQueue mainQueue]
usingBlock:^(NSNotification *notification) {
[weakSelf batteryStateChanged];
}];
}
- (void)createSubViews
{
//电池的宽度
w = 25;
//电池的x的坐标
CGFloat x = 0;
//电池的y的坐标
CGFloat y = self.height / 2 - 5;
//电池的线宽
lineW = 1;
//电池的高度
CGFloat h = 10;
//画电池
UIBezierPath *path1 = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(x, y, w, h) cornerRadius:2];
batteryLayer = [CAShapeLayer layer];
batteryLayer.lineWidth = lineW;
batteryLayer.strokeColor = [UIColor grayColor].CGColor;
batteryLayer.fillColor = [UIColor clearColor].CGColor;
batteryLayer.path = [path1 CGPath];
[self.layer addSublayer:batteryLayer];
UIBezierPath *path2 = [UIBezierPath bezierPath];
[path2 moveToPoint:CGPointMake(x+w+1, y+h/3)];
[path2 addLineToPoint:CGPointMake(x+w+1, y+h*2/3)];
layer2 = [CAShapeLayer layer];
layer2.lineWidth = 2;
layer2.strokeColor = [UIColor grayColor].CGColor;
layer2.fillColor = [UIColor clearColor].CGColor;
layer2.path = [path2 CGPath];
[self.layer addSublayer:layer2];
//绘制进度
batteryView = [[UIView alloc] initWithFrame:CGRectMake(x + 1,y + lineW, 0, h - lineW * 2)];
batteryView.layer.cornerRadius = 1;
[self addSubview:batteryView];
batteryLabel = [[UILabel alloc] initWithFrame:CGRectMake(x + w + 5, y, 80, h)];
batteryLabel.textColor = [UIColor grayColor];
batteryLabel.textAlignment = NSTextAlignmentLeft;
batteryLabel.font = kFont10;
[self addSubview:batteryLabel];
time = [NSTimer scheduledTimerWithTimeInterval:1.0f target:self selector:@selector(updateTime) userInfo:nil repeats:YES];
}
- (void)updateTime
{
//获取当前时间
NSDate *now = [NSDate date];
NSCalendar *calendar = [NSCalendar currentCalendar];
NSCalendarUnit unitFlags = NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay | NSCalendarUnitHour | NSCalendarUnitMinute | NSCalendarUnitSecond;
NSDateComponents *dateComponent = [calendar components:unitFlags fromDate:now];
NSInteger hour = [dateComponent hour];
NSInteger minute = [dateComponent minute];
if (hour < 10 && minute < 10) {
batteryLabel.text = [NSString stringWithFormat:@"0%d:0%d",(int)hour, (int)minute];
} else if (hour < 10) {
batteryLabel.text = [NSString stringWithFormat:@"0%d:%d",(int)hour, (int)minute];
} else if (minute < 10) {
batteryLabel.text = [NSString stringWithFormat:@"%d:0%d",(int)hour, (int)minute];
} else {
batteryLabel.text = [NSString stringWithFormat:@"%d:%d",(int)hour, (int)minute];
}
}
// 电池剩余比例
- (void)batteryLevelChanged
{
UIDevice *device = [UIDevice currentDevice];
device.batteryMonitoringEnabled = YES;
float batteryLevel = [device batteryLevel] * 100;
if (batteryLevel < 0) {
batteryLevel = 0;
} else if (batteryLevel > 100){
batteryLevel = 100;
}
if (batteryLevel <= 10) {
[self changeBatteryState:WXBatteryStateColorWarning];
} else {
[self changeBatteryState:WXBatteryStateColorNormal];
}
CGRect frame = batteryView.frame;
frame.size.width = (batteryLevel * (w - lineW * 2)) / 100;
batteryView.frame = frame;
}
- (void)batteryStateChanged
{
switch ([[UIDevice currentDevice] batteryState]) {
case 0: // 未开启监视电池状态
[self changeBatteryState:WXBatteryStateColorNormal];
break;
case 1: // 电池未充电状态
[self changeBatteryState:WXBatteryStateColorNormal];
break;
case 2: // 电池充电状态
[self changeBatteryState:WXBatteryStateColorCharging];
break;
case 3: // 电池充电完成
[self changeBatteryState:WXBatteryStateColorCharging];
break;
default:
break;
}
}
- (void)changeBatteryState:(WXBatteryStateColor)batteryState
{
switch (batteryState) {
case WXBatteryStateColorNormal:
batteryView.backgroundColor = kColorRGBA(131, 131, 131, 1);
break;
case WXBatteryStateColorCharging:
batteryView.backgroundColor = kColorRGBA(75, 216, 102, 1);
break;
case WXBatteryStateColorWarning:
batteryView.backgroundColor = kColorRGBA(252, 62, 46, 1);
break;
default:
break;
}
}
- (void)setBatteryTintColor:(UIColor *)batteryTintColor
{
batteryLayer.strokeColor = batteryTintColor.CGColor;
layer2.strokeColor = batteryTintColor.CGColor;
batteryLabel.textColor = batteryTintColor;
if ([UIDevice currentDevice].batteryState == UIDeviceBatteryStateCharging || [UIDevice currentDevice].batteryState == UIDeviceBatteryStateFull) {
[self changeBatteryState:WXBatteryStateColorCharging];
}
}
- (void)dealloc
{
[time invalidate];
time = nil;
}
@end
@@ -0,0 +1,21 @@
//
// DZMAnimatedTransitioning.h
// DZMAnimatedTransitioning
//
// Created by 邓泽淼 on 2017/12/20.
// Copyright © 2017年 邓泽淼. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import "NSObject+DZM.h"
@interface DZMAnimatedTransitioning : NSObject<UIViewControllerAnimatedTransitioning>
/// 构造方法
- (instancetype _Nullable)initWithOperation:(UINavigationControllerOperation)operation;
/// 构造方法
- (instancetype _Nullable)initWithOperation:(UINavigationControllerOperation)operation duration:(float)duration;
@end
@@ -0,0 +1,209 @@
//
// DZMAnimatedTransitioning.m
// DZMAnimatedTransitioning
//
// Created by 邓泽淼 on 2017/12/20.
// Copyright © 2017年 邓泽淼. All rights reserved.
//
#define DZM_TAG_COVER 818
#import "DZMAnimatedTransitioning.h"
static UIImage *coverImage;
@interface DZMAnimatedTransitioning()
{
UIView *contentView;
}
@property (nonatomic, assign) UINavigationControllerOperation operation;
@property (nonatomic, assign, readonly) float duration;
@end
@implementation DZMAnimatedTransitioning
- (instancetype)initWithOperation:(UINavigationControllerOperation)operation {
return [self initWithOperation:operation duration:0.6];
}
- (instancetype)initWithOperation:(UINavigationControllerOperation)operation duration:(float)duration{
self = [super init];
if (self) {
_duration = duration;
_operation = operation;
}
return self;
}
- (NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext {
return self.duration;
}
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext {
if (self.operation == UINavigationControllerOperationPush) {
[self push:transitionContext];
}else if (self.operation == UINavigationControllerOperationPop) {
[self pop:transitionContext];
}else{}
}
- (void)push:(id<UIViewControllerContextTransitioning>)transitionContext {
UIViewController *to = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
UIViewController *from = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
UIView *containerView = [transitionContext containerView];
UIView *fromView = (from.ATTarget != nil ? from.ATTarget : from.view);
CGRect rect = [fromView convertRect:fromView.bounds toView:containerView];
contentView = [self GetImageView:[self screenCapture:to.view]];
contentView.frame = rect;
if (fromView.layer.cornerRadius > 0.0) {
contentView.layer.cornerRadius = fromView.layer.cornerRadius;
contentView.layer.masksToBounds = YES;
}
[containerView addSubview:contentView];
UIImageView *coverView = [self GetImageView: [self screenCapture:fromView]];
coverView.tag = DZM_TAG_COVER;
coverView.frame = CGRectMake(rect.origin.x - (rect.size.width / 2), rect.origin.y, rect.size.width, rect.size.height);
[containerView addSubview:coverView];
coverView.layer.anchorPoint = CGPointMake(0, 0.5);
coverView.opaque = YES;
coverImage = coverView.image;
CATransform3D transform = CATransform3DMakeRotation(- M_PI_2 , 0.0, 1.0, 0.0);
transform.m34 = 1.0f / 500.0f;
[UIView animateWithDuration:[self transitionDuration:transitionContext] delay:0.0f options:UIViewAnimationOptionCurveEaseInOut animations:^{
coverView.frame = to.view.bounds;
self->contentView.frame = to.view.bounds;
coverView.layer.transform = transform;
} completion:^(BOOL finished) {
coverView.image = nil;
coverView.hidden = YES;
[self->contentView removeFromSuperview];
[containerView addSubview:to.view];
[transitionContext completeTransition:YES];
}];
}
- (void)pop:(id<UIViewControllerContextTransitioning>)transitionContext {
UIViewController *to = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
UIViewController *from = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
UIView *containerView = [transitionContext containerView];
[containerView addSubview:to.view];
contentView = [self GetImageView:[self screenCapture:from.view]];
contentView.frame = from.view.bounds;
[containerView addSubview:contentView];
UIImageView *coverView = [containerView viewWithTag:DZM_TAG_COVER];
coverView.image = coverImage;
coverView.hidden = NO;
[containerView addSubview:coverView];
CATransform3D transform = CATransform3DMakeRotation(0.0, 0.0, 1.0, 0.0);
transform.m34 = 1.0f / 500.0f;
CGFloat book_width = BOOK_WIDTH;
CGFloat book_height = BOOK_HEIGHT;
[UIView animateWithDuration:[self transitionDuration:transitionContext] delay:0.0f options:UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionShowHideTransitionViews animations:^{
self->contentView.frame = CGRectMake(kHalfMargin, (kGeometricHeight(SCREEN_WIDTH, 3, 1) + kLabelHeight + kMargin + kQuarterMargin + PUB_NAVBAR_HEIGHT), book_width, book_height);
coverView.frame = CGRectMake(kHalfMargin, (kGeometricHeight(SCREEN_WIDTH, 3, 1) + kLabelHeight + kMargin + kQuarterMargin + PUB_NAVBAR_HEIGHT), book_width, book_height);
coverView.layer.transform = transform;
} completion:^(BOOL finished) {
[coverView removeFromSuperview];
[self->contentView removeFromSuperview];
[transitionContext completeTransition:YES];
}];
}
- (UIImageView *)GetImageView:(UIImage *)image {
UIImageView *imageView = [[UIImageView alloc] init];
imageView.image = image;
return imageView;
}
- (nullable UIImage *)screenCapture:(nullable UIView *)target {
if (!target) { return nil; }
UIGraphicsBeginImageContextWithOptions(target.frame.size, NO, 0.0);
[target.layer renderInContext: UIGraphicsGetCurrentContext()];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
@end
@@ -0,0 +1,50 @@
//
// WXYZ_ChapterBottomPayBar.h
// WXReader
//
// Created by Andrew on 2020/7/27.
// Copyright © 2020 Andrew. All rights reserved.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
typedef NS_ENUM(NSUInteger, WXYZ_BottomPayBarType) {
WXYZ_BottomPayBarTypeDownload,
WXYZ_BottomPayBarTypeBuyChapter
};
typedef void(^PaySuccessChaptersBlock)(NSArray <NSString *>*success_chapter_ids);
typedef void(^PayCancleChapterBlock)(NSArray <NSString *>*fail_chapter_ids);
typedef void(^PayFailChaptersBlock)(NSArray <NSString *>*fail_chapter_ids);
typedef void(^BottomPayBarHiddenBlock)(void);
@interface WXYZ_ChapterBottomPayBar : UIView
@property (nonatomic, copy) PaySuccessChaptersBlock paySuccessChaptersBlock;
@property (nonatomic, copy) PayCancleChapterBlock payCancleChapterBlock;
@property (nonatomic, copy) PayFailChaptersBlock payFailChaptersBlock;
@property (nonatomic, copy) BottomPayBarHiddenBlock bottomPayBarHiddenBlock;
@property (nonatomic, assign) BOOL canTouchHiddenView;
- (instancetype)initWithChapterModel:(TFProductionChapterModel *)chapterModel barType:(WXYZ_BottomPayBarType)barType productionType:(TFProductionType)productionType;
- (instancetype)initWithChapterModel:(TFProductionChapterModel *)chapterModel barType:(WXYZ_BottomPayBarType)barType productionType:(TFProductionType)productionType buyChapterNum:(NSInteger)buyChapterNum;
- (instancetype)initWithFrame:(CGRect)frame chapterModel:(TFProductionChapterModel *)chapterModel barType:(WXYZ_BottomPayBarType)barType productionType:(TFProductionType)productionType;
- (void)showBottomPayBar;
- (void)hiddenBottomPayBar;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,470 @@
//
// WXYZ_ChapterBottomPayBar.m
// WXReader
//
// Created by Andrew on 2020/7/27.
// Copyright © 2020 Andrew. All rights reserved.
//
#import "WXYZ_ChapterBottomPayBar.h"
#import "TFRechargeViewController.h"
#import "WXYZ_ChapterBottomPayBarTitleTableViewCell.h"
#import "WXYZ_ChapterBottomPayBarOptionTableViewCell.h"
#import "WXYZ_ChapterBottomPayBarBalanceTableViewCell.h"
#import "WXYZ_ChapterBottomPayBarAutoBuyTableViewCell.h"
#import "WXYZ_ChapterBottomPayBarCostTableViewCell.h"
#import "TFReaderBookManager.h"
@interface WXYZ_ChapterBottomPayBar () <UITableViewDelegate, UITableViewDataSource>
@property (nonatomic, strong) UITableView *mainTableView;
@end
@implementation WXYZ_ChapterBottomPayBar
{
// 选项卡选择下标
NSInteger _optionSelectIndex;
NSInteger _buyChapterNum;
WXYZ_BottomPayBarType _barType;
TFProductionType _productionType;
TFProductionChapterModel *_chapterModel;
WXYZ_ChapterPayBarModel *_payBarModel;
}
- (instancetype)initWithChapterModel:(TFProductionChapterModel *)chapterModel barType:(WXYZ_BottomPayBarType)barType productionType:(TFProductionType)productionType
{
if (self = [self initWithFrame:CGRectMake(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT) chapterModel:chapterModel barType:barType productionType:productionType buyChapterNum:1]) {
[[TFViewHelper getWindowRootController].view addSubview:self];
}
return self;
}
- (instancetype)initWithChapterModel:(TFProductionChapterModel *)chapterModel barType:(WXYZ_BottomPayBarType)barType productionType:(TFProductionType)productionType buyChapterNum:(NSInteger)buyChapterNum
{
if (self = [self initWithFrame:CGRectMake(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT) chapterModel:chapterModel barType:barType productionType:productionType buyChapterNum:buyChapterNum]) {
[[TFViewHelper getWindowRootController].view addSubview:self];
}
return self;
}
- (instancetype)initWithFrame:(CGRect)frame chapterModel:(TFProductionChapterModel *)chapterModel barType:(WXYZ_BottomPayBarType)barType productionType:(TFProductionType)productionType
{
if (self = [self initWithFrame:CGRectMake(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT) chapterModel:chapterModel barType:barType productionType:productionType buyChapterNum:1]) {
[[TFViewHelper getWindowRootController].view addSubview:self];
}
return self;
}
- (instancetype)initWithFrame:(CGRect)frame chapterModel:(TFProductionChapterModel *)chapterModel barType:(WXYZ_BottomPayBarType)barType productionType:(TFProductionType)productionType buyChapterNum:(NSInteger)buyChapterNum
{
if (self = [super initWithFrame:frame]) {
_barType = barType;
_productionType = productionType;
_chapterModel = chapterModel;
_optionSelectIndex = 0;
_buyChapterNum = buyChapterNum;
_canTouchHiddenView = YES;
self.backgroundColor = kBlackTransparentColor;
[self initialize];
[self createSubViews];
[self netRequest];
}
return self;
}
- (void)initialize
{
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(netRequest) name:Notification_Login_Success object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(netRequest) name:Notification_Recharge_Success object:nil];
}
- (void)createSubViews
{
self.mainTableView = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain];
self.mainTableView.frame = CGRectMake(0, SCREEN_HEIGHT, SCREEN_WIDTH, CGFLOAT_MIN);
self.mainTableView.backgroundColor = [UIColor clearColor];
self.mainTableView.showsVerticalScrollIndicator = NO;
self.mainTableView.showsHorizontalScrollIndicator = NO;
self.mainTableView.estimatedRowHeight = 100;
self.mainTableView.sectionFooterHeight = 10;
self.mainTableView.rowHeight = UITableViewAutomaticDimension;
self.mainTableView.separatorStyle = UITableViewCellSeparatorStyleNone;
self.mainTableView.delegate = self;
self.mainTableView.dataSource = self;
self.mainTableView.scrollEnabled = NO;
if (@available(iOS 11.0, *)) {
self.mainTableView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
} else {
// Fallback on earlier versions
}
[self addSubview:self.mainTableView];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
if (_barType == WXYZ_BottomPayBarTypeDownload) {
return 3;
}
return 5;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
switch (indexPath.row) {
case 0:
{
return [self createPayBarTitleTabelViewCellWithTabelView:tableView];
}
break;
case 1:
{
if (_barType == WXYZ_BottomPayBarTypeDownload) {
return [self createPayBarBalanceTabelViewCellWithTabelView:tableView];
} else {
WS(weakSelf)
static NSString *cellName = @"WXYZ_ChapterBottomPayBarOptionTableViewCell";
WXYZ_ChapterBottomPayBarOptionTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellName];
if (!cell) {
cell = [[WXYZ_ChapterBottomPayBarOptionTableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellName];
}
cell.hiddenEndLine = NO;
cell.pay_options = _payBarModel.pay_options;
cell.payOptionClickBlock = ^(WXYZ_ChapterPayBarOptionModel * _Nonnull chapterOptionModel, NSInteger selectIndex) {
_optionSelectIndex = selectIndex;
_buyChapterNum = chapterOptionModel.buy_num;
[weakSelf.mainTableView reloadData];
};
return cell;
}
}
break;
case 2:
{
if (_barType == WXYZ_BottomPayBarTypeDownload) {
return [self createPayBarCostTableViewCellWithTableView:tableView];
} else {
return [self createPayBarBalanceTabelViewCellWithTabelView:tableView];
}
}
break;
case 3:
{
static NSString *cellName = @"WXYZ_ChapterBottomPayBarAutoBuyTableViewCell";
WXYZ_ChapterBottomPayBarAutoBuyTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellName];
if (!cell) {
cell = [[WXYZ_ChapterBottomPayBarAutoBuyTableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellName];
}
cell.hiddenEndLine = NO;
return cell;
}
break;
case 4:
{
return [self createPayBarCostTableViewCellWithTableView:tableView];
}
break;
default:
break;
}
return [[UITableViewCell alloc] init];
}
- (UITableViewCell *)createPayBarTitleTabelViewCellWithTabelView:(UITableView *)tableView
{
static NSString *cellName = @"WXYZ_ChapterBottomPayBarTitleTableViewCell";
WXYZ_ChapterBottomPayBarTitleTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellName];
if (!cell) {
cell = [[WXYZ_ChapterBottomPayBarTitleTableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellName];
}
cell.hiddenEndLine = NO;
cell.buyOptionModel = [_payBarModel.pay_options objectOrNilAtIndex:_optionSelectIndex];
return cell;
}
- (UITableViewCell *)createPayBarBalanceTabelViewCellWithTabelView:(UITableView *)tableView
{
static NSString *cellName = @"WXYZ_ChapterBottomPayBarBalanceTableViewCell";
WXYZ_ChapterBottomPayBarBalanceTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellName];
if (!cell) {
cell = [[WXYZ_ChapterBottomPayBarBalanceTableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellName];
}
cell.hiddenEndLine = NO;
cell.base_info = _payBarModel.base_info;
return cell;
}
- (UITableViewCell *)createPayBarCostTableViewCellWithTableView:(UITableView *)tableView
{
// WS(weakSelf)
static NSString *cellName = @"WXYZ_ChapterBottomPayBarCostTableViewCell";
WXYZ_ChapterBottomPayBarCostTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellName];
if (!cell) {
cell = [[WXYZ_ChapterBottomPayBarCostTableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellName];
}
cell.base_info = _payBarModel.base_info;
cell.buyOptionModel = [_payBarModel.pay_options objectOrNilAtIndex:_optionSelectIndex];
cell.buyChapterClickBlock = ^(BOOL needRecharge) {
if (!TFUserInfoManager.isLogin) {
[TFLoginOptionsViewController presentLoginView:nil];
// [kMainWindow sendSubviewToBack:weakSelf];
} else {
if (needRecharge) {
TFRechargeViewController *vc = [[TFRechargeViewController alloc] init];
vc.production_id = _chapterModel.production_id;
vc.productionType = _productionType;
TFNavigationController *t_nav = [[TFNavigationController alloc] initWithRootViewController:vc];
[[TFViewHelper getWindowRootController] presentViewController:t_nav animated:YES completion:nil];
// [kMainWindow sendSubviewToBack:weakSelf];
} else {
WS(weakSelf)
[weakSelf hiddenBottomPayBar];
[weakSelf chapterPayRequest];
}
}
};
return cell;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
if ((_barType == WXYZ_BottomPayBarTypeDownload && indexPath.row == 2) || indexPath.row == 4) {
return 60;
}
return 50;
}
//section头间距
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
return CGFLOAT_MIN;
}
//section头视图
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, SCREEN_WIDTH, CGFLOAT_MIN)];
view.backgroundColor = [UIColor clearColor];
return view;
}
//section底部间距
- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section
{
return PUB_TABBAR_OFFSET;
}
//section底部视图
- (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section
{
UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, SCREEN_WIDTH, PUB_TABBAR_OFFSET)];
view.backgroundColor = kColorRGBA(247, 248, 250, 1);
return view;
}
- (void)showBottomPayBar
{
[UIView animateWithDuration:kAnimatedDurationFast animations:^{
self.mainTableView.frame = CGRectMake(0, SCREEN_HEIGHT - (_barType == WXYZ_BottomPayBarTypeDownload?(2 * 50 + 60):(4 * 50 + 60)) - PUB_TABBAR_OFFSET, SCREEN_WIDTH, (_barType == WXYZ_BottomPayBarTypeDownload?(2 * 50 + 60):(4 * 50 + 60)) + PUB_TABBAR_OFFSET);
}];
}
- (void)hiddenBottomPayBar
{
if (!self.hidden) {
[UIView animateWithDuration:kAnimatedDurationFast animations:^{
self.mainTableView.frame = CGRectMake(0, SCREEN_HEIGHT, SCREEN_WIDTH, (_barType == WXYZ_BottomPayBarTypeDownload?(2 * 50 + 60):(4 * 50 + 60)) + PUB_TABBAR_OFFSET);
} completion:^(BOOL finished) {
[self removeAllSubviews];
[self removeFromSuperview];
self.hidden = YES;
if (self.bottomPayBarHiddenBlock) {
self.bottomPayBarHiddenBlock();
}
}];
}
}
#pragma mark - 系统方法
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
if (self.canTouchHiddenView) {
UIView *touchView = [[touches anyObject] view];
if (![touchView isDescendantOfView:self.mainTableView]) {
[self hiddenBottomPayBar];
[TFPromptManager showPromptViewWithStatus:TFPromptStatusError promptTitle:TFLocalizedString(@"您已取消购买")];
if (self.payCancleChapterBlock) {
self.payCancleChapterBlock(_chapterModel.chapter_ids);
}
}
}
}
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
if (CGRectContainsPoint(CGRectMake(0, 0, SCREEN_WIDTH, PUB_NAVBAR_HEIGHT), point) && !self.canTouchHiddenView) {
return nil;
}
return [super hitTest:point withEvent:event];
}
- (void)netRequest
{
NSString *url = @"";
NSMutableDictionary *parameters = [NSMutableDictionary dictionary];
switch (_productionType) {
case TFProductionTypeNovel:
case TFProductionTypeAi:
{
url = Book_Buy_Index;
[parameters setObject:[TFUtilsHelper formatStringWithInteger:_chapterModel.production_id] forKey:@"book_id"];
[parameters setObject:[TFUtilsHelper formatStringWithInteger:_chapterModel.chapter_id] forKey:@"chapter_id"];
if (_barType == WXYZ_BottomPayBarTypeDownload) {
[parameters setObject:@"down" forKey:@"page_from"];
[parameters setObject:[TFUtilsHelper formatStringWithInteger:_buyChapterNum] forKey:@"num"];
} else {
[parameters setObject:@"read" forKey:@"page_from"];
}
}
break;
case TFProductionTypeComic:
{
url = Comic_Buy_Index;
[parameters setObject:[TFUtilsHelper formatStringWithInteger:_chapterModel.production_id] forKey:@"comic_id"];
if (_barType == WXYZ_BottomPayBarTypeDownload) {
[parameters setObject:[_chapterModel.chapter_ids componentsJoinedByString:@","] forKey:@"chapter_id"];
[parameters setObject:@"down" forKey:@"page_from"];
} else {
[parameters setObject:[TFUtilsHelper formatStringWithInteger:_chapterModel.chapter_id] forKey:@"chapter_id"];
[parameters setObject:@"read" forKey:@"page_from"];
}
}
break;
case TFProductionTypeAudio:
{
url = Audio_Buy_Index;
[parameters setObject:[TFUtilsHelper formatStringWithInteger:_chapterModel.production_id] forKey:@"audio_id"];
if (_barType == WXYZ_BottomPayBarTypeDownload) {
[parameters setObject:[_chapterModel.chapter_ids componentsJoinedByString:@","] forKey:@"chapter_id"];
[parameters setObject:@"down" forKey:@"page_from"];
} else {
[parameters setObject:[TFUtilsHelper formatStringWithInteger:_chapterModel.chapter_id] forKey:@"chapter_id"];
[parameters setObject:@"read" forKey:@"page_from"];
}
}
break;
default:
break;
}
WS(weakSelf)
[TFNetworkTools POST:url parameters:[parameters copy] model:nil success:^(BOOL isSuccess, NSDictionary * _Nullable t_model, TFNetworkRequestModel * _Nonnull requestModel) {
if (isSuccess) {
SS(strongSelf)
[TFUserInfoManager shareInstance].totalRemain = [[[t_model objectForKey:@"data"] objectForKey:@"remain"] integerValue];
if (strongSelf) {
strongSelf->_payBarModel = [WXYZ_ChapterPayBarModel modelWithDictionary:[t_model objectForKey:@"data"]];
}
[weakSelf.mainTableView reloadData];
}
} failure:nil];
}
- (void)chapterPayRequest
{
NSString *url = @"";
NSMutableDictionary *parameters = [NSMutableDictionary dictionary];
switch (_productionType) {
case TFProductionTypeNovel:
case TFProductionTypeAi:
{
url = Book_Buy_Chapter;
[parameters setObject:[TFUtilsHelper formatStringWithInteger:_chapterModel.production_id] forKey:@"book_id"];
[parameters setObject:[TFUtilsHelper formatStringWithInteger:_chapterModel.chapter_id] forKey:@"chapter_id"];
[parameters setObject:[TFUtilsHelper formatStringWithInteger:_buyChapterNum] forKey:@"num"];
}
break;
case TFProductionTypeComic:
{
url = Comic_Buy_Chapter;
[parameters setObject:[TFUtilsHelper formatStringWithInteger:_chapterModel.production_id] forKey:@"comic_id"];
if (_barType == WXYZ_BottomPayBarTypeDownload) {
[parameters setObject:[_chapterModel.chapter_ids componentsJoinedByString:@","] forKey:@"chapter_id"];
} else {
[parameters setObject:[TFUtilsHelper formatStringWithInteger:_chapterModel.chapter_id] forKey:@"chapter_id"];
[parameters setObject:[TFUtilsHelper formatStringWithInteger:_buyChapterNum] forKey:@"num"];
}
}
break;
case TFProductionTypeAudio:
{
url = Audio_Buy_Chapter;
[parameters setObject:[TFUtilsHelper formatStringWithInteger:_chapterModel.production_id] forKey:@"audio_id"];
[parameters setObject:[TFUtilsHelper formatStringWithInteger:_buyChapterNum] forKey:@"num"];
if (_barType == WXYZ_BottomPayBarTypeDownload) {
[parameters setObject:[_chapterModel.chapter_ids componentsJoinedByString:@","] forKey:@"chapter_id"];
} else {
[parameters setObject:[TFUtilsHelper formatStringWithInteger:_chapterModel.chapter_id] forKey:@"chapter_id"];
}
}
break;
default:
break;
}
[TFNetworkTools POST:url parameters:parameters model:nil success:^(BOOL isSuccess, id _Nullable t_model, TFNetworkRequestModel * _Nonnull requestModel) {
if (isSuccess) {
NSArray<NSString *> *t_arr = [requestModel.data objectForKey:@"chapter_ids"];
if (self.paySuccessChaptersBlock) {
self.paySuccessChaptersBlock(t_arr);
}
[TFPromptManager showPromptViewWithStatus:TFPromptStatusSuccess promptTitle:TFLocalizedString(@"购买成功")];
NSInteger index = [TFReaderBookManager sharedManager].currentChapterIndex;
for (NSInteger i = index; i < index + t_arr.count; i++) {
[TFReaderBookManager sharedManager].bookModel.chapter_list[index].is_preview = NO;
}
if (_productionType == TFProductionTypeNovel || _productionType == TFProductionTypeAi) {
[[NSNotificationCenter defaultCenter] postNotificationName:NSNotification_Retry_Chapter object:nil];
}
[[NSNotificationCenter defaultCenter] postNotificationName:Notification_Production_Pay_Success object:t_arr];
} else {
!self.payFailChaptersBlock ?: self.payFailChaptersBlock(_chapterModel.chapter_ids);
}
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
[TFPromptManager showPromptWithError:error defaultText:nil];
}];
}
@end
@@ -0,0 +1,17 @@
//
// WXYZ_ChapterBottomPayBarAutoBuyTableViewCell.h
// WXReader
//
// Created by Andrew on 2020/7/27.
// Copyright © 2020 Andrew. All rights reserved.
//
#import "TFBasicTableViewCell.h"
NS_ASSUME_NONNULL_BEGIN
@interface WXYZ_ChapterBottomPayBarAutoBuyTableViewCell : TFBasicTableViewCell
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,64 @@
//
// WXYZ_ChapterBottomPayBarAutoBuyTableViewCell.m
// WXReader
//
// Created by Andrew on 2020/7/27.
// Copyright © 2020 Andrew. All rights reserved.
//
#import "WXYZ_ChapterBottomPayBarAutoBuyTableViewCell.h"
#import <AudioToolbox/AudioToolbox.h>
#import "KLSwitch.h"
@implementation WXYZ_ChapterBottomPayBarAutoBuyTableViewCell
- (void)createSubviews
{
[super createSubviews];
WS(weakSelf)
KLSwitch *autoBuySwitch = [[KLSwitch alloc] initWithFrame:CGRectMake(SCREEN_WIDTH - 51 - kMargin, 10, 51, 31) didChangeHandler:^(BOOL isOn) {
[weakSelf autoBuyNetRequest];
}];
autoBuySwitch.transform = CGAffineTransformMakeScale(0.7, 0.7);//缩放
autoBuySwitch.onTintColor = kMainColor;
[autoBuySwitch setDefaultOnState:[TFUserInfoManager shareInstance].auto_sub];
[self.contentView addSubview:autoBuySwitch];
[autoBuySwitch mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerY.mas_equalTo(self.contentView.mas_centerY);
make.right.mas_equalTo(self.contentView.mas_right).with.offset(- kMargin);
make.width.mas_equalTo(51);
make.height.mas_offset(31);
}];
UILabel *autoBuyTitleLabel = [[UILabel alloc] init];
autoBuyTitleLabel.text = TFLocalizedString(@"自动购买下一章");
autoBuyTitleLabel.textColor = kBlackColor;
autoBuyTitleLabel.font = kMainFont;
autoBuyTitleLabel.textAlignment = NSTextAlignmentLeft;
[self.contentView addSubview:autoBuyTitleLabel];
[autoBuyTitleLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(self.contentView.mas_left).with.offset(kMargin);
make.right.mas_equalTo(autoBuySwitch.mas_left).with.offset(- kHalfMargin);
make.top.mas_equalTo(self.contentView.mas_top);
make.bottom.mas_equalTo(self.contentView.mas_bottom);
}];
}
- (void)autoBuyNetRequest
{
[TFNetworkTools POST:Auto_Sub_Chapter parameters:nil model:nil success:^(BOOL isSuccess, NSDictionary * _Nullable t_model, TFNetworkRequestModel * _Nonnull requestModel) {
if (isSuccess) {
NSString *auto_sub_state = [NSString stringWithFormat:@"%@", [[t_model objectForKey:@"data"] objectForKey:@"auto_sub"]];
if (auto_sub_state && auto_sub_state.length > 0) {
AudioServicesPlaySystemSound(1519);
[TFUserInfoManager shareInstance].auto_sub = [auto_sub_state isEqualToString:@"1"];
}
}
} failure:nil];
}
@end
@@ -0,0 +1,20 @@
//
// WXYZ_ChapterBottomPayBarBalanceTableViewCell.h
// WXReader
//
// Created by Andrew on 2020/7/27.
// Copyright © 2020 Andrew. All rights reserved.
//
#import "TFBasicTableViewCell.h"
#import "WXYZ_ChapterPayBarModel.h"
NS_ASSUME_NONNULL_BEGIN
@interface WXYZ_ChapterBottomPayBarBalanceTableViewCell : TFBasicTableViewCell
@property (nonatomic, strong) WXYZ_ChapterPayBarInfoModel *base_info;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,68 @@
//
// WXYZ_ChapterBottomPayBarBalanceTableViewCell.m
// WXReader
//
// Created by Andrew on 2020/7/27.
// Copyright © 2020 Andrew. All rights reserved.
//
#import "WXYZ_ChapterBottomPayBarBalanceTableViewCell.h"
@implementation WXYZ_ChapterBottomPayBarBalanceTableViewCell
{
UILabel *balanceDetailTitleLabel;
}
- (void)createSubviews
{
[super createSubviews];
UILabel *balanceTitleLabel = [[UILabel alloc] init];
balanceTitleLabel.text = TFLocalizedString(@"账户余额");
balanceTitleLabel.textColor = kBlackColor;
balanceTitleLabel.font = kMainFont;
balanceTitleLabel.textAlignment = NSTextAlignmentLeft;
[self.contentView addSubview:balanceTitleLabel];
[balanceTitleLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(self.contentView.mas_left).with.offset(kMargin);
make.width.mas_equalTo(150);
make.top.mas_equalTo(self.contentView.mas_top);
make.bottom.mas_equalTo(self.contentView.mas_bottom);
}];
balanceDetailTitleLabel = [[UILabel alloc] init];
balanceDetailTitleLabel.text = [NSString stringWithFormat:@"0%@", Main_Unit_Name];
balanceDetailTitleLabel.textColor = kGrayTextColor;
balanceDetailTitleLabel.font = kFont12;
balanceDetailTitleLabel.textAlignment = NSTextAlignmentRight;
[self.contentView addSubview:balanceDetailTitleLabel];
[balanceDetailTitleLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(balanceTitleLabel.mas_right);
make.right.mas_equalTo(self.contentView.mas_right).with.offset(- kMargin);
make.top.mas_equalTo(balanceTitleLabel.mas_top);
make.height.mas_equalTo(balanceTitleLabel.mas_height);
}];
}
- (void)setBase_info:(WXYZ_ChapterPayBarInfoModel *)base_info
{
if (_base_info != base_info) {
_base_info = base_info;
if (TFUserInfoManager.isLogin) {
NSString *constString = @"";
if (base_info.gold_remain == 0 && base_info.silver_remain > 0) {
constString = [constString stringByAppendingString:[NSString stringWithFormat:@"%@%@", [TFUtilsHelper formatStringWithInteger:base_info.silver_remain], base_info.subUnit?:@""]];
} else if (base_info.gold_remain > 0 && base_info.silver_remain == 0) {
constString = [constString stringByAppendingString:[NSString stringWithFormat:@"%@%@", [TFUtilsHelper formatStringWithInteger:base_info.gold_remain], base_info.unit?:@""]];
} else {
constString = [constString stringByAppendingString:[NSString stringWithFormat:@"%@%@ + %@%@", [TFUtilsHelper formatStringWithInteger:base_info.gold_remain], base_info.unit?:@"", [TFUtilsHelper formatStringWithInteger:base_info.silver_remain], base_info.subUnit?:@""]];
}
balanceDetailTitleLabel.text = constString;
}
}
}
@end
@@ -0,0 +1,24 @@
//
// WXYZ_ChapterBottomPayBarCostTableViewCell.h
// WXReader
//
// Created by Andrew on 2020/7/27.
// Copyright © 2020 Andrew. All rights reserved.
//
#import "TFBasicTableViewCell.h"
#import "WXYZ_ChapterPayBarModel.h"
NS_ASSUME_NONNULL_BEGIN
@interface WXYZ_ChapterBottomPayBarCostTableViewCell : TFBasicTableViewCell
@property (nonatomic, copy) void (^buyChapterClickBlock)(BOOL needRecharge);
@property (nonatomic, strong) WXYZ_ChapterPayBarInfoModel *base_info;
@property (nonatomic, strong) WXYZ_ChapterPayBarOptionModel *buyOptionModel;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,162 @@
//
// WXYZ_ChapterBottomPayBarCostTableViewCell.m
// WXReader
//
// Created by Andrew on 2020/7/27.
// Copyright © 2020 Andrew. All rights reserved.
//
#import "WXYZ_ChapterBottomPayBarCostTableViewCell.h"
@implementation WXYZ_ChapterBottomPayBarCostTableViewCell
{
UIButton *buyChapterButton;
UILabel *buyPriceLabel;
UILabel *buyOriginalPriceLabel;
}
- (void)createSubviews
{
[super createSubviews];
self.contentView.backgroundColor = kColorRGBA(247, 248, 250, 1);
buyChapterButton = [UIButton buttonWithType:UIButtonTypeCustom];
buyChapterButton.layer.cornerRadius = 4;
buyChapterButton.clipsToBounds = YES;
buyChapterButton.backgroundColor = kMainColor;
[buyChapterButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
[buyChapterButton.titleLabel setFont:kMainFont];
[buyChapterButton addTarget:self action:@selector(buyChapterButtonClick) forControlEvents:UIControlEventTouchUpInside];
[self.contentView addSubview:buyChapterButton];
[buyChapterButton mas_makeConstraints:^(MASConstraintMaker *make) {
make.right.mas_equalTo(self.contentView.mas_right).with.offset(- kMargin);
make.centerY.mas_equalTo(self.contentView.mas_centerY);
make.height.mas_equalTo(35);
make.width.mas_equalTo(100);
}];
buyOriginalPriceLabel = [[UILabel alloc] init];
buyOriginalPriceLabel.backgroundColor = [UIColor clearColor];
buyOriginalPriceLabel.font = kFont12;
buyOriginalPriceLabel.textAlignment = NSTextAlignmentLeft;
buyOriginalPriceLabel.numberOfLines = 1;
buyOriginalPriceLabel.textColor = kBlackColor;
[self.contentView addSubview:buyOriginalPriceLabel];
[buyOriginalPriceLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(self.contentView.mas_left).with.offset(kMargin);
make.right.mas_equalTo(buyChapterButton.mas_left).with.offset(- kHalfMargin);
make.bottom.mas_equalTo(self.contentView.mas_bottom).with.offset(- kHalfMargin);
make.height.mas_equalTo(20);
}];
buyPriceLabel = [[UILabel alloc] init];
buyPriceLabel.backgroundColor = [UIColor clearColor];
buyPriceLabel.font = kMainFont;
buyPriceLabel.textAlignment = NSTextAlignmentLeft;
buyPriceLabel.numberOfLines = 1;
[self.contentView addSubview:buyPriceLabel];
[buyPriceLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(buyOriginalPriceLabel.mas_left);
make.right.mas_equalTo(buyOriginalPriceLabel.mas_right);
make.top.mas_equalTo(self.contentView.mas_top).with.offset(kHalfMargin);
make.bottom.mas_equalTo(buyOriginalPriceLabel.mas_top);
}];
}
- (void)setBuyOptionModel:(WXYZ_ChapterPayBarOptionModel *)buyOptionModel
{
_buyOptionModel = buyOptionModel;
if (TFUserInfoManager.isLogin) {
if (self.base_info.remain - buyOptionModel.total_price >= 0) {
[buyChapterButton setTitle:TFLocalizedString(@"确认购买") forState:UIControlStateNormal];
} else {
[buyChapterButton setTitle:TFLocalizedString(@"充值并购买") forState:UIControlStateNormal];
}
} else {
[buyChapterButton setTitle:TFLocalizedString(@"登录后购买") forState:UIControlStateNormal];
}
// 实付
buyPriceLabel.attributedText = [self getPriceString];
// 原价
buyOriginalPriceLabel.attributedText = [self getOriginalPriceString];
if (buyOptionModel.discount.length > 0) {
[buyOriginalPriceLabel mas_updateConstraints:^(MASConstraintMaker *make) {
make.height.mas_equalTo(20);
}];
} else {
[buyOriginalPriceLabel mas_updateConstraints:^(MASConstraintMaker *make) {
make.height.mas_equalTo(CGFLOAT_MIN);
}];
}
}
- (void)setBase_info:(WXYZ_ChapterPayBarInfoModel *)base_info
{
_base_info = base_info;
}
- (void)buyChapterButtonClick
{
if (self.buyChapterClickBlock) {
self.buyChapterClickBlock((self.base_info.remain - self.buyOptionModel.total_price <= 0));
}
}
- (NSMutableAttributedString *)getPriceString
{
NSMutableAttributedString *attributedStr = [[NSMutableAttributedString alloc] init];
NSString *suffix = nil;
if (self.buyOptionModel.actual_cost.gold_cost == 0 && self.buyOptionModel.actual_cost.silver_cost > 0) {
[attributedStr appendString:[NSString stringWithFormat:@"%@%@%@", TFLocalizedString(@"实付:"), [TFUtilsHelper formatStringWithInteger:self.buyOptionModel.actual_cost.gold_cost], self.base_info.subUnit?:@""]];
suffix = [NSString stringWithFormat:@"%@%@", [TFUtilsHelper formatStringWithInteger:self.buyOptionModel.actual_cost.gold_cost], self.base_info.subUnit?:@""];
} else if (self.buyOptionModel.actual_cost.gold_cost > 0 && self.buyOptionModel.actual_cost.silver_cost == 0) {
[attributedStr appendString:[NSString stringWithFormat:@"%@%@%@", TFLocalizedString(@"实付:"), [TFUtilsHelper formatStringWithInteger:self.buyOptionModel.actual_cost.gold_cost], self.base_info.unit?:@""]];
suffix = [NSString stringWithFormat:@"%@%@", [TFUtilsHelper formatStringWithInteger:self.buyOptionModel.actual_cost.gold_cost], self.base_info.unit?:@""];
} else {
[attributedStr appendString:[NSString stringWithFormat:@"%@%@%@ + %@%@", TFLocalizedString(@"实付:"), [TFUtilsHelper formatStringWithInteger:self.buyOptionModel.actual_cost.gold_cost], self.base_info.unit?:@"", [TFUtilsHelper formatStringWithInteger:self.buyOptionModel.actual_cost.silver_cost], self.base_info.subUnit?:@""]];
suffix = [NSString stringWithFormat:@"%@%@ + %@%@", [TFUtilsHelper formatStringWithInteger:self.buyOptionModel.actual_cost.gold_cost], self.base_info.unit?:@"", [TFUtilsHelper formatStringWithInteger:self.buyOptionModel.actual_cost.silver_cost], self.base_info.subUnit?:@""];
}
[attributedStr addAttribute:NSForegroundColorAttributeName value:kMainColor range:NSMakeRange(attributedStr.length - suffix.length, suffix.length)];
return attributedStr;
}
- (NSMutableAttributedString *)getOriginalPriceString
{
NSMutableAttributedString *attributedStr = [[NSMutableAttributedString alloc] init];
NSString *suffix = nil;
if (self.buyOptionModel.original_cost.gold_cost == 0 && self.buyOptionModel.original_cost.silver_cost > 0) {
[attributedStr appendString:[NSString stringWithFormat:@"%@%@%@", TFLocalizedString(@"原价:"), [TFUtilsHelper formatStringWithInteger:self.buyOptionModel.original_cost.gold_cost], self.base_info.subUnit?:@""]];
suffix = [NSString stringWithFormat:@"%@%@", [TFUtilsHelper formatStringWithInteger:self.buyOptionModel.original_cost.gold_cost], self.base_info.subUnit?:@""];
} else if (self.buyOptionModel.original_cost.gold_cost > 0 && self.buyOptionModel.original_cost.silver_cost == 0) {
[attributedStr appendString:[NSString stringWithFormat:@"%@%@%@", TFLocalizedString(@"原价:"), [TFUtilsHelper formatStringWithInteger:self.buyOptionModel.original_cost.gold_cost], self.base_info.unit?:@""]];
suffix = [NSString stringWithFormat:@"%@%@", [TFUtilsHelper formatStringWithInteger:self.buyOptionModel.original_cost.gold_cost], self.base_info.unit?:@""];
} else {
[attributedStr appendString:[NSString stringWithFormat:@"%@%@%@ + %@%@", TFLocalizedString(@"原价:"), [TFUtilsHelper formatStringWithInteger:self.buyOptionModel.original_cost.gold_cost], self.base_info.unit?:@"", [TFUtilsHelper formatStringWithInteger:self.buyOptionModel.original_cost.silver_cost], self.base_info.subUnit?:@""]];
suffix = [NSString stringWithFormat:@"%@%@ + %@%@", [TFUtilsHelper formatStringWithInteger:self.buyOptionModel.original_cost.gold_cost], self.base_info.unit?:@"", [TFUtilsHelper formatStringWithInteger:self.buyOptionModel.original_cost.silver_cost], self.base_info.subUnit?:@""];
}
[attributedStr addAttribute:NSForegroundColorAttributeName value:kGrayTextColor range:NSMakeRange(0, attributedStr.length)];
[attributedStr addAttributes:@{NSStrikethroughStyleAttributeName : @(NSUnderlinePatternSolid | NSUnderlineStyleSingle), NSBaselineOffsetAttributeName : @(0)} range:NSMakeRange(attributedStr.length - suffix.length, suffix.length)];
return attributedStr;
}
@end
@@ -0,0 +1,22 @@
//
// WXYZ_ChapterBottomPayBarOptionTableViewCell.h
// WXReader
//
// Created by Andrew on 2020/7/27.
// Copyright © 2020 Andrew. All rights reserved.
//
#import "TFBasicTableViewCell.h"
#import "WXYZ_ChapterPayBarModel.h"
NS_ASSUME_NONNULL_BEGIN
@interface WXYZ_ChapterBottomPayBarOptionTableViewCell : TFBasicTableViewCell
@property (nonatomic, copy) void (^payOptionClickBlock)(WXYZ_ChapterPayBarOptionModel *chapterOptionModel, NSInteger selectIndex);
@property (nonatomic, strong) NSArray <WXYZ_ChapterPayBarOptionModel *> *pay_options;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,94 @@
//
// WXYZ_ChapterBottomPayBarOptionTableViewCell.m
// WXReader
//
// Created by Andrew on 2020/7/27.
// Copyright © 2020 Andrew. All rights reserved.
//
#import "WXYZ_ChapterBottomPayBarOptionTableViewCell.h"
@implementation WXYZ_ChapterBottomPayBarOptionTableViewCell
{
UIScrollView *optionScorllView;
}
- (void)createSubviews
{
[super createSubviews];
optionScorllView = [[UIScrollView alloc] init];
optionScorllView.showsVerticalScrollIndicator = NO;
optionScorllView.showsHorizontalScrollIndicator = NO;
optionScorllView.backgroundColor = [UIColor whiteColor];
[self.contentView addSubview:optionScorllView];
[optionScorllView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(self.contentView.mas_left);
make.top.mas_equalTo(self.contentView.mas_top);
make.width.mas_equalTo(self.contentView.mas_width);
make.height.mas_equalTo(self.contentView.mas_height);
}];
}
- (void)setPay_options:(NSArray<WXYZ_ChapterPayBarOptionModel *> *)pay_options
{
if (_pay_options != pay_options) {
_pay_options = pay_options;
[optionScorllView removeAllSubviews];
NSInteger buttonNum = pay_options.count;
CGFloat button_H = 30;//按钮高
CGFloat margin_X = kMargin;//第一个按钮的X坐标
CGFloat margin_Y = 10;//第一个按钮的Y坐标
CGFloat space_X = kHalfMargin;//按钮间距
CGFloat button_X = margin_X;
CGFloat button_W = - 10;
for (NSInteger i = 0; i < buttonNum; i++) {
WXYZ_ChapterPayBarOptionModel *option = [pay_options objectOrNilAtIndex:i];
button_X = button_X + button_W + space_X;
button_W = [TFViewHelper getDynamicWidthWithLabelFont:kMainFont labelHeight:30 labelText:option.label] + 10;//按钮宽
UIButton *optionButton = [UIButton buttonWithType:UIButtonTypeCustom];
optionButton.frame = CGRectMake(button_X, margin_Y, button_W, button_H);
optionButton.layer.cornerRadius = 4;
optionButton.backgroundColor = [UIColor whiteColor];
optionButton.tag = i;
[optionButton.titleLabel setFont:kMainFont];
[optionButton setTitle:option.label?:@"" forState:UIControlStateNormal];
[optionButton setTitleColor:kGrayTextColor forState:UIControlStateNormal];
[optionButton addTarget:self action:@selector(optionButtonClick:) forControlEvents:UIControlEventTouchUpInside];
[optionScorllView addSubview:optionButton];
if (i == buttonNum - 1) {
[optionScorllView setContentSize:CGSizeMake(optionButton.right, 0)];
}
if (buttonNum > 0 && i == 0) {
optionButton.backgroundColor = kMainColor;
[optionButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
}
}
}
}
- (void)optionButtonClick:(UIButton *)sender
{
[optionScorllView.subviews enumerateObjectsUsingBlock:^(__kindof UIView * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
if ([obj isKindOfClass:[UIButton class]]) {
UIButton *button = (UIButton *)obj;
button.backgroundColor = [UIColor whiteColor];
[button setTitleColor:kGrayTextColor forState:UIControlStateNormal];
}
}];
sender.backgroundColor = kMainColor;
[sender setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
if (self.payOptionClickBlock) {
self.payOptionClickBlock([self.pay_options objectOrNilAtIndex:sender.tag], sender.tag);
}
}
@end
@@ -0,0 +1,20 @@
//
// WXYZ_ChapterBottomPayBarTitleTableViewCell.h
// WXReader
//
// Created by Andrew on 2020/7/27.
// Copyright © 2020 Andrew. All rights reserved.
//
#import "TFBasicTableViewCell.h"
#import "WXYZ_ChapterPayBarModel.h"
NS_ASSUME_NONNULL_BEGIN
@interface WXYZ_ChapterBottomPayBarTitleTableViewCell : TFBasicTableViewCell
@property (nonatomic, strong) WXYZ_ChapterPayBarOptionModel *buyOptionModel;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,84 @@
//
// WXYZ_ChapterBottomPayBarTitleTableViewCell.m
// WXReader
//
// Created by Andrew on 2020/7/27.
// Copyright © 2020 Andrew. All rights reserved.
//
#import "WXYZ_ChapterBottomPayBarTitleTableViewCell.h"
@implementation WXYZ_ChapterBottomPayBarTitleTableViewCell
{
UILabel *payBarTitleLabel;
// 打折信息
UILabel *discountTitleLabel;
}
- (void)createSubviews
{
[super createSubviews];
payBarTitleLabel = [[UILabel alloc] init];
payBarTitleLabel.text = TFLocalizedString(@"从本章开始购买");
payBarTitleLabel.textColor = kBlackColor;
payBarTitleLabel.font = kMainFont;
payBarTitleLabel.textAlignment = NSTextAlignmentCenter;
[self.contentView addSubview:payBarTitleLabel];
[payBarTitleLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.mas_equalTo(self.contentView.mas_top);
make.centerX.mas_equalTo(self.contentView.mas_centerX).with.offset(CGFLOAT_MIN);
make.width.mas_equalTo(SCREEN_WIDTH);
make.bottom.mas_equalTo(self.contentView.mas_bottom);
}];
discountTitleLabel = [[UILabel alloc] init];
discountTitleLabel.textColor = kWhiteColor;
discountTitleLabel.textAlignment = NSTextAlignmentCenter;
discountTitleLabel.font = kFont10;
discountTitleLabel.backgroundColor = kRedColor;
discountTitleLabel.layer.cornerRadius = 2;
discountTitleLabel.clipsToBounds = YES;
[self.contentView addSubview:discountTitleLabel];
[discountTitleLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(payBarTitleLabel.mas_right).with.offset(kQuarterMargin);
make.centerY.mas_equalTo(payBarTitleLabel.mas_centerY);
make.width.mas_equalTo(CGFLOAT_MIN);
make.height.mas_equalTo(16);
}];
}
- (void)setBuyOptionModel:(WXYZ_ChapterPayBarOptionModel *)buyOptionModel
{
_buyOptionModel = buyOptionModel;
payBarTitleLabel.text = buyOptionModel.label?:TFLocalizedString(@"从本章开始购买");
if (buyOptionModel.discount.length > 0 && buyOptionModel.discount) {
discountTitleLabel.text = buyOptionModel.discount?:@"";
CGFloat t_width = [TFViewHelper getDynamicWidthWithLabel:discountTitleLabel] + kQuarterMargin;
[discountTitleLabel mas_updateConstraints:^(MASConstraintMaker *make) {
make.width.mas_equalTo(t_width);
}];
[payBarTitleLabel mas_updateConstraints:^(MASConstraintMaker *make) {
make.centerX.mas_equalTo(self.contentView.mas_centerX).with.offset(- t_width / 2);
make.width.mas_equalTo([TFViewHelper getDynamicWidthWithLabel:payBarTitleLabel]);
}];
} else {
[discountTitleLabel mas_updateConstraints:^(MASConstraintMaker *make) {
make.width.mas_equalTo(CGFLOAT_MIN);
}];
[payBarTitleLabel mas_updateConstraints:^(MASConstraintMaker *make) {
make.centerX.mas_equalTo(self.contentView.mas_centerX).with.offset(CGFLOAT_MIN);
make.width.mas_equalTo(SCREEN_WIDTH);
}];
}
}
@end
@@ -0,0 +1,71 @@
//
// WXYZ_ChapterPayBarModel.h
// WXReader
//
// Created by Andrew on 2018/7/15.
// Copyright © 2018年 Andrew. All rights reserved.
//
#import <Foundation/Foundation.h>
@class WXYZ_ChapterPayBarInfoModel, WXYZ_ChapterPayBarOptionModel, Actual_Cost, Original_Cost;
@interface WXYZ_ChapterPayBarModel : NSObject
@property (nonatomic, strong) WXYZ_ChapterPayBarInfoModel *base_info;
@property (nonatomic, strong) NSArray <WXYZ_ChapterPayBarOptionModel *> *pay_options;
@end
@interface WXYZ_ChapterPayBarInfoModel : NSObject
@property (nonatomic, assign) NSInteger remain;
@property (nonatomic, assign) NSInteger gold_remain;
@property (nonatomic, assign) NSInteger silver_remain;
@property (nonatomic, copy) NSString *unit;
@property (nonatomic, copy) NSString *subUnit;
@property (nonatomic, assign) NSInteger chapter_id;
@property (nonatomic, assign) NSInteger auto_sub;
@end
@interface WXYZ_ChapterPayBarOptionModel : NSObject
@property (nonatomic, strong) Original_Cost *original_cost; // 原价
@property (nonatomic, assign) NSInteger buy_num;
@property (nonatomic, assign) NSInteger original_price;
@property (nonatomic, strong) Actual_Cost *actual_cost; // 实际需要消费的金额
@property (nonatomic, copy) NSString *discount;
@property (nonatomic, copy) NSString *label;
@property (nonatomic, assign) NSInteger total_price;
@end
@interface Actual_Cost : NSObject
@property (nonatomic, assign) NSInteger gold_cost;
@property (nonatomic, assign) NSInteger silver_cost;
@end
@interface Original_Cost : NSObject
@property (nonatomic, assign) NSInteger gold_cost;
@property (nonatomic, assign) NSInteger silver_cost;
@end
@@ -0,0 +1,42 @@
//
// WXYZ_ChapterPayBarModel.m
// WXReader
//
// Created by Andrew on 2018/7/15.
// Copyright © 2018年 Andrew. All rights reserved.
//
#import "WXYZ_ChapterPayBarModel.h"
@implementation WXYZ_ChapterPayBarModel
+ (NSDictionary<NSString *,id> *)modelContainerPropertyGenericClass{
return @{@"pay_options" : [WXYZ_ChapterPayBarOptionModel class]};
}
+ (NSDictionary *)modelCustomPropertyMapper {
return @{
@"pay_options" :@"buy_option"
};
}
@end
@implementation WXYZ_ChapterPayBarInfoModel
@end
@implementation WXYZ_ChapterPayBarOptionModel
@end
@implementation Actual_Cost
@end
@implementation Original_Cost
@end
@@ -0,0 +1,73 @@
//
// WXYZ_ShadowView.h
// WXYZ_ShadowView
//
// Created by Sunshine on 2019/1/29.
// Copyright © 2019 com. All rights reserved.
//
#import <UIKit/UIKit.h>
typedef NS_OPTIONS(NSUInteger, YCShadowSide) {
YCShadowSideTop = 1 << 0,
YCShadowSideBottom = 1 << 1,
YCShadowSideLeft = 1 << 2,
YCShadowSideRight = 1 << 3,
YCShadowSideAllSides = ~0UL
};
@interface WXYZ_ShadowView : UIView
/**
* 设置四周阴影: shaodwRadius:5 shadowColor:[UIColor colorWithWhite:0 alpha:0.3]
*/
- (void)yc_shaodw;
/**
* 设置垂直方向的阴影
*
* @param shadowRadius 阴影半径
* @param shadowColor 阴影颜色
* @param shadowOffset 阴影b偏移
*/
- (void)yc_verticalShaodwRadius:(CGFloat)shadowRadius
shadowColor:(UIColor *)shadowColor
shadowOffset:(CGSize)shadowOffset;
/**
* 设置水平方向的阴影
*
* @param shadowRadius 阴影半径
* @param shadowColor 阴影颜色
* @param shadowOffset 阴影b偏移
*/
- (void)yc_horizontalShaodwRadius:(CGFloat)shadowRadius
shadowColor:(UIColor *)shadowColor
shadowOffset:(CGSize)shadowOffset;
/**
* 设置阴影
*
* @param shadowRadius 阴影半径
* @param shadowColor 阴影颜色
* @param shadowOffset 阴影b偏移
* @param shadowSide 阴影边
*/
- (void)yc_shaodwRadius:(CGFloat)shadowRadius
shadowColor:(UIColor *)shadowColor
shadowOffset:(CGSize)shadowOffset
byShadowSide:(YCShadowSide)shadowSide;
/**
* 设置圆角(四周)
*
* @param cornerRadius 圆角半径
*/
- (void)yc_cornerRadius:(CGFloat)cornerRadius;
/**
* 设置圆角
*
* @param cornerRadius 圆角半径
* @param corners 圆角边
*/
- (void)yc_cornerRadius:(CGFloat)cornerRadius
byRoundingCorners:(UIRectCorner)corners;
@end
@@ -0,0 +1,249 @@
//
// WXYZ_ShadowView.m
// WXYZ_ShadowView
//
// Created by Sunshine on 2019/1/29.
// Copyright © 2019 com. All rights reserved.
//
#import "WXYZ_ShadowView.h"
@interface WXYZ_ShadowView()
@property(nonatomic, strong) UIColor *shadowColor;
@property(nonatomic, assign) CGSize shadowOffset;
@property(nonatomic, assign) CGFloat shadowRadius;
@property(nonatomic, assign) NSInteger shadowSide;
@property(nonatomic, assign) CGFloat cornerRadius;
@property(nonatomic, assign) UIRectCorner rectCorner;
@property(nonatomic, strong)UIView *backContentView;
@property(nonatomic, strong)CALayer *topShadowLayer;
@property(nonatomic, strong)CALayer *botShadowLayer;
@property(nonatomic, strong)CALayer *leftShadowLayer;
@property(nonatomic, strong)CALayer *rightShadowLayer;
@property(nonatomic, strong)CAShapeLayer *maskLayer;
@end
@implementation WXYZ_ShadowView
#pragma mark - init 初始化自身
- (instancetype)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
[self setup];
}
return self;
}
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
if (self = [super initWithCoder:aDecoder]) {
[self setup];
}
return self;
}
#pragma mark - Life Cycle 生命周期的方法
#pragma mark - override 重载父类的方法
- (void)setBackgroundColor:(UIColor *)backgroundColor {
self.backContentView.backgroundColor = backgroundColor;
}
- (void)addSubview:(UIView *)view {
if (view == _backContentView) {
[super addSubview:view];
} else {
[self.backContentView addSubview:view];
}
}
- (void)layoutSubviews {
[super layoutSubviews];
self.backContentView.frame = self.bounds;
[self _setShadowWithShadowRadius:_shadowRadius shadowColor:_shadowColor shadowOffset:_shadowOffset byShadowSide:_shadowSide];
[self _setCornerRadius:_cornerRadius byRoundingCorners:_rectCorner];
}
#pragma mark - setupXxx Methods 初始化的方法
- (void)setup {
[self addSubview:self.backContentView];
}
#pragma mark - public method 共有方法
- (void)yc_shaodw {
[self yc_shaodwRadius:5 shadowColor:[UIColor colorWithWhite:0 alpha:0.3] shadowOffset:CGSizeMake(0, 0) byShadowSide:YCShadowSideAllSides];
}
- (void)yc_verticalShaodwRadius:(CGFloat)shadowRadius shadowColor:(UIColor *)shadowColor shadowOffset:(CGSize)shadowOffset {
[self yc_shaodwRadius:shadowRadius shadowColor:shadowColor shadowOffset:shadowOffset byShadowSide:YCShadowSideTop|YCShadowSideBottom];
}
- (void)yc_horizontalShaodwRadius:(CGFloat)shadowRadius shadowColor:(UIColor *)shadowColor shadowOffset:(CGSize)shadowOffset {
[self yc_shaodwRadius:shadowRadius shadowColor:shadowColor shadowOffset:shadowOffset byShadowSide:YCShadowSideLeft|YCShadowSideRight];
}
- (void)yc_shaodwRadius:(CGFloat)shadowRadius shadowColor:(UIColor *)shadowColor shadowOffset:(CGSize)shadowOffset byShadowSide:(YCShadowSide)shadowSide {
_shadowRadius = shadowRadius;
_shadowColor = shadowColor;
_shadowOffset = shadowOffset;
_shadowSide = shadowSide;
}
- (void)yc_cornerRadius:(CGFloat)cornerRadius {
_cornerRadius = cornerRadius;
_rectCorner = UIRectCornerAllCorners;
}
- (void)yc_cornerRadius:(CGFloat)cornerRadius byRoundingCorners:(UIRectCorner)corners {
_cornerRadius = cornerRadius;
_rectCorner = corners;
}
#pragma mark - Setter/Getter Methods set/get方法
- (UIView *)backContentView {
if (!_backContentView) {
_backContentView = [[UIView alloc] init];
_backContentView.backgroundColor = [UIColor whiteColor];
_backContentView.layer.masksToBounds = YES;
_backContentView.clipsToBounds = YES;
[self insertSubview:_backContentView atIndex:0];
}
return _backContentView;
}
- (CALayer *)topShadowLayer {
if (!_topShadowLayer) {
_topShadowLayer = [[CALayer alloc] init];
}
return _topShadowLayer;
}
- (CALayer *)botShadowLayer {
if (!_botShadowLayer) {
_botShadowLayer = [[CALayer alloc] init];
}
return _botShadowLayer;
}
- (CALayer *)leftShadowLayer {
if (!_leftShadowLayer) {
_leftShadowLayer = [[CALayer alloc] init];
}
return _leftShadowLayer;
}
- (CALayer *)rightShadowLayer {
if (!_rightShadowLayer) {
_rightShadowLayer = [[CALayer alloc] init];
}
return _rightShadowLayer;
}
- (CAShapeLayer *)maskLayer {
if (!_maskLayer) {
_maskLayer = [[CAShapeLayer alloc] init];
}
return _maskLayer;
}
#pragma mark - private method 私有方法
// 绘制圆角
- (void)_setCornerRadius:(CGFloat)cornerRadius byRoundingCorners:(UIRectCorner)corners {
if (cornerRadius <= 0) {
return;
}
UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:self.bounds byRoundingCorners:corners cornerRadii:CGSizeMake(cornerRadius, cornerRadius)];
self.maskLayer.frame = self.bounds;
self.maskLayer.path = maskPath.CGPath;
self.backContentView.layer.mask = self.maskLayer;
}
// 绘制阴影
- (void)_setShadowWithShadowRadius:(CGFloat)shadowRadius shadowColor:(UIColor *)shadowColor shadowOffset:(CGSize)shadowOffset byShadowSide:(YCShadowSide)shadowSide {
if (shadowRadius <= 0) {
return;
}
if (shadowSide & YCShadowSideTop) {
[self _setSingleSideShadowWithShadowRadius:shadowRadius shadowColor:shadowColor shadowOffset:shadowOffset byShadowSide:YCShadowSideTop];
}
if (shadowSide & YCShadowSideBottom) {
[self _setSingleSideShadowWithShadowRadius:shadowRadius shadowColor:shadowColor shadowOffset:shadowOffset byShadowSide:YCShadowSideBottom];
}
if (shadowSide & YCShadowSideLeft) {
[self _setSingleSideShadowWithShadowRadius:shadowRadius shadowColor:shadowColor shadowOffset:shadowOffset byShadowSide:YCShadowSideLeft];
}
if (shadowSide & YCShadowSideRight) {
[self _setSingleSideShadowWithShadowRadius:shadowRadius shadowColor:shadowColor shadowOffset:shadowOffset byShadowSide:YCShadowSideRight];
}
}
// 绘制单边阴影
- (void)_setSingleSideShadowWithShadowRadius:(CGFloat)shadowRadius shadowColor:(UIColor *)shadowColor shadowOffset:(CGSize)shadowOffset byShadowSide:(YCShadowSide)shadowSide {
CALayer *shadowLayer = nil;
UIBezierPath *path = [UIBezierPath bezierPath];
if (shadowSide & YCShadowSideTop) {
shadowLayer = self.topShadowLayer;
shadowLayer.frame = CGRectMake(0, 0, self.frame.size.width, self.frame.size.height*0.5);
[path moveToPoint:CGPointMake(self.bounds.size.width*0.5, self.bounds.size.height*0.5)];
[path addLineToPoint:(CGPointMake(0, 0))];
[path addLineToPoint:(CGPointMake(self.bounds.size.width, 0))];
} else if (shadowSide & YCShadowSideLeft) {
shadowLayer = self.leftShadowLayer;
shadowLayer.frame = CGRectMake(0, 0, self.frame.size.height*0.5, self.frame.size.height);
[path moveToPoint:CGPointMake(self.bounds.size.width*0.5, self.bounds.size.height*0.5)];
[path addLineToPoint:(CGPointMake(0, 0))];
[path addLineToPoint:(CGPointMake(0, self.bounds.size.height))];
} else if (shadowSide & YCShadowSideRight) {
shadowLayer = self.rightShadowLayer;
shadowLayer.frame = CGRectMake(self.frame.size.width*0.5, 0, self.frame.size.width*0.5, self.frame.size.height);
[path moveToPoint:CGPointMake(0, self.bounds.size.height*0.5)];
[path addLineToPoint:(CGPointMake(self.frame.size.width*0.5, 0))];
[path addLineToPoint:(CGPointMake(self.frame.size.width*0.5, self.bounds.size.height))];
} else if (shadowSide & YCShadowSideBottom) {
shadowLayer = self.botShadowLayer;
shadowLayer.frame = CGRectMake(0, self.frame.size.height*0.5, self.frame.size.width, self.frame.size.height*0.5);
[path moveToPoint:CGPointMake(self.bounds.size.width*0.5, 0)];
[path addLineToPoint:(CGPointMake(0, self.bounds.size.height*0.5))];
[path addLineToPoint:(CGPointMake(self.bounds.size.width, self.bounds.size.height*0.5))];
}else{
return;
}
[self.layer insertSublayer:shadowLayer atIndex:0];
shadowLayer.masksToBounds = NO;
CGFloat components[4];
[self _getRGBAComponents:components forColor:_shadowColor];
shadowLayer.shadowColor = [UIColor colorWithRed:components[0] green:components[1] blue:components[2] alpha:1.0].CGColor;
shadowLayer.shadowOpacity = components[3];
shadowLayer.shadowRadius = _shadowRadius;
shadowLayer.shadowOffset = CGSizeMake(0, 0);
shadowLayer.shadowPath = path.CGPath;
}
- (void)_getRGBAComponents:(CGFloat [4])components forColor:(UIColor *)color {
CGColorSpaceRef rgbColorSpace = CGColorSpaceCreateDeviceRGB();
unsigned char resultingPixel[4] = {0};
CGContextRef context = CGBitmapContextCreate(&resultingPixel,
1,
1,
8,
4,
rgbColorSpace,
kCGImageAlphaPremultipliedLast);
CGContextSetFillColorWithColor(context, [color CGColor]);
CGContextFillRect(context, CGRectMake(0, 0, 1, 1));
CGContextRelease(context);
CGColorSpaceRelease(rgbColorSpace);
CGFloat a = resultingPixel[3] / 255.0;
CGFloat unpremultiply = (a != 0.0) ? 1.0 / a / 255.0 : 0.0;
for (int component = 0; component < 3; component++) {
components[component] = resultingPixel[component] * unpremultiply;
}
components[3] = a;
}
@end
@@ -0,0 +1,19 @@
//
// WXYZ_CountDownView.h
// WXReader
//
// Created by Andrew on 2019/4/9.
// Copyright © 2019 Andrew. All rights reserved.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface WXYZ_CountDownView : UIView
@property (nonatomic, assign) NSInteger timeStamp;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,249 @@
//
// WXYZ_CountDownView.m
// WXReader
//
// Created by Andrew on 2019/4/9.
// Copyright © 2019 Andrew. All rights reserved.
//
#import "WXYZ_CountDownView.h"
#import "WXYZ_GCDTimer.h"
@interface WXYZ_CountDownView ()
@property (nonatomic, strong) UILabel *countDownLabel;
@property (nonatomic, strong) UILabel *endActiveLabel;
@property (nonatomic, strong) UILabel *hoursLabel;
@property (nonatomic, strong) UILabel *minutesLabel;
@property (nonatomic, strong) UILabel *secondsLabel;
@property (nonatomic, strong) UILabel *hoursPointLabel;
@property (nonatomic, strong) UILabel *minutesPointLabel;
@property (nonatomic, strong) WXYZ_GCDTimer *timer;
@end
@implementation WXYZ_CountDownView
- (instancetype)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame]) {
[self createSubViews];
}
return self;
}
- (void)createSubViews
{
self.countDownLabel.hidden = NO;
[self addSubview:self.countDownLabel];
self.endActiveLabel.hidden = YES;
[self addSubview:self.endActiveLabel];
[self.countDownLabel addSubview:self.hoursLabel];
[self.countDownLabel addSubview:self.minutesLabel];
[self.countDownLabel addSubview:self.secondsLabel];
[self.countDownLabel addSubview:self.hoursPointLabel];
[self.countDownLabel addSubview:self.minutesPointLabel];
[self.hoursLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(0);
make.top.mas_equalTo(0);
make.width.mas_equalTo(CGFLOAT_MIN);
make.height.mas_equalTo(self.mas_height);
}];
[self.minutesLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(self.hoursLabel.mas_right).with.offset(5);
make.top.mas_equalTo(self.hoursLabel.mas_top);
make.width.mas_equalTo(CGFLOAT_MIN);
make.height.mas_equalTo(self.hoursLabel.mas_height);
}];
[self.secondsLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(self.minutesLabel.mas_right).with.offset(5);
make.top.mas_equalTo(self.hoursLabel.mas_top);
make.width.mas_equalTo(CGFLOAT_MIN);
make.height.mas_equalTo(self.hoursLabel.mas_height);
}];
[self.hoursPointLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(self.hoursLabel.mas_right);
make.top.mas_equalTo(self.hoursLabel.mas_top);
make.width.mas_equalTo(5);
make.height.mas_equalTo(self.hoursLabel.mas_height);
}];
[self.minutesPointLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(self.minutesLabel.mas_right);
make.top.mas_equalTo(self.minutesLabel.mas_top);
make.width.mas_equalTo(5);
make.height.mas_equalTo(self.minutesLabel.mas_height);
}];
}
- (void)setTimeStamp:(NSInteger)timeStamp
{
_timeStamp = timeStamp;
if (_timeStamp != 0) {
[self.timer stopTimer];
[self.timer startTimerWithTimeDuration:timeStamp];
} else {
self.countDownLabel.hidden = YES;
self.endActiveLabel.hidden = NO;
}
}
- (void)getDetailTimeWithTimeStamp:(NSInteger)timeStamp
{
NSInteger ms = timeStamp;
NSInteger ss = 1;
NSInteger mi = ss * 60;
NSInteger hh = mi * 60;
// 剩余的
NSInteger hour = (ms) / hh;// 时
NSInteger minute = (ms - hour * hh) / mi;// 分
NSInteger second = (ms - hour * hh - minute * mi) / ss;// 秒
self.hoursLabel.text = [self formatTimeStringWithTimeStamp:hour];
self.minutesLabel.text = [self formatTimeStringWithTimeStamp:minute];
self.secondsLabel.text = [self formatTimeStringWithTimeStamp:second];
self.hoursPointLabel.hidden = NO;
self.minutesPointLabel.hidden = NO;
[self.hoursLabel mas_updateConstraints:^(MASConstraintMaker *make) {
make.width.mas_equalTo([TFViewHelper getDynamicWidthWithLabel:self.hoursLabel]);
}];
[self.minutesLabel mas_updateConstraints:^(MASConstraintMaker *make) {
make.width.mas_equalTo([TFViewHelper getDynamicWidthWithLabel:self.minutesLabel]);
}];
[self.secondsLabel mas_updateConstraints:^(MASConstraintMaker *make) {
make.width.mas_equalTo([TFViewHelper getDynamicWidthWithLabel:self.secondsLabel]);
}];
}
- (NSString *)formatTimeStringWithTimeStamp:(NSInteger)timeStamp
{
return [[NSString stringWithFormat:@"%zd", timeStamp] intValue] < 10?[NSString stringWithFormat:@"0%zd", timeStamp]:[NSString stringWithFormat:@"%zd", timeStamp];
}
- (UILabel *)countDownLabel
{
if (!_countDownLabel) {
_countDownLabel = [self basicLabel];
}
return _countDownLabel;
}
- (UILabel *)endActiveLabel
{
if (!_endActiveLabel) {
_endActiveLabel = [self basicCountDownLabel];
_endActiveLabel.font = kFont12;
_endActiveLabel.text = TFLocalizedString(@"活动已结束");
}
return _endActiveLabel;
}
- (WXYZ_GCDTimer *)timer
{
if (!_timer) {
WS(weakSelf)
_timer = [[WXYZ_GCDTimer alloc] initCountdownTimerWithTimeDuration:10 immediatelyCallBack:YES];
_timer.timerRunningBlock = ^(NSUInteger runTimes, CGFloat currentTime) {
dispatch_async(dispatch_get_main_queue(), ^{
weakSelf.endActiveLabel.hidden = YES;
weakSelf.countDownLabel.hidden = NO;
[weakSelf getDetailTimeWithTimeStamp:currentTime];
});
};
_timer.timerFinishedBlock = ^{
dispatch_async(dispatch_get_main_queue(), ^{
weakSelf.countDownLabel.hidden = YES;
weakSelf.endActiveLabel.hidden = NO;
});
[[NSNotificationCenter defaultCenter] postNotificationName:Notification_EndOfTimeLimit object:nil];
};
}
return _timer;
}
- (UILabel *)hoursLabel
{
if (!_hoursLabel) {
_hoursLabel = [self basicCountDownLabel];
}
return _hoursLabel;
}
- (UILabel *)minutesLabel
{
if (!_minutesLabel) {
_minutesLabel = [self basicCountDownLabel];
}
return _minutesLabel;
}
- (UILabel *)secondsLabel
{
if (!_secondsLabel) {
_secondsLabel = [self basicCountDownLabel];
}
return _secondsLabel;
}
- (UILabel *)hoursPointLabel
{
if (!_hoursPointLabel) {
_hoursPointLabel = [self basicLabel];
_hoursPointLabel.hidden = YES;
_hoursPointLabel.text = @":";
_hoursPointLabel.textColor = kBlackColor;
_hoursPointLabel.textAlignment = NSTextAlignmentCenter;
}
return _hoursPointLabel;
}
- (UILabel *)minutesPointLabel
{
if (!_minutesPointLabel) {
_minutesPointLabel = [self basicLabel];
_minutesPointLabel.hidden = YES;
_minutesPointLabel.text = @":";
_minutesPointLabel.textColor = kBlackColor;
_minutesPointLabel.textAlignment = NSTextAlignmentCenter;
}
return _minutesPointLabel;
}
- (UILabel *)basicLabel
{
UILabel *label = [[UILabel alloc] init];
label.textAlignment = NSTextAlignmentCenter;
label.numberOfLines = 1;
label.font = kFont12;
return label;
}
- (UILabel *)basicCountDownLabel
{
UILabel *label = [self basicLabel];
label.layer.cornerRadius = 4;
label.backgroundColor = kRedColor;
label.textColor = kWhiteColor;
label.clipsToBounds = YES;
return label;
}
@end

Some files were not shown because too many files have changed in this diff Show More