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