小说绘上架版本

This commit is contained in:
xtfei2011
2021-02-07 11:24:08 +08:00
commit ee5c1c8b12
1762 changed files with 115892 additions and 0 deletions
@@ -0,0 +1,35 @@
//
// IFlyAudioSession.h
// MSCDemo
//
// Created by AlexHHC on 1/9/14.
//
//
#import <Foundation/Foundation.h>
/**
* 音频环境初始化,设置AVAudioSession的Category属性。
*/
@interface IFlyAudioSession : NSObject
/**
* 初始化播音环境,主要用于合成播放器。
*
* 此接口主要根据原来的音频环境,重新优化设置AVAudioSession的Category属性值。<br>
* 若原来的Category属性值为AVAudioSessionCategoryPlayAndRecord,则添加AVAudioSessionCategoryOptionDefaultToSpeakerAVAudioSessionCategoryOptionAllowBluetooth选项;若为其他Category属性值且isMPCenter为NO,则设置Category属性值为AVAudioSessionCategoryPlayback,选项为AVAudioSessionCategoryOptionMixWithOthers;若为其他Category属性值且isMPCenter为YES,则保持原来的设置,不做任何更改。
*
* @param isMPCenter 是否初始化MPPlayerCenter:0不初始化,1初始化。此参数只在AVAudioSession的Category属性值不为AVAudioSessionCategoryPlayAndRecord时设置有效。
*/
+(void) initPlayingAudioSession:(BOOL)isMPCenter;
/**
* 初始化录音环境,主要用于识别录音器。
*
* 设置AVAudioSession的Category属性值为AVAudioSessionCategoryPlayAndRecord,选项为AVAudioSessionCategoryOptionDefaultToSpeaker|AVAudioSessionCategoryOptionAllowBluetooth。
*
* @return 成功返回YES,失败返回NO
*/
+(BOOL) initRecordingAudioSession;
@end
@@ -0,0 +1,24 @@
//
// IFlyContact.h
// msc
//
// Created by ypzhao on 13-3-1.
// Copyright (c) 2013年 IFLYTEK. All rights reserved.
//
#import <Foundation/Foundation.h>
/*!
* 此接口为获取通信录中的联系人。<br>
* 获取联系人是为了在进行语音识别时(sms)能更好的识别出您说的人名,联系人上传是属于个性化的一部分。
*/
@interface IFlyContact : NSObject
/*!
* 获取联系人。<br>
* 调用此方法需要添加 AddressBook.framework 和 Contacts.framework到工程中,调用此方法后可以直接将通信录中的联系人转化为语音云识别的数据结构。您可以将获取的数据通过IFlyDataUploader类,上传到语音云,我们只获取通信录中的人名。
*
* @return 返回联系人信息
*/
- (NSString *) contact;
@end
@@ -0,0 +1,54 @@
//
// IFlyDataUploader.h
// MSC
//
// Created by ypzhao on 13-4-8.
// Copyright (c) 2013年 iflytek. All rights reserved.
//
#import <Foundation/Foundation.h>
@class IFlySpeechError;
/*!
* 数据上传类,主要用于上传语法文件或上传联系人、词表等个性化数据。
*/
@interface IFlyDataUploader : NSObject
/*!
* 数据名称
*/
@property(nonatomic,copy) NSString *dataName;
/*!
* 数据
*/
@property(nonatomic,copy) NSString *data;
/*!
* 上传完成回调
*
* @param result 结果
* @param error 错误码
*/
typedef void(^IFlyUploadDataCompletionHandler)(NSString* result,IFlySpeechError * error);
/*!
* 上传数据
* 此函数用于上传数据,下载的过程是**异步**的。
*
* @param completionHandler -[in] 上传完成回调
* @param name -[in] 上传的内容名称,名称最好和你要上传的数据内容相关,不可以为nil
* @param data -[in] 上传的数据,以utf8编码,不可以为nil
*/
- (void) uploadDataWithCompletionHandler:(IFlyUploadDataCompletionHandler)completionHandler name:(NSString *)name data:(NSString *)data;
/*!
* 设置上传数据参数
*
* @param parameter 参数值
* @param key 参数名
*/
-(void) setParameter:(NSString*) parameter forKey:(NSString*) key;
@end
@@ -0,0 +1,37 @@
//
// IFlyDebugLog.h
// MSC
// description: 程序中的log处理类
// Created by ypzhao on 12-11-22.
// Copyright (c) 2012年 iflytek. All rights reserved.
//
#import <Foundation/Foundation.h>
/*!
* 调试信息
*/
@interface IFlyDebugLog : NSObject
/*!
* 打印调试信息
*
* @param format -[in] 要打印的内容格式
* @param ... -[in] 要打印的内容
*/
+ (void) showLog:(NSString *)format, ...;
/*!
* 将log写入文件中
*/
+ (void) writeLog;
/*!
* 设置是否显示log
*
* @param showLog YES:显示;NO:不显示
*/
+ (void) setShowLog:(BOOL) showLog;
@end
@@ -0,0 +1,48 @@
//
// IFlyISVDelegate.h
// msc_UI
//
// Created by admin on 14-9-15.
// Copyright (c) 2014年 iflytek. All rights reserved.
//
#import <Foundation/Foundation.h>
@class IFlySpeechError;
/*!
* 声纹回调协议
*/
@protocol IFlyISVDelegate
/*!
* 声纹结果回调
*
* @param dic 结果
*/
-(void) onResult:(NSDictionary *)dic;
/*!
* 错误码回调
*
* @param errorCode 错误码
*/
-(void) onCompleted:(IFlySpeechError *) errorCode;
@optional
/*!
* 等待结果
*/
-(void) onRecognition;
/*!
* 音量改变回调
*
* @param volume 音量值
*/
-(void) onVolumeChanged: (int)volume;
@end
@@ -0,0 +1,132 @@
//
// IFlyISVRecognizer.h
// ISV
//
// Created by wangdan on 14-9-6.
// Copyright (c) 2014年 IFlyTEK. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "IFlyISVDelegate.h"
/**
* 声纹接口类
*/
@interface IFlyISVRecognizer : NSObject
{
}
/*!
* The delegate of FlyISVRecognizer responsing to IFlyISVDelegate.
*/
@property (assign) id<IFlyISVDelegate> delegate;
/*!
* FlyISVRecognizer is a kind of Singleton calss.The function can be used as below:<br>
* IFLyISVRecognizer *recognizer=[IFlyISVRecognizer creteRecognizer: self];
*/
+(instancetype) sharedInstance;
/*!
* Genrerate a serial number password<br>
* Princeple:<br>
* 1.Number serial has no 1 in itself;<br>
* 2.The nuber serial has no same number("98765432"is right while "99876543" is wrong)
*
* @param length the serial number's length,length of "98765432" is 8,generally length is 8 and other value is forbidden
*/
-(NSString*) generatePassword:(int)length;
/*!
* Used to get password from server
*
* @param pwdt when pwdt is 1,the function will return chinese text;while pwdt is 2, the funciton will return number serial
*/
-(NSArray*) getPasswordList:(int)pwdt;
/*!
* Used to judge if the engine is running in listenning
*
* @return YES: the engine is listenning;<br>No : the engine is not listenning
*/
-(BOOL) isListening;
/*!
* Used to query or delete the voiceprint model in server
*
* @param cmd "del": delete model;<br>"que": query model;
* @param authid: user id ,can be @"tianxia" or other;
* @param pwdt voiceprint type<br>
* 1: fixed txt voiceprint code ,like @"我的地盘我做主";<br>
* 2: free voiceprint code , user can speek anything,but 5 times trainning the speech shall be same;<br>
* 3: number serial voiceprint code ,like @"98765432" and so on.
* @param ptxt voiceprint txt,only fixed voiceprint and number serial have this,in free voiceprint model this param shall be set nil.
* @param vid another voiceprint type model,user can use this to query or delete model in server can be @"jakillasdfasdjjjlajlsdfhdfdsadff",totally 32 bits;<br>
* NOTES:<br>
* when vid is not nil,then the server will judge the vid first; while the vid is nil, server can still query or delete the voiceprint model by other params.
*/
-(BOOL) sendRequest:(NSString*)cmd authid:(NSString *)auth_id pwdt:(int)pwdt ptxt:(NSString *)ptxt vid:(NSString *)vid err:(int *)err;
/*!
* Set the voiceprint params
*
* | key | value |
* |:---------------:|:-------------------------------------------------:|
* | sst | @"train" or @"verify" |
* | auth_id | @"tianxia" or other |
* | sub | @"ivp" |
* | ptxt | |
* | rgn | @"5" |
* | pwdt | @"1",or @"2", or @"3" |
* | auf | @"audio/L16;rate=16000" or @"audio/L16;rate=8000" |
* | vad_enable | @"1" or @"0" |
* | vad_timeout | @"3000" |
* | vad_speech_tail | @"100" |
*
* @param value 参数值
* @param key 参数类型
*
* @return 设置成功返回YES,失败返回NO
*/
-(BOOL) setParameter:(NSString *)value forKey:(NSString *)key;
/*!
* Get the voiceprint params used the same as function of setParameter
*/
-(NSString*) getParameter:(NSString *)key;
/*!
* Start recording
*/
-(void) startListening;
/*!
* Stop recording
*/
-(void) stopListening;
/*!
* Cancel recording,like function stopListening
*/
-(void) cancel; /* cancel recognization */
@end
@@ -0,0 +1,37 @@
//
// IFlyIdentityResult.h
// IFlyMSC
//
// Created by 张剑 on 15/5/14.
// Copyright (c) 2015年 iflytek. All rights reserved.
//
#import <Foundation/Foundation.h>
/**
* 身份验证结果类
*/
@interface IFlyIdentityResult : NSObject
/**
* json字符串格式结果
*/
@property(nonatomic,retain)NSString* result;
/**
* 创建身份验证结果类实例
*
* @param jsonString json字符串
*
* @return 身份验证结果类实例
*/
+(instancetype)identityResultWithString:(NSString*)jsonString;
/**
* 返回字典格式的结果
*
* @return 字典格式的结果
*/
-(NSDictionary*)dictionaryResults;
@end
@@ -0,0 +1,176 @@
//
// IFlyIdentityVerifier.h
// IFlyMSC
//
// Created by 张剑 on 15/4/22.
// Copyright (c) 2015年 iflytek. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "IFlyIdentityVerifierDelegate.h"
/**
* 身份验证功能类
*/
@interface IFlyIdentityVerifier : NSObject
/*!
* 设置委托对象
*/
@property (nonatomic, assign) id <IFlyIdentityVerifierDelegate> delegate;
/**
* 返回身份验证对象的单例
*
* @return 身份验证对象的单例
*/
+ (instancetype) sharedInstance;
/**
* 销毁身份验证对象单例。
*
* @return 成功返回YES,失败返回NO。
*/
+ (void)purgeSharedInstance;
/**
* 设置参数
*
* @param value 参数值
* @param key 参数名
*
* @return 设置的参数和取值正确返回YES,失败返回NO
*/
- (BOOL)setParameter:(NSString *)value forKey:(NSString *)key;
/**
* 开始会话
* 在这之后会开始各项业务。
*/
- (void)startWorking;
/*
* | ------------- |-----------------------------------------------------------
* | ifr 参数 | 描述
* | ------------- |-----------------------------------------------------------
* | data_format |数据格式:即图片格式,支持jpg(默认),gif
* | ------------- |-----------------------------------------------------------
* | data_encoding |数据压缩编码:即图片压缩编码,支持raw(不压缩,默认值)
* | ------------- |-----------------------------------------------------------
* | wtt |等待超时时间: 支持大于0的整数,默认为3000ms
* | ------------- |-----------------------------------------------------------
* | ivp 参数 | 描述
* | ------------- |-----------------------------------------------------------
* | rgn |训练次数:取值2~9.无默认值,必须明确指定。
* | ------------- |-----------------------------------------------------------
* | |声纹确认门限值,验证得分>=tsd验证通过,否则验证失败(该参数目前不支持,
* | tsd |作为保留参数。)却只范围:0~100.
* | ------------- |-----------------------------------------------------------
* | ptxt |密码文本。从服务端下载,比如数字密码所需要的数字串。
* | ------------- |-----------------------------------------------------------
* | pwdt |密码类型。取值:1(文本密码),2(自由说),3(数字密码).
* | ------------- |-----------------------------------------------------------
* | fin |取消注册。取值:0(不取消,即不生效),1(取消本次注册).
* | ------------- |-----------------------------------------------------------
* | wtt |等待超时时间:描述客户端等待结果的超时时间
* | ------------- |-----------------------------------------------------------
* | vad_enable |VAD功能开关。是否启用VAD处理,取值:1(开启,默认),0(不开启)。
* | ------------- |-----------------------------------------------------------
* | |头部静音最大长度。如果静音长度超过此值,则认为用户此次无有效音频输入。
* | vad_bos |此参数仅在打开VAD功能时生效。(云端暂时没有)。取值:0~30000ms,
* | |默认为10000ms
* | ------------- |-----------------------------------------------------------
* | |尾部静音长度。如果尾部静音长度超过了此值,则认为音频已经结束。
* | |此参数仅在打开VAD功能时生效。(云端暂时没有)。取值:0~30000ms,
* | vad_eos |默认为2000ms
* | ------------- |-----------------------------------------------------------
* | data_encoding |数据压缩编码,音频压缩编码。
* | ------------- |-----------------------------------------------------------
* | data_format |在声纹业务中为音频采样率,取值:16000(默认),8000
* | ------------- |-----------------------------------------------------------
* | ipt 参数 | 描述
* | ------------- |-----------------------------------------------------------
* | scope |操作范围 persongroup
* | ------------- |-----------------------------------------------------------
* | group_id |指定鉴别的组数字或者字符串,唯一值 不为空
* | ------------- |-----------------------------------------------------------
* | |组名称 “”或者不能包含^@,&=*'"等非法字符,且长度不得超过255.
* | group_name |此参数可以为空,对于group_name约束不在云端控制,由前段控制
* | ------------- |-----------------------------------------------------------
* | topc |[top candidates],返回得分最高的候选人数目 取值1-5 默认是1.
* | | 可以开放由用户设置,最大topc值为5
* | ------------- |-----------------------------------------------------------
*
*/
/**
* 写入子业务数据、或者进行模型操作、下载密码。
*
* @param ssub 子业务名
* @param data 数据
* @param offset 偏移量
* @param length 长度
* @param params 参数
* <table>
* <thead>
* <tr><th>*ifr参数</th><th><em>描述</em></th></tr>
* </thead>
* <tbody>
* <tr><td>data_format</td><td>数据格式:即图片格式,支持jpg(默认)gif</td></tr>
* <tr><td>data_encoding</td><td>数据压缩编码:即图片压缩编码,支持raw(不压缩,默认值)</td></tr>
* <tr><td>wtt</td><td>等待超时时间: 支持大于0的整数,默认为3000ms</td></tr>
* </tbody>
* <thead>
* <tr><th>*ivp参数</th><th><em>描述</em></th></tr>
* </thead>
* <tbody>
* <tr><td>rgn</td><td>训练次数:取值2~9.无默认值,必须明确指定。</td></tr>
* <tr><td>tsd</td><td>声纹确认门限值,验证得分>=tsd验证通过,否则验证失败(该参数目前不支持,作为保留参数。)却只范围:0~100.</td></tr>
* <tr><td>ptxt</td><td>密码文本。从服务端下载,比如数字密码所需要的数字串。</td></tr>
* <tr><td>pwdt</td><td>密码类型。取值:1(文本密码),2(自由说),3(数字密码).</td></tr>
* <tr><td>fin</td><td>取消注册。取值:0(不取消,即不生效),1(取消本次注册).</td></tr>
* <tr><td>wtt</td><td>等待超时时间:描述客户端等待结果的超时时间.</td></tr>
* <tr><td>vad_enable</td><td>VAD功能开关。是否启用VAD处理,取值:1(开启,默认),0(不开启)。</td></tr>
* <tr><td>vad_bos</td><td>头部静音最大长度。如果静音长度超过此值,则认为用户此次无有效音频输入。</br>此参数仅在打开VAD功能时生效。(云端暂时没有)。</br>取值:0~30000ms,默认为10000ms</td></tr>
* <tr><td>vad_eos</td><td>尾部静音长度。如果尾部静音长度超过了此值,则认为音频已经结束。</br>此参数仅在打开VAD功能时生效。(云端暂时没有)。</br>取值:0~30000ms,默认为2000ms</td></tr>
* <tr><td>data_encoding</td><td>数据压缩编码,音频压缩编码。</td></tr>
* <tr><td>data_format</td><td>在声纹业务中为音频采样率,取值:16000(默认)8000</td></tr>
* </tbody>
* <thead>
* <tr><th>*ipt参数</th><th><em>描述</em></th></tr>
* </thead>
* <tbody>
* <tr><td>scope</td><td>操作范围 persongroup</td></tr>
* <tr><td>group_id</td><td>指定鉴别的组数字或者字符串,唯一值 不为空</td></tr>
* <tr><td>group_name</td><td>组名称 “”或者不能包含^@,&=*'"等非法字符,且长度不得超过255.此参数可以为空,对于group_name约束不在云端控制,由前段控制</td></tr>
* <tr><td>topc</td><td>组名称[top candidates],返回得分最高的候选人数目 取值1-5 默认是1.可以开放由用户设置,最大topc值为5</td></tr>
* </tbody>
* </table>
*/
-(void)write:(NSString*)ssub data:(NSData*)data offset:(int)offset length:(int)length withParams:(NSString*)params;
/**
* 停止子业务数据写入
*
* @param ssub 子业务名:ivp,ifr,ipt
*/
-(void)stopWrite:(NSString*)ssub;
/**
* 执行模型查询、删除和声纹密码下载等操作
* *注意此方法不能与startWorking方法同时使用。
*
* @param ssub 子业务类型、可选值:ivp(声纹)、ifr(人脸)、ipt(鉴别)
* @param cmd 操作命令,可选值:query、delete、download、add
* @param params 子业务参数,参见write:data:offset:length:withParams:
*/
-(void)execute:(NSString*)ssub cmd:(NSString*)cmd params:(NSString*)params;
/**
* 取消本次会话
*/
- (void)cancel;
@end
@@ -0,0 +1,47 @@
//
// IFlyIdentityVerifierDelegate.h
// IFlyMSC
//
// Created by 张剑 on 15/4/22.
// Copyright (c) 2015年 iflytek. All rights reserved.
//
#import <Foundation/Foundation.h>
@class IFlySpeechError;
@class IFlyIdentityResult;
/**
* 身份验证功能类回调
*/
@protocol IFlyIdentityVerifierDelegate <NSObject>
/*!
* 错误回调
*
* @param error 错误描述类
*/
- (void)onCompleted:(IFlySpeechError *)error;
/*!
* 结果回调
*
* @param results -[out] 结果。
* @param isLast -[out] 是否最后一条结果
*/
- (void)onResults:(IFlyIdentityResult *)results isLast:(BOOL)isLast;
/**
* 扩展接口,用于抛出音量和vad_eos消息
*
* @param eventType 消息类型
* @param arg1 eventType为 Event_volume 时 arg1为音量值
* @param arg2 参数2
* @param obj 扩展参数
*/
- (void)onEvent:(int)eventType arg1:(int)arg1 arg2:(int)arg2 extra:(id)obj;
@end
@@ -0,0 +1,40 @@
//
// IFlyMSC.h
// msc
//
// Created by 张剑 on 15/1/14.
// Copyright (c) 2015年 iflytek. All rights reserved.
//
#ifndef MSC_IFlyMSC_h
#define MSC_IFlyMSC_h
#import "IFlyAudioSession.h"
#import "IFlyDataUploader.h"
#import "IFlyDebugLog.h"
#import "IFlyIdentityVerifier.h"
#import "IFlyIdentityResult.h"
#import "IFlyIdentityVerifierDelegate.h"
#import "IFlyISVDelegate.h"
#import "IFlyISVRecognizer.h"
#import "IFlyRecognizerView.h"
#import "IFlyRecognizerViewDelegate.h"
#import "IFlyResourceUtil.h"
#import "IFlySetting.h"
#import "IFlySpeechConstant.h"
#import "IFlySpeechError.h"
#import "IFlySpeechEvaluator.h"
#import "IFlySpeechEvaluatorDelegate.h"
#import "IFlySpeechEvent.h"
#import "IFlySpeechRecognizer.h"
#import "IFlySpeechRecognizerDelegate.h"
#import "IFlySpeechSynthesizer.h"
#import "IFlySpeechSynthesizerDelegate.h"
#import "IFlySpeechUtility.h"
#import "IFlyUserWords.h"
#import "IFlyPcmRecorder.h"
#import "IFlyVerifierUtil.h"
#import "IFlyVoiceWakeuper.h"
#import "IFlyVoiceWakeuperDelegate.h"
#endif
@@ -0,0 +1,116 @@
//
// IFlyPcmRecorder.h
// MSC
// description:
// Created by ypzhao on 12-11-15.
// Copyright (c) 2012年 iflytek. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <AudioToolbox/AudioQueue.h>
#import <AudioToolbox/AudioFile.h>
#import <AudioToolbox/AudioServices.h>
#import <AudioToolbox/AudioConverter.h>
#import <AVFoundation/AVFoundation.h>
@class IFlyPcmRecorder;
/*!
* 录音协议
*/
@protocol IFlyPcmRecorderDelegate<NSObject>
/*!
* 回调音频数据
*
* @param buffer 音频数据
* @param size 表示音频的长度
*/
- (void) onIFlyRecorderBuffer: (const void *)buffer bufferSize:(int)size;
/*!
* 回调音频的错误码
*
* @param recoder 录音器
* @param error 错误码
*/
- (void) onIFlyRecorderError:(IFlyPcmRecorder*)recoder theError:(int) error;
@optional
/*!
* 回调录音音量
*
* @param power 音量值
*/
- (void) onIFlyRecorderVolumeChanged:(int) power;
@end
/*!
* 录音器控件
*/
@interface IFlyPcmRecorder : NSObject<AVAudioSessionDelegate>
/*!
* 录音委托对象
*/
@property (nonatomic,assign) id<IFlyPcmRecorderDelegate> delegate;
/*!
* 用于设置是否在录音结束后发送Deactive通知,默认是YES:发送
*/
@property (nonatomic,assign) BOOL isNeedDeActive;
/*!
* 单例模式
*
* @return 返回录音对象单例
*/
+ (instancetype) sharedInstance;
/*!
* 开始录音
*
* @return 开启录音成功返回YES,否则返回NO
*/
- (BOOL) start;
/*!
* 停止录音
*/
- (void) stop;
/*!
* 设置音频采样率
*
* @param rate -[in] 采样率,8k/16k
*/
- (void) setSample:(NSString *) rate;
/*!
* 设置录音音量回调时间间隔参数
*/
- (void) setPowerCycle:(float) cycle;
/*!
* 保存录音
*
* @param savePath 音频保存路径
*/
-(void) setSaveAudioPath:(NSString *)savePath;
/*!
* 录音器是否完成
*
* @return 录音器完全结束返回YES,否则返回NO
*/
-(BOOL) isCompleted;
@end
@@ -0,0 +1,134 @@
//
// IFlyRecognizerView.h
// MSC
//
// Created by admin on 13-4-16.
// Copyright (c) 2013年 iflytek. All rights reserved.
//
#import <UIKit/UIKit.h>
@protocol IFlyRecognizerViewDelegate ;
/*!
* 语音识别控件<br>
* 录音时触摸控件结束录音,开始识别(相当于旧版的停止);触摸其他位置,取消录音,结束会话(取消)<br>
* 出错时触摸控件,重新开启会话(相当于旧版的再说一次);触摸其他位置,取消录音,结束会话(取消)
*
*/
@interface IFlyRecognizerView : UIView<NSObject>
/*!
* 设置委托对象
*/
@property(nonatomic,assign)id<IFlyRecognizerViewDelegate> delegate;
/*!
* 初始化控件
*
* @param origin 控件左上角的坐标
*
* @return IFlyRecognizerView 对象
*/
- (id)initWithOrigin:(CGPoint)origin;
/*!
* 初始化控件
*
* @param center 控件中心的坐标
*
* @return IFlyRecognizerView 对象
*/
- (id) initWithCenter:(CGPoint)center;
/*!
* 设置横竖屏自适应
*
* @param autoRotate 默认值YES,横竖屏自适应
*/
- (void) setAutoRotate:(BOOL)autoRotate;
/*
* | ------------- |-----------------------------------------------------------
* | 参数 | 描述
* | ------------- |-----------------------------------------------------------
* | domain |应用的领域: 取值为:iat、search、video、poi、music、asr
* | | iat:普通文本听写;
* | | search:热词搜索;
* | | video:视频音乐搜索;
* | | asr:关键词识别;
* | ------------- |-----------------------------------------------------------
* | vad_bos |前端点检测: 静音超时时间,即用户多长时间不说话则当做超时处理; 单位:ms;
* | | engine指定iat识别默认值为5000
* | | 其他情况默认值为 4000,范围 0-10000。
* | ------------- |-----------------------------------------------------------
* | vad_eos |后断点检测: 后端点静音检测时间,即用户停止说话多长时间内即认为不再输入,
* | | 自动停止录音;单位:ms;
* | | sms 识别默认值为 1800;
* | | 其他默认值为 700,范围 0-10000。
* | ------------- |-----------------------------------------------------------
* | sample_rate |采样率:目前支持的采样率设置有 16000 和 8000。
* | ------------- |-----------------------------------------------------------
* | asr_ptt |标点符号设置: 默认为 1,当设置为 0 时,将返回无标点符号文本。
* | ------------- |-----------------------------------------------------------
* | result_type |返回结果的数据格式: 可设置为json,xmlplain,默认为json。
* | ------------- |-----------------------------------------------------------
* | grammarID |识别的语法id: 只针对 domain 设置为”asr”的应用。
* | ------------- |-----------------------------------------------------------
* | asr_audio_path|音频文件名: 设置此参数后,将会自动保存识别的录音文件。
* | | 路径为Documents/(指定值)。
* | | 不设置或者设置为nil,则不保存音频。
* | ------------- |-----------------------------------------------------------
* | params |扩展参数: 对于一些特殊的参数可在此设置,一般用于设置语义。
* | ------------- |-----------------------------------------------------------
*
*/
/*!
* 设置识别引擎的参数
*
* 识别的引擎参数(key)取值如下:<br>
*
* | 参数 | 描述 |
* |-----------------|-------------------------------------------------------|
* | domain | 应用的领域: 取值为:iat、search、video、poi、music、asr<br>iat:普通文本听写;<br>search:热词搜索;<br>video:视频音乐搜索;<br>asr:关键词识别;|
* | vad_bos | 前端点检测: 静音超时时间,即用户多长时间不说话则当做超时处理; 单位:ms;<br>engine指定iat识别默认值为5000<br>其他情况默认值为 4000,范围 0-10000。|
* | vad_eos | 后断点检测: 后端点静音检测时间,即用户停止说话多长时间内即认为不再输入,自动停止录音;单位:ms;<br>sms 识别默认值为 1800;<br>其他默认值为 700,范围 0-10000。|
* | sample_rate | 采样率:目前支持的采样率设置有 16000 和 8000。|
* | asr_ptt | 标点符号设置: 默认为 1,当设置为 0 时,将返回无标点符号文本。|
* | result_type | 返回结果的数据格式: 可设置为json,xmlplain,默认为json。|
* | grammarID | 识别的语法id: 只针对 domain 设置为”asr”的应用。|
* | asr_audio_path | 音频文件名: 设置此参数后,将会自动保存识别的录音文件。<br>路径为Documents/(指定值)。<br>不设置或者设置为nil,则不保存音频。|
* | params | 扩展参数: 对于一些特殊的参数可在此设置,一般用于设置语义。|
*
* @param value 参数对应的取值
* @param key 识别引擎参数
*
* @return 成功返回YES;失败返回NO
*/
-(BOOL) setParameter:(NSString *) value forKey:(NSString*)key;
/*!
* 获取识别引擎参数
*
* @param key 参数key
*
* @return 参数值
*/
-(NSString*) parameterForKey:(NSString *)key;
/*!
* 开始识别
*
* @return 成功返回YES;失败返回NO
*/
- (BOOL)start;
/*!
* 取消本次识别
*/
- (void)cancel;
@end
@@ -0,0 +1,36 @@
//
// IFlyRecognizerDelegate.h
// MSC
//
// Created by admin on 13-4-16.
// Copyright (c) 2013年 iflytek. All rights reserved.
//
#import <Foundation/Foundation.h>
@class IFlyRecognizerView;
@class IFlySpeechError;
/*!
* 识别回调委托
*/
@protocol IFlyRecognizerViewDelegate <NSObject>
/*!
* 回调返回识别结果
*
* @param resultArray 识别结果,NSArray的第一个元素为NSDictionaryNSDictionary的key为识别结果,sc为识别结果的置信度
* @param isLast -[out] 是否最后一个结果
*/
- (void)onResult:(NSArray *)resultArray isLast:(BOOL) isLast;
/*!
* 识别结束回调
*
* @param error 识别结束错误码
*/
- (void)onCompleted: (IFlySpeechError *) error;
@optional
@end
@@ -0,0 +1,90 @@
//
// IFlyResourceUtil.h
// MSCDemo
//
// Created by admin on 14-6-20.
// Copyright (c) 2014年 iflytek. All rights reserved.
//
#import <Foundation/Foundation.h>
/*!
* 资源工具类
*/
@interface IFlyResourceUtil : NSObject
/*!
* 获取通过MSPSetParam,启动引擎的标识
*
* @return 通过MSPSetParam,启动引擎的标识
*/
+(NSString*) ENGINE_START;
/*!
* 获取通过MSPSetParam,销毁引擎的标识
*
* @return 通过MSPSetParam,销毁引擎的标识
*/
+(NSString*) ENGINE_DESTROY;
/*!
* 获取识别引擎的资源目录标识
*
* @return 识别引擎的资源目录标识
*/
+(NSString*) ASR_RES_PATH;
/*!
* 得到语法构建目录
*
* @return 语法构建目录
*/
+(NSString*) GRM_BUILD_PATH;
/*!
* 获取合成引擎的资源目录标识,同时需要先传入voice_name方可生效
*
* @return 合成引擎的资源目录标识,同时需要先传入voice_name方可生效
*/
+(NSString*) TTS_RES_PATH;
/*!
* 获取唤醒资源的资源目录标识
*
* @return 唤醒资源的资源目录标识
*/
+(NSString*) IVW_RES_PATH;
/*!
* 语法类型
*
* @return 语法类型
*/
+(NSString*) GRAMMARTYPE;
/*!
* 语记SDK专用参数,用于设置本地默认资源路径
*
* @return 本地默认资源路径key字符串
*/
+(NSString*) PLUS_LOCAL_DEFAULT_RES_PATH;
#pragma mark -
/*!
* 资源存放路径
*
* @param path 设置的路径
*
* @return 资源目录
*/
+(NSString*) generateResourcePath:(NSString *)path;
/**
* 获得离线发音人对应的id
*
* @param voiceName 发音人名称
*
* @return 有,发音人对应的id;无,返回nil
*/
+(NSString*) identifierForVoiceName:(NSString*)voiceName;
@end
@@ -0,0 +1,88 @@
//
// IFlySetting.h
// MSC
//
// Created by iflytek on 13-4-12.
// Copyright (c) 2013年 iflytek. All rights reserved.
//
#import <Foundation/Foundation.h>
/*!
* 日志打印等级
*/
typedef NS_OPTIONS(NSInteger, LOG_LEVEL){
/*!
* 全部打印
*/
LVL_ALL = -1,
/*!
* 高,异常分析需要的级别
*/
LVL_DETAIL = 31,
/*!
* 中,打印基本日志信息
*/
LVL_NORMAL = 15,
/*!
* 低,只打印主要日志信息
*/
LVL_LOW = 7,
/*!
* 不打印
*/
LVL_NONE = 0
};
/*!
* 此接口为iflyMSC sdk 配置接口。<br>
* 可以获取版本号,设置日志打印等级等
*/
@interface IFlySetting : NSObject
/*!
* 获取版本号
*
* @return 版本号
*/
+ (NSString *) getVersion;
/*!
* 获取日志等级
*
* @return 返回日志等级
*/
+ (LOG_LEVEL) logLvl;
/*!
* 是否打印控制台log<br>
* 在软件发布时,建议关闭此log。
*
* @param showLog -[in] YES,打印log;NO,不打印
*/
+ (void) showLogcat:(BOOL) showLog;
/*!
* 设置日志msc.log生成路径以及日志等级
*
* | 日志打印等级 | 描述 |
* |------------------------|-----------------------------------|
* | LVL_ALL | 全部打印 |
* | LVL_DETAIL | 高,异常分析需要的级别 |
* | LVL_NORMAL | 中,打印基本日志信息 |
* | LVL_LOW | 低,只打印主要日志信息 |
* | LVL_NONE | 不打印 |
*
* @param level -[in] 日志打印等级
*/
+ (void) setLogFile:(LOG_LEVEL) level;
/*!
* 设置日志文件的路径<br>
* 日志文件默认存放在Documents目录。
*
* @param path -[in] 日志文件的全路径
*/
+ (void) setLogFilePath:(NSString*) path;
@end
@@ -0,0 +1,945 @@
//
// IFlySpeechConstant.h
// MSCDemo
//
// Created by iflytek on 5/9/14.
// Copyright (c) 2014 iflytek. All rights reserved.
//
#import <Foundation/Foundation.h>
/*!
* 公共常量类<br>
* 主要定义参数的key value值
*/
@interface IFlySpeechConstant : NSObject
#pragma mark - 通用参数key
/*!
* 语音应用ID<br>
* 通过开发者网站申请
*
* @return 语音应用IDkey
*/
+(NSString*)APPID;
/*!
* 语言区域。
*
* @return 语言区域key。
*/
+(NSString*)ACCENT;
/*!
* 语言区域。
*
* @return 普通话value。
*/
+(NSString*)ACCENT_MANDARIN;
/*!
* 语言区域。
*
* @return 河南话value。
*/
+(NSString*)ACCENT_HENANESE;
/*!
* 语言区域。
*
* @return 四川话value。
*/
+(NSString*)ACCENT_SICHUANESE;
/*!
* 语言区域。
*
* @return 粤语value。
*/
+(NSString*)ACCENT_CANTONESE;
/*!
* 语言<br>
* 支持:zh_cnzh_twen_us<br>
*
* @return 语言key
*/
+(NSString*)LANGUAGE;
/*!
* 语言
*
* @return 中文value
*/
+(NSString*)LANGUAGE_CHINESE;
/*!
* 语言
*
* @return 中文台湾value
*/
+(NSString*)LANGUAGE_CHINESE_TW;
/*!
* 语言
*
* @return 英文value
*/
+(NSString*)LANGUAGE_ENGLISH;
/*!
* 语言
*
* @return 俄语value
*/
+(NSString*)LANGUAGE_RUSSIAN;
/*!
* 语言
*
* @return 日语value
*/
+(NSString*)LANGUAGE_JAPANESE;
/*!
* 语言
*
* @return 法语value
*/
+(NSString*)LANGUAGE_FRENCH;
/*!
* 语言
*
* @return 西班牙语value
*/
+(NSString*)LANGUAGE_SPANISH;
/*!
* 语言
*
* @return 韩语value
*/
+(NSString*)LANGUAGE_KOREAN;
/*!
* 返回结果的数据格式,可设置为json,xml,plain,默认为json。
*
* @return 返回结果的数据格式key
*/
+(NSString*)RESULT_TYPE;
/*!
* 应用领域。
*
* @return 应用领域key
*/
+(NSString*)IFLY_DOMAIN;
/*!
* 个性化数据上传类型
*
* @return 个性化数据上传类型key
*/
+(NSString*)DATA_TYPE;
/*!
* 语音输入超时时间<br>
* 单位:ms,默认30000
*
* @return 语音输入超时时间key
*/
+(NSString*)SPEECH_TIMEOUT;
/*!
* 网络连接超时时间<br>
* 单位:ms,默认20000
*
* @return 网络连接超时时间key
*/
+(NSString*)NET_TIMEOUT;
/*!
* 业务类型。
*
* @return 业务类型key。
*/
+(NSString*)SUBJECT;
/*!
* 扩展参数。
*
* @return 扩展参数key。
*/
+(NSString*)PARAMS;
/*!
* 加密参数
*
* 支持类型:ssl 加密 tcp 非加密 默认:tcp<br>
* 建议对安全性要求较高时使用ssl。
*
* @return 加密参数key
*/
+(NSString*)PROT_TYPE;
/*!
* ssl证书内容
*
* @return ssl证书内容key
*/
+(NSString*)SSL_CERT;
/*!
* 录音音量返回时间间隔。
*
* @return 间隔key。
*/
+(NSString*)POWER_CYCLE;
/*!
* 合成、识别、唤醒、评测、声纹等业务采样率。
*
* @return 合成及识别采样率key。
*/
+(NSString*)SAMPLE_RATE;
/*!
* 合成、识别、唤醒、声纹等业务采样率。
*
* @return 合成及识别采样率8K Value。
*/
+(NSString*)SAMPLE_RATE_8K;
/*!
* 合成、识别、唤醒、评测、声纹等业务采样率。
*
* @return 合成及识别采样率16K Value。
*/
+(NSString*)SAMPLE_RATE_16K;
/*!
* 引擎类型。<br>
* 可选:localcloudauto<br>
* 默认:auto
*
* @return 引擎类型key。
*/
+(NSString*)ENGINE_TYPE;
/*!
* 本地xtts识别引擎。
*
* @return 本地识别引擎value。
*/
+(NSString*)TYPE_LOCAL_XTTS;
/*!
* 本地识别引擎。
*
* @return 本地识别引擎value。
*/
+(NSString*)TYPE_LOCAL;
/*!
* 云端识别引擎。
*
* @return 云端识别引擎value。
*/
+(NSString*)TYPE_CLOUD;
/*!
* 混合识别引擎。
*
* @return 混合识别引擎value。
*/
+(NSString*)TYPE_MIX;
/*!
* 引擎根据当前配置进行选择。
*
* @return 引擎根据当前配置进行选择value。
*/
+(NSString*)TYPE_AUTO;
/*!
* 输入文本编码格式。
*
* @return 编码格式key。
*/
+(NSString*)TEXT_ENCODING;
/*!
* 结果编码格式。
*
* @return 结果编码格式key。
*/
+(NSString*)RESULT_ENCODING;
/*!
* 是否初始化播放器<br>
* SDK内部播放器采用音频队列实现,有部分外部需求需要自定义音频队列,可以通过此开关控制<br>
* 0:不初始化,非0或者参数为空:初始化,默认初始化
*
* @return 是否初始化播放器参数key
*/
+(NSString*)PLAYER_INIT;
/*!
* 是否播放器结束后发送deactive系统通知<br>
* SDK内部播放器结束后可通过此开关发送deactive系统通知,使其他被中断的音频应用解除中断<br>
* 0:不发送,非0或者参数为空:发送,默认发送
*
* @return 是否播放器结束后发送deactive系统通知参数key
*/
+(NSString*)PLAYER_DEACTIVE;
/**
* 是否初始化录音器<br>
* SDK内部录音器采用音频队列实现,有部分外部需求需要自定义音频队列,可以通过此开关控制<br>
* 0:不初始化,非0或者参数为空:初始化,默认初始化
*
* @return 是否初始化录音器参数key
*/
+(NSString*)RECORDER_INIT;
/**
* 是否录音器结束后发送deactive系统通知<br>
* SDK内部录音器结束后可通过此开关发送deactive系统通知,使其他被中断的音频应用解除中断<br>
* 0:不发送,非0或者参数为空:发送,默认发送
*
* @return 是否录音器结束后发送deactive系统通知参数key
*/
+(NSString*)RECORDER_DEACTIVE;
#pragma mark - 合成相关设置key
/*!
* 语速<br>
* 范围 0~100 默认值:50
*
* @return 语速key
*/
+(NSString*)SPEED;
/*!
* 音调<br>
* 范围(0~100)默认值:50
*
* @return 音调key
*/
+(NSString*)PITCH;
/*!
* 合成录音保存路径
*
* 注意:只需要设置文件名则可,会自动拼接到[IFlySetting setLogFilePath]接口设置的目录后
*
* @return 合成录音保存路径key
*/
+(NSString*)TTS_AUDIO_PATH;
/**
* 启用VAD功能
*
* @return 启用VAD功能key
*/
+(NSString*)VAD_ENABLE;
/*!
* VAD前端点超时<br>
* 范围:0-10000(单位ms)
*
* @return VAD前端点超时key
*/
+(NSString*)VAD_BOS;
/*!
* VAD后端点超时。<br>
* 可选范围:0-10000(单位ms)
*
* @return VAD后端点超时key
*/
+(NSString*)VAD_EOS;
/*
* 云端支持如下发音人:
* 对于网络TTS的发音人角色,不同引擎类型支持的发音人不同,使用中请注意选择。
*
* |--------|----------------|
* | 发音人 | 参数 |
* |--------|----------------|
* | 小燕 | xiaoyan |
* |--------|----------------|
* | 小宇 | xiaoyu |
* |--------|----------------|
* | 凯瑟琳 | catherine |
* |--------|----------------|
* | 亨利 | henry |
* |--------|----------------|
* | 玛丽 | vimary |
* |--------|----------------|
* | 小研 | vixy |
* |--------|----------------|
* | 小琪 | vixq |
* |--------|----------------|
* | 小峰 | vixf |
* |--------|----------------|
* | 小梅 | vixl |
* |--------|----------------|
* | 小莉 | vixq |
* |--------|----------------|
* | 小蓉 | vixr |
* |--------|----------------|
* | 小芸 | vixyun |
* |--------|----------------|
* | 小坤 | vixk |
* |--------|----------------|
* | 小强 | vixqa |
* |--------|----------------|
* | 小莹 | vixyin |
* |--------|----------------|
* | 小新 | vixx |
* |--------|----------------|
* | 楠楠 | vinn |
* |--------|----------------|
* | 老孙 | vils |
* |--------|----------------|
*/
/*!
* 发音人
*
* 云端支持如下发音人:<br>
* 对于网络TTS的发音人角色,不同引擎类型支持的发音人不同,使用中请注意选择。<br>
*
* | 发音人 | 参数 |
* |:--------:|:----------------:|
* | 小燕 | xiaoyan |
* | 小宇 | xiaoyu |
* | 凯瑟琳 | catherine |
* | 亨利 | henry |
* | 玛丽 | vimary |
* | 小研 | vixy |
* | 小琪 | vixq |
* | 小峰 | vixf |
* | 小梅 | vixl |
* | 小莉 | vixq |
* | 小蓉 | vixr |
* | 小芸 | vixyun |
* | 小坤 | vixk |
* | 小强 | vixqa |
* | 小莹 | vixyin |
* | 小新 | vixx |
* | 楠楠 | vinn |
* | 老孙 | vils |
*
* @return 发音人key
*/
+(NSString*)VOICE_NAME;
/*!
* 发音人ID key。
*
* @return 发音人ID key
*/
+(NSString*)VOICE_ID;
/*!
* 发音人语种 key。
*
* 参数值:0:Auto 1:中文 2英文 ,默认 0.
*
* @return 发音人ID key
*/
+(NSString*)VOICE_LANG;
/*!
* 音量<br>
* 范围(0~100 默认值:50
*
* @return 音量key
*/
+(NSString*)VOLUME ;
/*!
* 合成音频播放缓冲时间<br>
* 即缓冲多少秒音频后开始播放,如tts_buffer_time=1000;<br>
* 默认缓冲1000ms毫秒后播放。
*
* @return 合成音频播放缓冲时间缓冲时间key
*/
+(NSString*)TTS_BUFFER_TIME ;
/*!
* 合成数据是否即时返回
*
* 是否需要数据回调,为1时,当合成一段音频会通过onEvent回调返回,直接合成结束;<br>
* 设置为1为即时返回;0为非即时返回;默认值为0;
*
* @return 合成数据即时返回key
*/
+(NSString*)TTS_DATA_NOTIFY;
/*!
* 预合成文本
*
* @return 预合成文本参数key
*/
+(NSString*)NEXT_TEXT;
/*!
* 是否需要打开MPPlayingInfocenter<br>
* 是否需要初始化MPPlayerCenter的属性;0:需要初始化,1:不初始化
*
* @return 是否需要打开MPPlayingInfocenter 参数key
*/
+(NSString*)MPPLAYINGINFOCENTER;
#pragma mark - 识别、听写、语义相关设置key
/*!
* 录音源<br>
* 录音时的录音方式,默认为麦克风,设置为1;<br>
* 如果需要外部送入音频,设置为-1,通过WriteAudio接口送入音频。
*
* @return 录音源key
*/
+(NSString*)AUDIO_SOURCE;
/*!
* 识别录音保存路径
*
* @return 识别录音保存路径key
*/
+(NSString*) ASR_AUDIO_PATH;
/*!
* 设置是否开启语义
*
* @return 设置是否开启语义key
*/
+(NSString*)ASR_SCH;
/*!
* 设置是否有标点符号
*
* @return 设置是否有标点符号key
*/
+(NSString*)ASR_PTT;
/*!
* ASR_PTT 参数值:设置带标点符号
*
* @return 设置是有标点符号Value
*/
+(NSString*)ASR_PTT_HAVEDOT;
/*!
* ASR_PTT 参数值:设置不带标点符号
*
* @return 设置是无标点符号Value
*/
+(NSString*)ASR_PTT_NODOT;
/*!
* 本地语法名称。<br>
* 本地语法名称,对应云端的有CLOUD_GRAMMAR
*
* @return 本地语法名称key。
*/
+(NSString*)LOCAL_GRAMMAR;
/*!
* 云端语法ID。<br>
* 云端编译语法返回的表示,早期版本使用GRAMMAR_ID,仍然兼容,但建议使用新的。
*
* @return 云端语法ID key。
*/
+(NSString*)CLOUD_GRAMMAR;
/*!
* 语法类型
*
* @return 语法类型key
*/
+(NSString*)GRAMMAR_TYPE;
/*!
* 语法内容。
*
* @return 语法内容key。
*/
+(NSString*)GRAMMAR_CONTENT;
/*!
* 字典内容。
*
* @return 字典内容key。
*/
+(NSString*)LEXICON_CONTENT;
/*!
* 字典名字。
*
* @return 字典名字key。
*/
+(NSString*)LEXICON_NAME;
/*!
* 语法名称列表。
*
* @return 语法名称列表key。
*/
+(NSString*)GRAMMAR_LIST;
/*!
* 开放语义协议版本号。<br>
* 如需使用请在http://osp.voicecloud.cn/上进行业务配置
*
* @return 开放语义协议版本号key。
*/
+(NSString*)NLP_VERSION;
#pragma mark - 唤醒相关设置key
/*!
* 唤醒门限值。
*
* @return 唤醒门限值key。
*/
+(NSString*)IVW_THRESHOLD;
/*!
* 唤醒服务类型。
*
* @return 唤醒服务类型key。
*/
+(NSString*)IVW_SST;
/*!
* 唤醒+识别。
*
* @return 唤醒+识别key。
*/
+(NSString*)IVW_ONESHOT;
/*!
* 唤醒工作方式<br>
* 1:表示唤醒成功后继续录音,0:表示唤醒成功后停止录音。
*
* @return 唤醒工作方式key
*/
+(NSString*)KEEP_ALIVE;
/*!
* 唤醒录音保存路径
*
* @return 唤醒录音保存路径key
*/
+(NSString*) IVW_AUDIO_PATH;
#pragma mark - 评测相关设置key
/*!
* 评测类型<br>
* 可选值:read_syllable(英文评测不支持):单字;read_word:词语;read_sentence:句子;read_chapter(待开放):篇章。
*
* @return 评测类型 key
*/
+(NSString*)ISE_CATEGORY;
/*!
* 评测结果等级<br>
* 可选值:complete:完整 plain:简单
*
* @return 评测结果等级 key
*/
+(NSString*)ISE_RESULT_LEVEL;
/*!
* 评测结果格式<br>
* 可选值:xml;plain
*
* @return 评测结果格式 key
*/
+(NSString*)ISE_RESULT_TYPE;
/*!
* 评测录音保存路径
*
* @return 评测录音保存路径key
*/
+(NSString*) ISE_AUDIO_PATH;
/*!
* 朗读跟踪,只对句子和篇章有效<br>
* 可选值:enable:开启;disable:关闭。
*
* @return 朗读跟踪 key
*/
+(NSString*)ISE_AUTO_TRACKING;
/*!
* 跟踪模式<br>
* 可选值:easy:简单;hard:复杂。
*
* @return 跟踪模式 key
*/
+(NSString*)ISE_TRACK_TYPE;
#pragma mark - 语记SDK业务key
/*!
* 本地所有资源
*
* @return 本地所有资源key
*/
+ (NSString *)PLUS_LOCAL_ALL;
/*!
* 本地合成资源
*
* @return 本地合成资源key
*/
+ (NSString *)PLUS_LOCAL_TTS;
/*!
* 本地识别资源
*
* @return 本地识别资源key
*/
+ (NSString *)PLUS_LOCAL_ASR;
/*!
* 本地唤醒资源
*
* @return 本地唤醒资源key
*/
+ (NSString *)PLUS_LOCAL_IVW;
#pragma mark - 身份验证业务key
/*!
* auth_id<br>
* 用于用户注册和登录、查询、删除等业务时标识用户身份
*
* @return 用户标识
*/
+ (NSString*)MFV_AUTH_ID;
/*!
* 请求业务类型,可选值:mfv(默认,融合验证),ivp(声纹),ifr(人脸)
*
* @return 请求业务类型key
*/
+ (NSString*)MFV_SUB;
/*!
* 会话类型,不同sub有不同的sst取值。<br>
* ifrenrollverifyidentifyreenrollquerydelete<br>
* ivpenrolltrain),verifyreenrollquerydeletedownload
*
* @return 会话类型key
*/
+ (NSString*)MFV_SST;
/*!
* 融合验证模式,仅在融合验证场景下使用。可选值:sin(单一生物特征数据验证),mix(混合生物特征数据验证),agi(灵活生物特征数据验证)
*
* @return 融合验证模式key
*/
+ (NSString*)MFV_VCM;
/*!
* 特征场景,用来说明本次验证将涉及的业务。可选值:ivp,ifr,ivp|ifr
*
* @return 特征场景 key
*/
+ (NSString*)MFV_SCENES;
/*!
* 确认周期(affirmance cycle,单位:s),用户设置的确认超时时间(生命周期),仅在灵活融合验证场景下使用
*
* @return 确认周期key
*/
+ (NSString*)MFV_AFC;
/*!
* 数据保存路径
*
* @return 数据保存路径key
*/
+ (NSString*)MFV_DATA_PATH;
/*!
* 训练次数:取值2~9.无默认值,必须明确指定。
*
* @return 训练次数key
*/
+ (NSString*)MFV_RGN;
/*!
* 声纹确认门限值,验证得分>=tsd验证通过,否则验证失败(该参数目前不支持,作为保留参数。)却只范围:0~100.
*
* @return 声纹确认门限值key
*/
+ (NSString*)MFV_TSD;
/*!
* 密码文本。从服务端下载,比如数字密码所需要的数字串。
*
* @return 密码文本key
*/
+ (NSString*)MFV_PTXT;
/*!
* 密码类型。取值:1(文本密码),2(自由说),3(数字密码).
*
* @return 密码类型key
*/
+ (NSString*)MFV_PWDT;
/*!
* 取消注册。取值:0(不取消,即不生效),1(取消本次注册).
*
* @return 取消注册key
*/
+ (NSString*)MFV_FIN;
/*!
* 等待超时时间:描述客户端等待结果的超时时间
*
* @return 等待超时时间:key
*/
+ (NSString*)MFV_WTT;
/*!
* 数据格式<br>
* 声纹为音频采样率支持:16000和8000;人脸为图片格式,支持jpg和gif
*
* @return 数据格式key
*/
+ (NSString*)MFV_DATA_FORMAT;
/*!
* 数据压缩编码<br>
* 声纹为;人脸支持raw,不对图片压缩
*
* @return 数据压缩编码key
*/
+ (NSString*)MFV_DATA_ENCODING;
#pragma mark - 人脸业务key
//1. sub 取值: wfr 用途: 用于区分业务类型,web访问方式中,nginx配置不用使用,但是在结构化日志和染色日志记录中使用。
//2. sst 取值: reg、verify、detect、align 用途: 指定本路会话是属于何种性质
// + 人脸图像注册(reg):上传图像,验证图像的有效性,然后存储起来,作为数据源。
// + 人脸图像验证(verify):通过与指定源图像比较,验证人脸相似性。
// + 人脸图像检测(detect):能够检测出不同姿态方位的人脸在图中的位置。
// + 人脸图像聚焦(align):在给定人脸框下自动标定出两眼、鼻尖、嘴角的坐标。
//3. aue 取值: raw 用途: 图像压缩格式,现在引擎不支持图像压缩,aue只能取值raw
//4. pset 取值: 整数 用途: 人脸识别验证阈值,取值可以是负数也可以是整数。
//5. skip 取值: true/false 用途: 后台图片处理是否进行过滤。true表示不过滤,false表示过滤
//6. gid 取值: *********** 用途: 图像模型id,如:4a6c124ed6b78436ee5aac4563f13eb5
//7. appid 取值:用户申请的appid 用途: 验证用户
/*!
* sub 默认值:wfr<br>
* 用于区分业务类型,web访问方式中,nginx配置不用使用,但是在结构化日志和染色日志记录中使用。
*/
+ (NSString*) FACE_SUB;
/*!
* WFR<br>
* sub参数的默认值
*/
+ (NSString*) FACE_WFR;
/*!
* sst<br>
* 指定本路会话是属于何种性质
*/
+ (NSString*) FACE_SST;
/*!
* REG<br>
* 人脸图像注册(reg):上传图像,验证图像的有效性,然后存储起来,作为数据源。
*/
+ (NSString*) FACE_REG;
/*!
* VERIFY<br>
* 人脸图像验证(verify):通过与指定源图像比较,验证人脸相似性。
*/
+ (NSString*) FACE_VERIFY;
/*!
* DETECT<br>
* 人脸图像检测(detect):能够检测出不同姿态方位的人脸在图中的位置。
*/
+ (NSString*) FACE_DETECT;
/*!
* ALIGN<br>
* 人脸图像聚焦(align):在给定人脸框下自动标定出两眼、鼻尖、嘴角的坐标。
*/
+ (NSString*) FACE_ALIGN;
/*!
* ATTR<br>
* 面部属性识别(attr):对面部属性进行识别:例如秃顶、刘海、大嘴、模糊、眼镜等。
*/
+ (NSString*) FACE_ATTR;
/*!
* AUE<br>
* 图像压缩格式,现在引擎不支持图像压缩,aue只能取值raw
*/
+ (NSString*) FACE_AUE;
/*!
* RAW<br>
* AUE参数的值
*/
+ (NSString*) FACE_RAW;
/*!
* PSET<br>
* 人脸识别验证阈值,取值可以是负数也可以是整数。
*/
+ (NSString*) FACE_PSET;
/*!
* SKIP<br>
* 后台图片处理是否进行过滤。true表示不过滤,false表示过滤,传入字符串@“true”或@“false”
*/
+ (NSString*) FACE_SKIP;
/*!
* GID<br>
* 图像模型id,如:4a6c124ed6b78436ee5aac4563f13eb5
*/
+ (NSString*) FACE_GID;
/*!
* auth_id<br>
* 用于用户注册和登录、查询、删除等业务时标识用户身份
*
* @return 用户标识
*/
+ (NSString*)FACE_AUTH_ID;
/*!
* DVC<br>
* 用户设备编号,用于验证用户
*/
+ (NSString*) FACE_DVC;
@end
@@ -0,0 +1,58 @@
//
// IFlySpeechError.h
// MSC
//
// Created by iflytek on 13-3-19.
// Copyright (c) 2013年 iflytek. All rights reserved.
//
#ifndef __IFlySpeechError__
#define __IFlySpeechError__
#import <Foundation/Foundation.h>
/*!
* 错误描述类
*/
@interface IFlySpeechError : NSObject
/*!
* 错误码
*/
@property(nonatomic,assign) int errorCode;
/*!
* 错误码类型
*/
@property(nonatomic,assign) int errorType;
/*!
* 错误描述
*/
@property(nonatomic,retain) NSString* errorDesc;
/*!
* 初始化
*
* @param errorCode -[in] 错误码
*
* @return IFlySpeechError对象
*/
+ (instancetype) initWithError:(int) errorCode;
/*!
* 获取错误码
*
* @return 错误码
*/
-(int) errorCode;
/*!
* 获取错误描述
*
* @return 错误描述
*/
- (NSString *) errorDesc;
@end
#endif
@@ -0,0 +1,101 @@
//
// IFlySpeechEvaluator.h
// msc
//
// Created by jianzhang on 14-1-13
// Copyright (c) 2013年 iflytek. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "IFlySpeechEvaluatorDelegate.h"
#define IFLY_AUDIO_SOURCE_MIC @"1"
#define IFLY_AUDIO_SOURCE_STREAM @"-1"
/*!
* 语音评测类
*/
@interface IFlySpeechEvaluator : NSObject <IFlySpeechEvaluatorDelegate>
/*!
* 设置委托对象
*/
@property (assign) id <IFlySpeechEvaluatorDelegate> delegate;
/*!
* 返回评测对象的单例
*
* @return 别对象的单例
*/
+ (instancetype)sharedInstance;
/*!
* 销毁评测对象。
*
* @return 成功返回YES,失败返回NO。
*/
- (BOOL)destroy;
/*!
* 设置评测引擎的参数
*
* @param value 评测引擎参数值
* @param key 评测引擎参数
*
* @return 设置的参数和取值正确返回YES,失败返回NO
*/
- (BOOL)setParameter:(NSString *)value forKey:(NSString *)key;
/*!
* 获得评测引擎的参数
*
* @param key 评测引擎参数
*
* @return key对应的参数值
*/
- (NSString*)parameterForKey:(NSString *)key;
/*!
* 开始评测<br>
* 同时只能进行一路会话,这次会话没有结束不能进行下一路会话,否则会报错
*
* @param data 评测的试题
* @param params 评测的参数
* @return 成功返回YES,失败返回NO
*/
- (BOOL)startListening:(NSData *)data params:(NSString *)params;
/*!
* 停止录音<br>
* 调用此函数会停止录音,并开始进行语音识别
*/
- (void)stopListening;
/*!
* 取消本次会话
*/
- (void)cancel;
@end
/*!
* 音频流评测<br>
* 音频流评测可以将文件分段写入
*/
@interface IFlySpeechEvaluator(IFlyStreamISERecognizer)
/*!
* 写入音频流
*
* @param audioData 音频数据
*
* @return 写入成功返回YES,写入失败返回NO
*/
- (BOOL) writeAudio:(NSData *) audioData;
@end
@@ -0,0 +1,64 @@
//
// IFlySpeechEvaluatorDelegate.h
// msc
//
// Created by admin on 13-6-19.
// Copyright (c) 2013年 iflytek. All rights reserved.
//
#import <Foundation/Foundation.h>
@class IFlySpeechError;
/*!
* 评测协议
*/
@protocol IFlySpeechEvaluatorDelegate <NSObject>
/*!
* 音量和数据回调
*
* @param volume 音量
* @param buffer 音频数据
*/
- (void)onVolumeChanged:(int)volume buffer:(NSData *)buffer;
/*!
* 开始录音回调<br>
* 当调用了`startListening`函数之后,如果没有发生错误则会回调此函数。如果发生错误则回调onCompleted:函数
*/
- (void)onBeginOfSpeech;
/*!
* 停止录音回调<br>
* 当调用了`stopListening`函数或者引擎内部自动检测到断点,如果没有发生错误则回调此函数。<br>
* 如果发生错误则回调onCompleted:函数
*/
- (void)onEndOfSpeech;
/*!
* 正在取消
*/
- (void)onCancel;
/*!
* 评测错误回调
*
* 在进行语音评测过程中的任何时刻都有可能回调此函数,你可以根据errorCode进行相应的处理.当errorCode没有错误时,表示此次会话正常结束,否则,表示此次会话有错误发生。特别的当调用`cancel`函数时,引擎不会自动结束,需要等到回调此函数,才表示此次会话结束。在没有回调此函数之前如果重新调用了`startListenging`函数则会报错误。
*
* @param errorCode 错误描述类
*/
- (void)onCompleted:(IFlySpeechError *)errorCode;
/*!
* 评测结果回调<br>
* 在评测过程中可能会多次回调此函数,你最好不要在此回调函数中进行界面的更改等操作,只需要将回调的结果保存起来。
*
* @param results -[out] 评测结果。
* @param isLast -[out] 是否最后一条结果
*/
- (void)onResults:(NSData *)results isLast:(BOOL)isLast;
@end
@@ -0,0 +1,151 @@
//
// IFlySpeechEvent.h
// MSCDemo
//
// Created by admin on 14-8-12.
// Copyright (c) 2014年 iflytek. All rights reserved.
//
#import <Foundation/Foundation.h>
/*!
* 事件类型
*/
typedef NS_ENUM(NSUInteger,IFlySpeechEventType){
/*!
* 网络状态消息<br>
* 在消息到达时,可通过onEvent的第2个参数arg1,获取当前网络连接状态值
*/
IFlySpeechEventTypeNetPref = 10001,
/*!
* 转写音频文件消息<br>
* 在录音模式下,成功创建音频文件时返回。可通过onEvent第4个参数data,指定Key为[IFlySpeechConstant IST_AUDIO_PATH],获取音频文件绝对路径.或通过[IFlySpeechTranscripter getParameter:[IFlySpeechConstant IST_AUDIO_PATH]],获取音频文件绝对路径.
*/
IFlySpeechEventTypeISTAudioFile = 10004,
/*!
* 转写已上传字节消息<br>
* 在消息到达时,通过onEvent的第二个参数arg1,获取已确认上传到服务器的字节数.若当前音频源为非写音频模式,还可通过onEvent
* 的第三个参数arg2,获取当前所有音频的字节大小.录音模式时,由于所有音频字节大小会变。当停止音频输入后(等待录音时间超时[IFlySpeechConstant SPEECH_TIMEOUT],或调用[IFlySpeechTranscripter stopTranscripting]),且服务器收到所有音频时,第四个参数data,将包含完成标记的布尔值(true),可通过data调用指定KEY为KCIFlySpeechEventKeyISTUploadComplete获取。此消息可能多次返回.
*/
IFlySpeechEventTypeISTUploadBytes = 10006,
/*!
* 转写缓存剩余<br>
* 此消息仅在音频源为-1时需要关注,在调用[IFlySpeechTranscripter writeAudio]写音频时,应该关注此事件。<br>
* 此事件在调用写音频接口、及音频最后被写入底库库时分别回调一次。当事件回调时,通过onEvent的第二个参数arg1,获取当前剩余的缓存大小,当缓存小于要写入的音频时,应该先暂停写音频数据,直到下次缓存大小大于要写入的音频时.最大缓存为128KByte。
*/
IFlySpeechEventTypeISTCacheLeft = 10007,
/*!
* 转写结果等待时间消息<br>
* 在消息到达时,通过 onEvent的第二个参数arg1,获取当前结果需要的时间.<br>
* 此消息可能多次返回,返回时间不定,且不一定会返回.
*/
IFlySpeechEventTypeISTResultTime= 10008,
/*!
* 转写转写音频同步ID消息<br>
* 在消息到达时,通过 onEvent的第二个参数arg1,获取当前写音频同步ID.<br>
* 此消息可能多次返回.
*/
IFlySpeechEventTypeISTSyncID= 10009,
/*!
* 会话开始消息<br>
* 在会话开始成功后返回
*/
IFlySpeechEventTypeSessionBegin = 10010,
/*!
* 会话结束消息<br>
* 在会话结束前返回
*/
IFlySpeechEventTypeSessionEnd = 10011,
/*!
* 音量消息,在得到音量时抛出,暂时只有身份验证的声纹业务用到
*/
IFlySpeechEventTypeVolume = 10012,
/*!
* VAD后端点消息,在检测到VAD后端点时抛出,暂时只有身份验证的声纹业务用到
*/
IFlySpeechEventTypeVadEOS = 10013,
/*!
* 服务端会话id<br>
* 在消息到达时,可通过onEvent的第4个参数data(字典类型),指定key KCIFlySpeechEventKeySessionID,获取服务端会话id.
*/
IFlySpeechEventTypeSessionID = 20001,
/*!
* TTS合成数据消息<br>
* -(void)onEvent:(int)eventType arg0:(int)arg0 arg1:(int)arg1 data:(NSData *)eventData<br>
* 其中eventData中包含数据
*
*/
IFlySpeechEventTypeTTSBuffer = 21001,
/*!
* 通知cancel方法被调用的回调
*
*/
IFlySpeechEventTypeTTSCancel = 21002,
/*!
* IVW onshot 听写 or 识别结果<br>
* 在消息到达时,第2个参数arg1包含是否为最后一个结果:1为是,0为否;<br>
* 第4个参数data中包含数据,通过指定KEY为KCIFlySpeechEventKeyIVWResult获取.
*/
IFlySpeechEventTypeIVWResult = 22001,
/*!
* 开始处理录音数据
*
*/
IFlySpeechEventTypeSpeechStart= 22002,
/*!
* 录音停止
*
*/
IFlySpeechEventTypeRecordStop= 22003,
/*!
* 服务端音频url<br>
* 在消息到达时,第4个参数data,包含数据,通过指定KEY为KCIFlySpeechEventKeyAudioUrl获取.
*/
IFlySpeechEventTypeAudioUrl = 23001,
/*!
* 变声数据结果返回<br>
* 设置voice_change参数获取结果.
*/
IFlySpeechEventTypeVoiceChangeResult = 24001
};
#pragma mark - keys for event data
/**
* 转写是否已上传完标记key
*/
extern NSString* const KCIFlySpeechEventKeyISTUploadComplete;
/**
* 服务端会话key
*/
extern NSString* const KCIFlySpeechEventKeySessionID;
/**
* TTS取音频数据key
*/
extern NSString* const KCIFlySpeechEventKeyTTSBuffer;
/**
* IVW oneshot 听写 or 识别结果 key
*/
extern NSString* const KCIFlySpeechEventKeyIVWResult;
/**
* 服务端音频url key
*/
extern NSString* const KCIFlySpeechEventKeyAudioUrl;
@@ -0,0 +1,175 @@
//
// IFlySpeechRecognizer.h
// MSC
//
// Created by iflytek on 13-3-19.
// Copyright (c) 2013年 iflytek. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "IFlySpeechRecognizerDelegate.h"
#define IFLY_AUDIO_SOURCE_MIC @"1"
#define IFLY_AUDIO_SOURCE_STREAM @"-1"
/*!
* 语音识别类<br>
* 此类现在设计为单例,你在使用中只需要创建此对象,不能调用release/dealloc函数去释放此对象。所有关于语音识别的操作都在此类中。
*/
@interface IFlySpeechRecognizer : NSObject<IFlySpeechRecognizerDelegate>
/*!
* 设置委托对象
*/
@property(nonatomic,assign) id<IFlySpeechRecognizerDelegate> delegate ;
/*!
* 返回识别对象的单例
*
* @return 识别对象的单例
*/
+ (instancetype) sharedInstance;
/*!
* 销毁识别对象。
*
* @return 成功返回YES,失败返回NO
*/
- (BOOL) destroy;
/*
* | ------------- |-----------------------------------------------------------
* | 参数 | 描述
* | ------------- |-----------------------------------------------------------
* | domain |应用的领域: 取值为:iat、search、video、poi、music、asr
* | | iat:普通文本听写;
* | | search:热词搜索;
* | | video:视频音乐搜索;
* | | asr:关键词识别;
* | ------------- |-----------------------------------------------------------
* | vad_bos |前端点检测: 静音超时时间,即用户多长时间不说话则当做超时处理; 单位:ms;
* | | engine指定iat识别默认值为5000
* | | 其他情况默认值为 4000,范围 0-10000。
* | ------------- |-----------------------------------------------------------
* | vad_eos |后断点检测: 后端点静音检测时间,即用户停止说话多长时间内即认为不再输入,
* | | 自动停止录音;单位:ms;
* | | sms 识别默认值为 1800;
* | | 其他默认值为 700,范围 0-10000。
* | ------------- |-----------------------------------------------------------
* | sample_rate |采样率:目前支持的采样率设置有 16000 和 8000。
* | ------------- |-----------------------------------------------------------
* | asr_ptt |标点符号设置: 默认为 1,当设置为 0 时,将返回无标点符号文本。
* | ------------- |-----------------------------------------------------------
* | result_type |返回结果的数据格式: 可设置为json,xmlplain,默认为json。
* | ------------- |-----------------------------------------------------------
* | grammarID |识别的语法id: 只针对 domain 设置为”asr”的应用。
* | ------------- |-----------------------------------------------------------
* | asr_audio_path|音频文件名: 设置此参数后,将会自动保存识别的录音文件。
* | | 路径为Documents/(指定值)。
* | | 不设置或者设置为nil,则不保存音频。
* | ------------- |-----------------------------------------------------------
* | params |扩展参数: 对于一些特殊的参数可在此设置,一般用于设置语义。
* | ------------- |-----------------------------------------------------------
*
*/
/*!
* 设置识别引擎的参数
*
* 识别的引擎参数(key)取值如下:
*
* | 参数 | 描述 |
* |-----------------|-------------------------------------------------------|
* | domain | 应用的领域: 取值为:iat、search、video、poi、music、asr<br>iat:普通文本听写;<br>search:热词搜索;<br>video:视频音乐搜索;<br>asr:关键词识别;|
* | vad_bos | 前端点检测: 静音超时时间,即用户多长时间不说话则当做超时处理; 单位:ms;<br>engine指定iat识别默认值为5000<br>其他情况默认值为 4000,范围 0-10000。|
* | vad_eos | 后断点检测: 后端点静音检测时间,即用户停止说话多长时间内即认为不再输入,自动停止录音;单位:ms;<br>sms 识别默认值为 1800;<br>其他默认值为 700,范围 0-10000。|
* | sample_rate | 采样率:目前支持的采样率设置有 16000 和 8000。|
* | asr_ptt | 标点符号设置: 默认为 1,当设置为 0 时,将返回无标点符号文本。|
* | result_type | 返回结果的数据格式: 可设置为json,xmlplain,默认为json。|
* | grammarID | 识别的语法id: 只针对 domain 设置为”asr”的应用。|
* | asr_audio_path | 音频文件名: 设置此参数后,将会自动保存识别的录音文件。<br>路径为Documents/(指定值)。<br>不设置或者设置为nil,则不保存音频。|
* | params | 扩展参数: 对于一些特殊的参数可在此设置,一般用于设置语义。|
*
* @param value 参数对应的取值
* @param key 识别引擎参数
*
* @return 成功返回YES;失败返回NO
*/
-(BOOL) setParameter:(NSString *) value forKey:(NSString*)key;
/*!
* 获取识别引擎参数
*
* @param key 参数key
*
* @return 参数值
*/
-(NSString*) parameterForKey:(NSString *)key;
/*!
* 开始识别
*
* 同时只能进行一路会话,这次会话没有结束不能进行下一路会话,否则会报错。若有需要多次回话,请在onCompleted回调返回后请求下一路回话。
*
* @return 成功返回YES;失败返回NO
*/
- (BOOL) startListening;
/*!
* 停止录音<br>
* 调用此函数会停止录音,并开始进行语音识别
*/
- (void) stopListening;
/*!
* 取消本次会话
*/
- (void) cancel;
/*!
* 上传语法
*
* @param completionHandler 上传语法完成回调
* @param grammarType 语法类型
* @param grammarContent 语法内容
*
* @return 错误码
*/
- (int) buildGrammarCompletionHandler:(IFlyOnBuildFinishCompletionHandler)completionHandler
grammarType:(NSString *)grammarType
grammarContent:(NSString *)grammarContent;
/*!
* 是否正在识别
*/
@property (nonatomic, readonly) BOOL isListening;
@end
/*!
* 音频流识别<br>
* 音频流识别可以将文件分段写入
*/
@interface IFlySpeechRecognizer(IFlyStreamRecognizer)
/*!
* 写入音频流
*
* 此方法的使用示例如下:
* <pre><code>[_iFlySpeechRecognizer setParameter:@"-1" value:@"audio_source"];
* [_iFlySpeechRecognizer startListening];
* [_iFlySpeechRecognizer writeAudio:audioData1];
* [_iFlySpeechRecognizer writeAudio:audioData2];
* ...
* [_iFlySpeechRecognizer stopListening];
* </code></pre>
*
* @param audioData 音频数据
*
* @return 写入成功返回YES,写入失败返回NO
*/
- (BOOL) writeAudio:(NSData *) audioData;
@end
@@ -0,0 +1,111 @@
//
// IFlySpeechRecognizerDelegate.h
// MSC
//
// Created by ypzhao on 13-3-27.
// Copyright (c) 2013年 iflytek. All rights reserved.
//
#import <Foundation/Foundation.h>
@class IFlySpeechError;
/*!
* 构建语法结束回调
*
* @param grammarId 语法id
* @param error 错误描述
*/
typedef void(^IFlyOnBuildFinishCompletionHandler)(NSString* grammarId,IFlySpeechError * error);
/*!
* 语音识别协议<br>
* 在使用语音识别时,需要实现这个协议中的方法.
*/
@protocol IFlySpeechRecognizerDelegate <NSObject>
@required
/*!
* 识别结果回调
*
* 在进行语音识别过程中的任何时刻都有可能回调此函数,你可以根据errorCode进行相应的处理,当errorCode没有错误时,表示此次会话正常结束;否则,表示此次会话有错误发生。特别的当调用`cancel`函数时,引擎不会自动结束,需要等到回调此函数,才表示此次会话结束。在没有回调此函数之前如果重新调用了`startListenging`函数则会报错误。
*
* @param errorCode 错误描述
*/
- (void) onCompleted:(IFlySpeechError *) errorCode;
/*!
* 识别结果回调
*
* 在识别过程中可能会多次回调此函数,你最好不要在此回调函数中进行界面的更改等操作,只需要将回调的结果保存起来。<br>
* 使用results的示例如下:
* <pre><code>
* - (void) onResults:(NSArray *) results{
* NSMutableString *result = [[NSMutableString alloc] init];
* NSDictionary *dic = [results objectAtIndex:0];
* for (NSString *key in dic){
* [result appendFormat:@"%@",key];//合并结果
* }
* }
* </code></pre>
*
* @param results -[out] 识别结果,NSArray的第一个元素为NSDictionaryNSDictionary的key为识别结果,sc为识别结果的置信度。
* @param isLast -[out] 是否最后一个结果
*/
- (void) onResults:(NSArray *) results isLast:(BOOL)isLast;
@optional
/*!
* 音量变化回调<br>
* 在录音过程中,回调音频的音量。
*
* @param volume -[out] 音量,范围从0-30
*/
- (void) onVolumeChanged: (int)volume;
/*!
* 开始录音回调<br>
* 当调用了`startListening`函数之后,如果没有发生错误则会回调此函数。<br>
* 如果发生错误则回调onCompleted:函数
*/
- (void) onBeginOfSpeech;
/*!
* 停止录音回调<br>
* 当调用了`stopListening`函数或者引擎内部自动检测到断点,如果没有发生错误则回调此函数。<br>
* 如果发生错误则回调onCompleted:函数
*/
- (void) onEndOfSpeech;
/*!
* 取消识别回调<br>
* 当调用了`cancel`函数之后,会回调此函数,在调用了cancel函数和回调onCompleted之前会有一个<br>
* 短暂时间,您可以在此函数中实现对这段时间的界面显示。
*/
- (void) onCancel;
#ifdef _EDUCATION_
/*!
* 返回音频Key
*
* @param key 音频Key
*/
- (void) getAudioKey:(NSString *)key;
#endif
/*!
* 扩展事件回调<br>
* 根据事件类型返回额外的数据
*
* @param eventType 事件类型,具体参见IFlySpeechEventType的IFlySpeechEventTypeVoiceChangeResult枚举。
* @param arg0 arg0
* @param arg1 arg1
* @param eventData 事件数据
*/
- (void) onEvent:(int)eventType arg0:(int)arg0 arg1:(int)arg1 data:(NSData *)eventData;
@end
@@ -0,0 +1,123 @@
//
// IFlySpeechSynthesizer.h
// MSC
//
// Created by 侯效林 on 16-4-22.
// Copyright (c) 2016年 iflytek. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "IFlySpeechSynthesizerDelegate.h"
/*!
* 语音合成
*/
@interface IFlySpeechSynthesizer : NSObject
/*!
* 设置识别的委托对象
*/
@property(nonatomic,assign) id<IFlySpeechSynthesizerDelegate> delegate;
/*!
* 返回合成对象的单例
*
* @return 合成对象
*/
+ (instancetype) sharedInstance;
/*!
* 销毁合成对象。
*
* @return 成功返回YES,失败返回NO.
*/
+ (BOOL) destroy;
/*
* | ------------- |-----------------------------------------------------------
* | 参数 | 描述
* | ------------- |-----------------------------------------------------------
* | speed |合成语速,取值范围 0~100
* | ------------- |-----------------------------------------------------------
* | volume |合成的音量,取值范围 0~100
* | ------------- |-----------------------------------------------------------
* | voice_name |默认为”xiaoyan”;可以设置的参数列表可参考个性化发音人列表
* | ------------- |-----------------------------------------------------------
* | sample_rate |采样率:目前支持的采样率设置有 16000 和 8000。
* | ------------- |-----------------------------------------------------------
* | tts_audio_path|音频文件名 设置此参数后,将会自动保存合成的音频文件。
* | |路径为Documents/(指定值)。不设置或者设置为nil,则不保存音频。
* | ------------- |-----------------------------------------------------------
* | params |扩展参数: 对于一些特殊的参数可在此设置。
* | ------------- |-----------------------------------------------------------
*
*/
/*!
* 设置合成参数
*
* | 参数 | 描述 |
* |-----------------|----------------------------------------------------|
* | speed | 合成语速,取值范围 0~100 |
* | volume | 合成的音量,取值范围 0~100 |
* | voice_name | 默认为”xiaoyan”;可以设置的参数列表可参考个性化发音人列表 |
* | sample_rate | 采样率:目前支持的采样率设置有 16000 和 8000。 |
* | tts_audio_path | 音频文件名 设置此参数后,将会自动保存合成的音频文件。<br>路径为Documents/(指定值)。不设置或者设置为nil,则不保存音频。|
* | params | 扩展参数: 对于一些特殊的参数可在此设置。 |
*
* @param value 参数取值
* @param key 合成参数
*
* @return 设置成功返回YES,失败返回NO
*/
-(BOOL) setParameter:(NSString *) value forKey:(NSString*)key;
/*!
* 获取合成参数
*
* @param key 参数key
*
* @return 参数值
*/
-(NSString*) parameterForKey:(NSString *)key;
/*!
* 开始合成(播放)<br>
* 调用此函数进行合成,如果发生错误会回调错误`onCompleted`
*
* @param text 合成的文本,最大的字节数为1k
*/
- (void) startSpeaking:(NSString *)text;
/*!
* 开始合成(不播放)<br>
* 调用此函数进行合成,如果发生错误会回调错误`onCompleted`
*
* @param text 合成的文本,最大的字节数为1k
* @param uri 合成后,保存再本地的音频路径
*/
-(void)synthesize:(NSString *)text toUri:(NSString*)uri;
/*!
* 暂停播放<br>
* 暂停播放之后,合成不会暂停,仍会继续,如果发生错误则会回调错误`onCompleted`
*/
- (void) pauseSpeaking;
/*!
* 恢复播放
*/
- (void) resumeSpeaking;
/*!
* 停止播放并停止合成
*/
- (void) stopSpeaking;
/*!
* 是否正在播放
*/
@property (nonatomic, readonly) BOOL isSpeaking;
@end
@@ -0,0 +1,81 @@
//
// IFlySpeechSynthesizerDelegate.h
// MSC
//
// Created by ypzhao on 13-3-20.
// Copyright (c) 2013年 iflytek. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "IFlySpeechEvent.h"
@class IFlySpeechError;
/*!
* 语音合成回调
*/
@protocol IFlySpeechSynthesizerDelegate <NSObject>
@required
/*!
* 结束回调<br>
* 当整个合成结束之后会回调此函数
*
* @param error 错误码
*/
- (void) onCompleted:(IFlySpeechError*) error;
@optional
/*!
* 开始合成回调
*/
- (void) onSpeakBegin;
/*!
* 缓冲进度回调
*
* @param progress 缓冲进度,0-100
* @param msg 附件信息,此版本为nil
*/
- (void) onBufferProgress:(int) progress message:(NSString *)msg;
/*!
* 播放进度回调
*
* @param progress 当前播放进度,0-100
* @param beginPos 当前播放文本的起始位置(按照字节计算),对于汉字(2字节)需/2处理
* @param endPos 当前播放文本的结束位置(按照字节计算),对于汉字(2字节)需/2处理
*/
- (void) onSpeakProgress:(int) progress beginPos:(int)beginPos endPos:(int)endPos;
/*!
* 暂停播放回调
*/
- (void) onSpeakPaused;
/*!
* 恢复播放回调<br>
* 注意:此回调方法SDK内部不执行,播放恢复全部在onSpeakBegin中执行
*/
- (void) onSpeakResumed;
/*!
* 正在取消回调<br>
* 注意:此回调方法SDK内部不执行
*/
- (void) onSpeakCancel;
/*!
* 扩展事件回调<br>
* 根据事件类型返回额外的数据
*
* @param eventType 事件类型,具体参见IFlySpeechEventType枚举。目前只支持EVENT_TTS_BUFFER也就是实时返回合成音频。
* @param arg0 arg0
* @param arg1 arg1
* @param eventData 事件数据
*/
- (void) onEvent:(int)eventType arg0:(int)arg0 arg1:(int)arg1 data:(NSData *)eventData;
@end
@@ -0,0 +1,132 @@
//
// IFlySpeechUnderstander.h
// MSC
//
// Created by iflytek on 2014-03-12.
// Copyright (c) 2014年 iflytek. All rights reserved.
//
#import <Foundation/Foundation.h>
@class IFlySpeechError;
@protocol IFlySpeechRecognizerDelegate;
/*!
* 语义理解接口
*/
@interface IFlySpeechUnderstander : NSObject
/*!
* 是否正在语义理解
*/
@property (readonly) BOOL isUnderstanding;
/*!
* 设置委托对象
*/
@property(nonatomic,retain) id<IFlySpeechRecognizerDelegate> delegate ;
/*!
* 创建语义理解对象的单例
*
* @return 语义理解对象
*/
+(instancetype) sharedInstance;
/*!
* 开始义理解
*
* 同时只能进行一路会话,这次会话没有结束不能进行下一路会话,否则会报错。若有需要多次回话,请在onCompleted回调返回后请求下一路回话。
*
* @return 成功返回YES,失败返回NO
*/
- (BOOL) startListening;
/*!
* 停止录音<br>
* 调用此函数会停止录音,并开始进行语义理解
*/
- (void) stopListening;
/*!
* 取消本次会话
*/
- (void) cancel;
/*
* | ------------- |-----------------------------------------------------------
* | 参数 | 描述
* | ------------- |-----------------------------------------------------------
* | domain |应用的领域: 取值为:iat、search、video、poi、music、asr
* | | iat:普通文本听写;
* | | search:热词搜索;
* | | video:视频音乐搜索;
* | | asr:关键词识别;
* | ------------- |-----------------------------------------------------------
* | vad_bos |前端点检测: 静音超时时间,即用户多长时间不说话则当做超时处理; 单位:ms;
* | | engine指定iat识别默认值为5000
* | | 其他情况默认值为 4000,范围 0-10000。
* | ------------- |-----------------------------------------------------------
* | vad_eos |后断点检测: 后端点静音检测时间,即用户停止说话多长时间内即认为不再输入,
* | | 自动停止录音;单位:ms;
* | | sms 识别默认值为 1800;
* | | 其他默认值为 700,范围 0-10000。
* | ------------- |-----------------------------------------------------------
* | sample_rate |采样率:目前支持的采样率设置有 16000 和 8000。
* | ------------- |-----------------------------------------------------------
* | asr_ptt |标点符号设置: 默认为 1,当设置为 0 时,将返回无标点符号文本。
* | ------------- |-----------------------------------------------------------
* | result_type |返回结果的数据格式: 可设置为json,xmlplain,默认为json。
* | ------------- |-----------------------------------------------------------
* | grammarID |识别的语法id: 只针对 domain 设置为”asr”的应用。
* | ------------- |-----------------------------------------------------------
* | asr_audio_path|音频文件名: 设置此参数后,将会自动保存识别的录音文件。
* | | 路径为Documents/(指定值)。
* | | 不设置或者设置为nil,则不保存音频。
* | ------------- |-----------------------------------------------------------
* | params |扩展参数: 对于一些特殊的参数可在此设置,一般用于设置语义。
* | ------------- |-----------------------------------------------------------
*
*/
/*!
* 设置语义理解引擎的参数
*
* 语义理解的引擎参数(key)取值如下:
*
* | 参数 | 描述 |
* |-----------------|-------------------------------------------------------|
* | domain | 应用的领域: 取值为:iat、search、video、poi、music、asr<br>iat:普通文本听写;<br>search:热词搜索;<br>video:视频音乐搜索;<br>asr:关键词识别;|
* | vad_bos | 前端点检测: 静音超时时间,即用户多长时间不说话则当做超时处理; 单位:ms;<br>engine指定iat识别默认值为5000<br>其他情况默认值为 4000,范围 0-10000。|
* | vad_eos | 后断点检测: 后端点静音检测时间,即用户停止说话多长时间内即认为不再输入,自动停止录音;单位:ms;<br>sms 识别默认值为 1800;<br>其他默认值为 700,范围 0-10000。|
* | sample_rate | 采样率:目前支持的采样率设置有 16000 和 8000。|
* | asr_ptt | 标点符号设置: 默认为 1,当设置为 0 时,将返回无标点符号文本。|
* | result_type | 返回结果的数据格式: 可设置为json,xmlplain,默认为json。|
* | grammarID | 识别的语法id: 只针对 domain 设置为”asr”的应用。|
* | asr_audio_path | 音频文件名: 设置此参数后,将会自动保存识别的录音文件。<br>路径为Documents/(指定值)。<br>不设置或者设置为nil,则不保存音频。|
* | params | 扩展参数: 对于一些特殊的参数可在此设置,一般用于设置语义。|
*
* @param value 参数对应的取值
* @param key 语义理解引擎参数
*
* @return 成功返回YES;失败返回NO
*/
-(BOOL) setParameter:(NSString *) value forKey:(NSString*)key;
/*!
* 写入音频流
*
* @param audioData 音频数据
*
* @return 写入成功返回YES,写入失败返回NO
*/
- (BOOL) writeAudio:(NSData *) audioData;
/*!
* 销毁语义理解对象。
*
* @return 成功返回YES;失败返回NO
*/
- (BOOL) destroy;
@end
@@ -0,0 +1,184 @@
//
// IFlySpeechUtility.h
// MSCDemo
//
// Created by admin on 14-5-7.
// Copyright (c) 2014年 iflytek. All rights reserved.
//
#import <Foundation/Foundation.h>
#define iOS_EXCLUSIVE //iOS平台独占API
@class IFlySpeechError;
/*!
* 引擎模式
*/
typedef NS_ENUM(NSUInteger,IFlyEngineMode){
/*!
* 云端使用MSC,本地优先使用语记
*/
IFlyEngineModeAuto = 0,
/*!
* 只使用MSC
*/
IFlyEngineModeMsc,
/*!
* 本地只使用语记(受平台限制,云端无法使用语记)
*/
IFlyEngineModePlus,
};
/*!
* 服务类型
*/
typedef NS_ENUM(NSUInteger,IFlySpeechPlusServiceType){
/*!
* 打开语记主界面
*/
IFlySpeechPlusServiceTypeNone=0,
/*!
* 获取合成资源
*/
IFlySpeechPlusServiceTypeTTS,
/*!
* 获取识别资源(未开放)
*/
IFlySpeechPlusServiceTypeISR,
/*!
* 获取唤醒资源(未开放)
*/
IFlySpeechPlusServiceTypeIVW,
} ;
/*! 语记返回回调
*/
@protocol IFlySpeechplusDelegate <NSObject>
/*!
* 发生错误
*
* @param errorCode 错误码
*/
- (void)onCompleted:(int)errorCode;
/*!
* 服务正常结束
*/
- (void)onCompleted;
@end
/*!
* 用户配置
*/
@interface IFlySpeechUtility : NSObject
/*!
* 创建用户语音配置<br>
* 注册应用请前往语音云开发者网站。<br>
* 网站:http://www.xfyun.cn
*
* @param params 启动参数,必须保证appid参数传入,示例:appid=123456
*
* @return 语音配置对象
*/
+ (IFlySpeechUtility*) createUtility:(NSString *) params;
/*!
* 销毁用户配置对象
*
* @return 成功返回YES,失败返回NO
*/
+(BOOL) destroy;
/*!
* 获取用户配置对象
*
* @return 用户配置对象
*/
+(IFlySpeechUtility *) getUtility;
/*!
* 设置MSC引擎的状态参数
*
* @param value 参数值
* @param key 参数名称
*
* @return 成功返回YES,失败返回NO
*/
-(BOOL) setParameter:(NSString *) value forKey:(NSString*)key;
/*!
* 获取MSC引擎状态参数
*
* @param key 参数名
*
* @return 参数值
*/
- (NSString *)parameterForKey:(NSString *)key;
/*!
* 引擎类型
*/
@property (nonatomic, readonly) IFlyEngineMode engineMode;
/*!
* 语记协议委托
*/
@property (nonatomic, assign) id<IFlySpeechplusDelegate> delegate;
@end
/*!
* 讯飞语记类别
*/
@interface IFlySpeechUtility (SpeechPlus)
/*!
* 检查讯飞语记是否安装
*
* @return 已安装返回YES,否则返回NO
*/
+ (BOOL)checkServiceInstalled;
/*!
* 获取讯飞语记下载地址进行下载,安装完成后即可使用服务。<br>
* 下载地址需要通过[[UIApplication sharedApplication] openUrl:]打开
*
* @return 讯飞语记在App Store下载地址
*/
+ (NSString *)componentUrl;
/*!
* 注意:此接口废弃,不再需要使用<br>
* 处理语记使用URL启动第三方应用程序时传递的数据<br>
* 需要在 application:openURL:sourceApplication:annotation:或者application:handleOpenURL中调用。
*
* @param url 语记启动第三方应用程序时传递过来的URL
*
* @return 成功返回YES,失败返回NO。
*/
- (BOOL)handleOpenURL:(NSURL *)url iOS_EXCLUSIVE;
/*!
* 打开讯飞语记获取相应类型服务,0表示打开主界面
*
* @param serviceType 服务类型
*
* @return 成功打开返回YES,否则返回NO
*/
- (BOOL)openSpeechPlus:(IFlySpeechPlusServiceType)serviceType iOS_EXCLUSIVE;
@end
@@ -0,0 +1,57 @@
//
// TextUnderstand.h
// MSCDemo
//
// Created by iflytek on 4/24/14.
// Copyright (c) 2014 iflytek. All rights reserved.
//
#import <Foundation/Foundation.h>
@class IFlySpeechError;
/*!
* 文本转语义完成回调函数
*
* @param result 成功,返回文本语义理解结果
* @param error 错误描述
*/
typedef void(^IFlyUnderstandTextCompletionHandler)(NSString* result, IFlySpeechError * error);
/*!
* 文本转语义类
*/
@interface IFlyTextUnderstander : NSObject
/*!
* 是否正在文本转语义
*/
@property (readonly, atomic) __block BOOL isUnderstanding;
/*!
* 文本转语义接口<br>
* 输入文本内容,获取语义理解结果
*
* @param text 输入的文本内容
* @param completionHandler 文本转语义完成回调函数
*
* @return 错误码
*/
-(int) understandText:(NSString*)text withCompletionHandler:(IFlyUnderstandTextCompletionHandler) completionHandler;
/*!
* 设置文本转语义参数
*
* @param value 参数对应的取值
* @param key 文本转语义参数参数
*
* @return 成功返回YES,失败返回NO
*/
-(BOOL) setParameter:(NSString *) value forKey:(NSString*)key;
/*!
* 取消本次会话
*/
-(void)cancel;
@end
@@ -0,0 +1,74 @@
//
// IFlyUserWords.h
// MSC
//
// Created by ypzhao on 13-2-26.
// Copyright (c) 2013年 iflytek. All rights reserved.
//
#import <Foundation/Foundation.h>
/*!
* 用户词表类
* 获取用户词表是为了更好的语音识别(iat),用户词表也属于个性化的一部分.
*/
@interface IFlyUserWords : NSObject
/*!
* 初始化对象
*
* 在进行初始化时,需要传入的格式如下:
* <pre><code>{\"userword\":[{\"name\":\"iflytek\",\"words\":[\"科大讯飞\",
* \"云平台\",\"用户词条\",\"开始上传词条\"]}]}</code></pre>
*
* @param json 初始化时传入的数据
*
* @return IFlyUserWords对象
*/
- (id) initWithJson:(NSString *)json;
/*!
* 将数据转化为上传的数据格式
*
* @return 没有数据或者格式不对时返回nil
*/
- (NSString *) toString;
/*!
* 返回key对应的数据
*
* @param key 在putword:value中设置的key
*
* @return key对应的数组
*/
- (NSArray *) getWords: (NSString *) key;
/*!
* 添加一条用户词数据
*
* @param key 用户词对应的key
* @param value 上传的用户词数据
*
* @return 成功返回YES,失败返回NO
*/
- (BOOL) putWord: (NSString *) key value:(NSString *)value;
/*!
* 添加一组数据
*
* @param key 用户词对应的key
* @param words 上传的用户词数据
*
* @return 成功返回YES,失败返回NO
*/
- (BOOL) putwords: (NSString *) key words:(NSArray *)words;
/*!
* 是否包含key对应的用户词数据
*
* @param key 用户词对应的key
*
* @return 成功返回YES,失败返回NO
*/
- (BOOL) containsKey: (NSString *) key;
@end
@@ -0,0 +1,39 @@
//
// IFlyVerifierUtil.h
// IFlyMSC
//
// Created by 张剑 on 15/4/28.
// Copyright (c) 2015年 iflytek. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
/**
* 身份验证工具类
*/
@interface IFlyVerifierUtil : NSObject
#pragma mark - ISV
/**
* 返回定长的随机数字字符串(不包含数字1,而且2和5不邻接)
*
* @param length 随机字符串长度
*
* @return 随机字符串
*/
+(NSString*)generateNumberPassword:(int)length;
#pragma mark - Face
/**
* ARGB彩图转灰度图,Detector和Alignment需要灰度图的输入
*
* @param sourceImage ARGB彩图
*
* @return 灰度图
*/
+ (UIImage*)ARGBToGray:(UIImage*)sourceImage;
@end
@@ -0,0 +1,83 @@
//
// IFlyVoiceWakeuper.h
// wakeup
//
// Created by admin on 14-3-18.
// Copyright (c) 2014年 iflytek. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "IFlyVoiceWakeuperDelegate.h"
#define IFLY_AUDIO_SOURCE_MIC @"1"
#define IFLY_AUDIO_SOURCE_STREAM @"-1"
/*!
* 语音唤醒
*/
@interface IFlyVoiceWakeuper : NSObject
/*!
* 代理
*/
@property (nonatomic, assign) id<IFlyVoiceWakeuperDelegate> delegate;
/*!
* 是否正在唤醒
*/
@property (nonatomic, readonly) BOOL isListening;
/*!
* 创建唤醒实例,采用单例模式
*/
+ (instancetype) sharedInstance;
/*!
* 启动唤醒
* 返回值:YES 成功,NO:失败
*/
-(BOOL) startListening;
/*!
* 停止录音
*/
-(BOOL) stopListening;
/*!
* 取消唤醒会话
*/
-(BOOL) cancel;
/*!
* 获取工作参数
*/
-(NSString*) getParameter:(NSString *)key;
/*!
* 设置工作参数<br>
* 注意服务正在运行中,不能设置参数
*/
-(BOOL) setParameter:(NSString *) value forKey:(NSString*)key;
@end
/*!
* 音频流唤醒<br>
* 音频流唤醒可以将文件分段写入
*/
@interface IFlyVoiceWakeuper(IFlyStreamVoiceWakeuper)
/*!
* 写入音频流
*
* @param audioData 音频数据
*
* @return 写入成功返回YES,写入失败返回NO
*/
- (BOOL) writeAudio:(NSData *) audioData;
@end
@@ -0,0 +1,60 @@
//
// IFlyVoiceWakeuperDel.h
// wakeup
//
// Created by admin on 14-3-18.
// Copyright (c) 2014年 iflytek. All rights reserved.
//
#import <Foundation/Foundation.h>
@class IFlySpeechError;
@protocol IFlyVoiceWakeuperDelegate <NSObject>
@optional
/*!
* 录音开始
*/
-(void) onBeginOfSpeech;
/*!
* 录音结束
*/
-(void) onEndOfSpeech;
/*!
* 会话错误
*
* @param errorCode 错误描述类,
*/
- (void) onCompleted:(IFlySpeechError *) error;
/*!
* 唤醒结果
*
* @param resultDic 唤醒结果字典
*/
-(void) onResult:(NSMutableDictionary *)resultDic;
/*!
* 音量反馈,返回频率与录音数据返回回调频率一致
*
* @param volume 音量值
*/
- (void) onVolumeChanged: (int)volume;
/*!
* 扩展事件回调<br>
* 根据事件类型返回额外的数据
*
@param eventType 事件类型,具体参见IFlySpeechEvent枚举。
*/
- (void) onEvent:(int)eventType isLast:(BOOL)isLast arg1:(int)arg1 data:(NSMutableDictionary *)eventData;
@end
@@ -0,0 +1,16 @@
//
// CALayer+OCBarrage.h
// OCBarrage
//
// Created by QMTV on 2017/8/29.
// Copyright © 2017年 LFC. All rights reserved.
//
#import <QuartzCore/QuartzCore.h>
#import <UIKit/UIKit.h>
@interface CALayer (OCBarrage)
- (UIImage *)convertContentToImageWithSize:(CGSize)contentSize;
@end
@@ -0,0 +1,24 @@
//
// CALayer+OCBarrage.m
// OCBarrage
//
// Created by QMTV on 2017/8/29.
// Copyright © 2017年 LFC. All rights reserved.
//
#import "CALayer+OCBarrage.h"
@implementation CALayer (OCBarrage)
- (UIImage *)convertContentToImageWithSize:(CGSize)contentSize {
UIGraphicsBeginImageContextWithOptions(contentSize, 0.0, [UIScreen mainScreen].scale);
//self为需要截屏的UI控件 即通过改变此参数可以截取特定的UI控件
[self renderInContext:UIGraphicsGetCurrentContext()];
UIImage *image= UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
@end
@@ -0,0 +1,20 @@
//
// OCBarrage.h
// OCBarrage
//
// Created by QMTV on 2017/8/25.
// Copyright © 2017年 LFC. All rights reserved.
//
#ifndef OCBarrage_h
#define OCBarrage_h
#import "OCBarrageHeader.h"
#import "OCBarrageManager.h"
#import "OCBarrageRenderView.h"
#import "OCBarrageDescriptor.h"
#import "OCBarrageCell.h"
#import "OCBarrageTextDescriptor.h"
#import "OCBarrageTextCell.h"
#endif /* OCBarrage_h */
@@ -0,0 +1,43 @@
//
// OCBarrageCell.h
// TestApp
//
// Created by QMTV on 2017/8/21.
// Copyright © 2017年 LFC. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "CALayer+OCBarrage.h"
#import "OCBarrageDescriptor.h"
NS_ASSUME_NONNULL_BEGIN
@protocol OCBarrageCellDelegate;
@interface OCBarrageCell : UIView
@property (nonatomic, assign, getter=isIdle) BOOL idle;//是否是空闲状态
@property (nonatomic, assign) NSTimeInterval idleTime;//开始闲置的时间, 闲置超过5秒的, 自动回收内存
@property (nonatomic, strong, nullable) OCBarrageDescriptor *barrageDescriptor;
@property (nonatomic, strong, readonly, nullable) CAAnimation *barrageAnimation;
@property (nonatomic, assign) int trackIndex;
- (void)addBarrageAnimationWithDelegate:(id<CAAnimationDelegate>)animationDelegate;
- (void)prepareForReuse;
- (void)clearContents;
- (void)updateSubviewsData;
- (void)layoutContentSubviews;
- (void)convertContentToImage;
- (void)sizeToFit;//设置好数据之后调用一下自动计算bounds
- (void)removeSubViewsAndSublayers;//默认删除所有的subview和sublayer; 如果需要选择性的删除可以重写这个方法.
- (void)addBorderAttributes;
@end
@protocol OCBarrageCellDelegate <NSObject, CAAnimationDelegate>
@optional
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,113 @@
//
// OCBarrageCell.m
// TestApp
//
// Created by QMTV on 2017/8/21.
// Copyright © 2017年 LFC. All rights reserved.
//
#import "OCBarrageCell.h"
@implementation OCBarrageCell
- (instancetype)init {
self = [super init];
if (self) {
_trackIndex = -1;
}
return self;
}
- (void)prepareForReuse {
[self.layer removeAnimationForKey:kBarrageAnimation];
_barrageDescriptor = nil;
if (!_idle) {
_idle = YES;
}
_trackIndex = -1;
}
- (void)setBarrageDescriptor:(OCBarrageDescriptor *)barrageDescriptor {
_barrageDescriptor = barrageDescriptor;
}
- (void)clearContents {
self.layer.contents = nil;
}
- (void)convertContentToImage {
}
- (void)sizeToFit {
CGFloat height = 0.0;
CGFloat width = 0.0;
for (CALayer *sublayer in self.layer.sublayers) {
CGFloat maxY = CGRectGetMaxY(sublayer.frame);
if (maxY > height) {
height = maxY;
}
CGFloat maxX = CGRectGetMaxX(sublayer.frame);
if (maxX > width) {
width = maxX;
}
}
if (width == 0 || height == 0) {
CGImageRef content = (__bridge CGImageRef)self.layer.contents;
if (content) {
UIImage *image = [UIImage imageWithCGImage:content];
width = image.size.width/[UIScreen mainScreen].scale;
height = image.size.height/[UIScreen mainScreen].scale;
}
}
self.bounds = CGRectMake(0.0, 0.0, width, height);
}
- (void)removeSubViewsAndSublayers {
NSEnumerator *viewEnumerator = [self.subviews reverseObjectEnumerator];
UIView *subView = nil;
while (subView = [viewEnumerator nextObject]){
[subView removeFromSuperview];
}
NSEnumerator *layerEnumerator = [self.layer.sublayers reverseObjectEnumerator];
CALayer *sublayer = nil;
while (sublayer = [layerEnumerator nextObject]){
[sublayer removeFromSuperlayer];
}
}
- (void)addBorderAttributes {
if (self.barrageDescriptor.borderColor) {
self.layer.borderColor = self.barrageDescriptor.borderColor.CGColor;
}
if (self.barrageDescriptor.borderWidth > 0) {
self.layer.borderWidth = self.barrageDescriptor.borderWidth;
}
if (self.barrageDescriptor.cornerRadius > 0) {
self.layer.cornerRadius = self.barrageDescriptor.cornerRadius;
}
}
- (void)addBarrageAnimationWithDelegate:(id<CAAnimationDelegate>)animationDelegate {
}
- (void)updateSubviewsData {
}
- (void)layoutContentSubviews {
}
- (CAAnimation *)barrageAnimation {
return [self.layer animationForKey:kBarrageAnimation];
}
@end
@@ -0,0 +1,34 @@
//
// OCBarrageDescriptor.h
// TestApp
//
// Created by QMTV on 2017/8/23.
// Copyright © 2017年 LFC. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import "OCBarrageHeader.h"
@class OCBarrageCell;
NS_ASSUME_NONNULL_BEGIN
@interface OCBarrageDescriptor : NSObject
@property (nonatomic, assign, nullable) Class barrageCellClass;
@property (nonatomic, assign) OCBarragePositionPriority positionPriority;//显示位置normal型的渲染在low型的上面, height型的渲染在normal上面
@property (nonatomic, assign) CGFloat animationDuration;//动画时间, 时间越长速度越慢, 时间越短速度越快
@property (nonatomic, assign) CGFloat fixedSpeed;//固定速度, 可以防止弹幕在有空闲轨道的情况下重叠, 取值0.0~100.0, animationDuration与fixedSpeed只能选择一个, fixedSpeed设置之后可以不用设置animationDuration
//@property (nonatomic, copy, nullable) OCBarrageTouchAction touchAction DEPRECATED_MSG_ATTRIBUTE("use OCBarrageCellTouchedAction instead");
@property (nonatomic, copy, nullable) OCBarrageTouchAction touchAction;
@property (nonatomic, copy, nullable) OCBarrageCellTouchedAction cellTouchedAction;//新属性里回传了被点击的cell, 可以在代码块里更改被点击的cell的属性, 比如之前有用户需要在弹幕被点击的时候修改被点击的弹幕的文字颜色等等. 用来替代旧版本的touchAction
@property (nonatomic, strong, nullable) UIColor *borderColor; // Default is no border
@property (nonatomic, assign) CGFloat borderWidth; // Default is 0
@property (nonatomic, assign) CGFloat cornerRadius; // Default is 8
@property (nonatomic, assign) NSRange renderRange;//渲染范围, 最终渲染出来的弹幕的Y坐标最小不小于renderRange.location, 最大不超过renderRange.length-barrageCell.height
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,24 @@
//
// OCBarrageDescriptor.m
// TestApp
//
// Created by QMTV on 2017/8/23.
// Copyright © 2017年 LFC. All rights reserved.
//
#import "OCBarrageDescriptor.h"
@implementation OCBarrageDescriptor
- (instancetype)init {
self = [super init];
if (self) {
}
return self;
}
@end
@@ -0,0 +1,32 @@
//
// OCBarrageHeader.h
// TestApp
//
// Created by QMTV on 2017/8/23.
// Copyright © 2017年 LFC. All rights reserved.
//
#ifndef OCBarrageHeader_h
#define OCBarrageHeader_h
#define kBarrageAnimation @"kBarrageAnimation"
@class OCBarrageDescriptor;
@class OCBarrageCell;
typedef void(^OCBarrageTouchAction)(__weak OCBarrageDescriptor *descriptor);
typedef void(^OCBarrageCellTouchedAction)(__weak OCBarrageDescriptor *descriptor, __weak OCBarrageCell *cell);
typedef NS_ENUM(NSInteger, OCBarragePositionPriority) {
OCBarragePositionLow = 0,
OCBarragePositionMiddle,
OCBarragePositionHigh,
OCBarragePositionVeryHigh
};
typedef NS_ENUM(NSInteger, OCBarrageRenderPositionStyle) {//新加的cell的y坐标的类型
OCBarrageRenderPositionRandomTracks = 0, //将OCBarrageRenderView分成几条轨道, 随机选一条展示
OCBarrageRenderPositionRandom, // y坐标随机
OCBarrageRenderPositionIncrease, //y坐标递增, 循环
};
#endif /* OCBarrageHeader_h */
@@ -0,0 +1,30 @@
//
// OCBarrageView.h
// TestApp
//
// Created by QMTV on 2017/8/22.
// Copyright © 2017年 LFC. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "OCBarrageRenderView.h"
NS_ASSUME_NONNULL_BEGIN
@interface OCBarrageManager : NSObject {
OCBarrageRenderView *_renderView;
}
@property (nonatomic, strong, readonly) OCBarrageRenderView *renderView;
@property (nonatomic, assign, readonly) OCBarrageRenderStatus renderStatus;
- (void)start;
- (void)pause;
- (void)resume;
- (void)stop;
- (void)renderBarrageDescriptor:(OCBarrageDescriptor *)barrageDescriptor;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,65 @@
//
// OCBarrageView.m
// TestApp
//
// Created by QMTV on 2017/8/22.
// Copyright © 2017年 LFC. All rights reserved.
//
#import "OCBarrageManager.h"
@implementation OCBarrageManager
- (void)dealloc {
NSLog(@"%s", __func__);
[_renderView stop];
}
- (instancetype)init {
self = [super init];
if (self) {
_renderView = [[OCBarrageRenderView alloc] init];
}
return self;
}
- (void)start {
[self.renderView start];
}
- (void)pause {
[self.renderView pause];
}
- (void)resume {
[self.renderView resume];
}
- (void)stop {
[self.renderView stop];
}
- (void)renderBarrageDescriptor:(OCBarrageDescriptor *)barrageDescriptor {
if (!barrageDescriptor) {
return;
}
if (![barrageDescriptor isKindOfClass:[OCBarrageDescriptor class]]) {
return;
}
OCBarrageCell *barrageCell = [self.renderView dequeueReusableCellWithClass:barrageDescriptor.barrageCellClass];
if (!barrageCell) {
return;
}
barrageCell.barrageDescriptor = barrageDescriptor;
[self.renderView fireBarrageCell:barrageCell];
}
#pragma mark ------ getter
- (OCBarrageRenderView *)renderView {
return _renderView;
}
@end
@@ -0,0 +1,55 @@
//
// OCBarrageContentView.h
// TestApp
//
// Created by QMTV on 2017/8/22.
// Copyright © 2017年 LFC. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "OCBarrageCell.h"
NS_ASSUME_NONNULL_BEGIN
typedef NS_ENUM(NSInteger, OCBarrageRenderStatus) {
OCBarrageRenderStoped = 0,
OCBarrageRenderStarted,
OCBarrageRenderPaused
};
@interface OCBarrageRenderView : UIView <CAAnimationDelegate> {
NSMutableArray<OCBarrageCell *> *_animatingCells;
NSMutableArray<OCBarrageCell *> *_idleCells;
dispatch_semaphore_t _animatingCellsLock;
dispatch_semaphore_t _idleCellsLock;
dispatch_semaphore_t _trackInfoLock;
OCBarrageCell *_lastestCell;
UIView *_lowPositionView;
UIView *_middlePositionView;
UIView *_highPositionView;
UIView *_veryHighPositionView;
BOOL _autoClear;
OCBarrageRenderStatus _renderStatus;
NSMutableDictionary *_trackNextAvailableTime;
}
@property (nonatomic, strong, readonly) NSMutableArray<OCBarrageCell *> *animatingCells;
@property (nonatomic, strong, readonly) NSMutableArray<OCBarrageCell *> *idleCells;
@property (nonatomic, assign) OCBarrageRenderPositionStyle renderPositionStyle;
@property (nonatomic, assign, readonly) OCBarrageRenderStatus renderStatus;
- (nullable OCBarrageCell *)dequeueReusableCellWithClass:(Class)barrageCellClass;
- (void)fireBarrageCell:(OCBarrageCell *)barrageCell;
- (BOOL)trigerActionWithPoint:(CGPoint)touchPoint;
@property (nonatomic, copy) void(^animationStopBlock)(void);
- (void)start;
- (void)pause;
- (void)resume;
- (void)stop;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,581 @@
//
// OCBarrageContentView.m
// TestApp
//
// Created by QMTV on 2017/8/22.
// Copyright © 2017年 LFC. All rights reserved.
//
#define kNextAvailableTimeKey(identifier, index) [NSString stringWithFormat:@"%@_%d", identifier, index]
#import "OCBarrageRenderView.h"
#import "OCBarrageTrackInfo.h"
@implementation OCBarrageRenderView
- (void)dealloc {
NSLog(@"%s", __func__);
}
- (instancetype)init {
self = [super init];
if (self) {
_animatingCellsLock = dispatch_semaphore_create(1);
_idleCellsLock = dispatch_semaphore_create(1);
_trackInfoLock = dispatch_semaphore_create(1);
_lowPositionView = [[UIView alloc] init];
[self addSubview:_lowPositionView];
_middlePositionView = [[UIView alloc] init];
[self addSubview:_middlePositionView];
_highPositionView = [[UIView alloc] init];
[self addSubview:_highPositionView];
_veryHighPositionView = [[UIView alloc] init];
[self addSubview:_veryHighPositionView];
self.layer.masksToBounds = YES;
_trackNextAvailableTime = [NSMutableDictionary dictionary];
}
return self;
}
- (nullable OCBarrageCell *)dequeueReusableCellWithClass:(Class)barrageCellClass {
OCBarrageCell *barrageCell = nil;
dispatch_semaphore_wait(_idleCellsLock, DISPATCH_TIME_FOREVER);
for (OCBarrageCell *cell in self.idleCells) {
if ([NSStringFromClass([cell class]) isEqualToString:NSStringFromClass(barrageCellClass)]) {
barrageCell = cell;
break;
}
}
if (barrageCell) {
[self.idleCells removeObject:barrageCell];
barrageCell.idleTime = 0.0;
} else {
barrageCell = [self newCellWithClass:barrageCellClass];
}
dispatch_semaphore_signal(_idleCellsLock);
if (![barrageCell isKindOfClass:[OCBarrageCell class]]) {
return nil;
}
return barrageCell;
}
- (OCBarrageCell *)newCellWithClass:(Class)barrageCellClass {
OCBarrageCell *barrageCell = [[barrageCellClass alloc] init];
if (![barrageCell isKindOfClass:[OCBarrageCell class]]) {
return nil;
}
return barrageCell;
}
- (void)start {
switch (self.renderStatus) {
case OCBarrageRenderStarted: {
return;
}
break;
case OCBarrageRenderPaused: {
[self resume];
return;
}
break;
default: {
_renderStatus = OCBarrageRenderStarted;
}
break;
}
}
- (void)pause {
switch (self.renderStatus) {
case OCBarrageRenderStarted: {
_renderStatus = OCBarrageRenderPaused;
}
break;
case OCBarrageRenderPaused: {
return;
}
break;
default: {
return;
}
break;
}
dispatch_semaphore_wait(_animatingCellsLock, DISPATCH_TIME_FOREVER);
NSEnumerator *enumerator = [self.animatingCells reverseObjectEnumerator];
OCBarrageCell *cell = nil;
while (cell = [enumerator nextObject]){
CFTimeInterval pausedTime = [cell.layer convertTime:CACurrentMediaTime() fromLayer:nil];
cell.layer.speed = 0.0;
cell.layer.timeOffset = pausedTime;
}
dispatch_semaphore_signal(_animatingCellsLock);
}
- (void)resume {
switch (self.renderStatus) {
case OCBarrageRenderStarted: {
return;
}
break;
case OCBarrageRenderPaused: {
_renderStatus = OCBarrageRenderStarted;
}
break;
default: {
return;
}
break;
}
dispatch_semaphore_wait(_animatingCellsLock, DISPATCH_TIME_FOREVER);
NSEnumerator *enumerator = [self.animatingCells reverseObjectEnumerator];
OCBarrageCell *cell = nil;
while (cell = [enumerator nextObject]){
CFTimeInterval pausedTime = cell.layer.timeOffset;
cell.layer.speed = 1.0;
cell.layer.timeOffset = 0.0;
cell.layer.beginTime = 0.0;
CFTimeInterval timeSincePause = [cell.layer convertTime:CACurrentMediaTime() fromLayer:nil] - pausedTime;
cell.layer.beginTime = timeSincePause;
}
dispatch_semaphore_signal(_animatingCellsLock);
}
- (void)stop {
switch (self.renderStatus) {
case OCBarrageRenderStarted: {
_renderStatus = OCBarrageRenderStoped;
}
break;
case OCBarrageRenderPaused: {
_renderStatus = OCBarrageRenderStoped;
}
break;
default: {
return;
}
break;
}
if (_autoClear) {
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(clearIdleCells) object:nil];
}
dispatch_semaphore_wait(_animatingCellsLock, DISPATCH_TIME_FOREVER);
NSEnumerator *animatingEnumerator = [self.animatingCells reverseObjectEnumerator];
OCBarrageCell *animatingCell = nil;
while (animatingCell = [animatingEnumerator nextObject]){
CFTimeInterval pausedTime = [animatingCell.layer convertTime:CACurrentMediaTime() fromLayer:nil];
animatingCell.layer.speed = 0.0;
animatingCell.layer.timeOffset = pausedTime;
[animatingCell.layer removeAllAnimations];
[animatingCell removeFromSuperview];
}
[self.animatingCells removeAllObjects];
dispatch_semaphore_signal(_animatingCellsLock);
dispatch_semaphore_wait(_idleCellsLock, DISPATCH_TIME_FOREVER);
[self.idleCells removeAllObjects];
dispatch_semaphore_signal(_idleCellsLock);
dispatch_semaphore_wait(_trackInfoLock, DISPATCH_TIME_FOREVER);
[_trackNextAvailableTime removeAllObjects];
dispatch_semaphore_signal(_trackInfoLock);
}
- (void)fireBarrageCell:(OCBarrageCell *)barrageCell {
switch (self.renderStatus) {
case OCBarrageRenderStarted: {
}
break;
case OCBarrageRenderPaused: {
return;
}
break;
default:
return;
break;
}
if (!barrageCell) {
return;
}
if (![barrageCell isKindOfClass:[OCBarrageCell class]]) {
return;
}
[barrageCell clearContents];
[barrageCell updateSubviewsData];
[barrageCell layoutContentSubviews];
[barrageCell convertContentToImage];
[barrageCell sizeToFit];
[barrageCell removeSubViewsAndSublayers];
[barrageCell addBorderAttributes];
dispatch_semaphore_wait(_animatingCellsLock, DISPATCH_TIME_FOREVER);
_lastestCell = [self.animatingCells lastObject];
[self.animatingCells addObject:barrageCell];
barrageCell.idle = NO;
dispatch_semaphore_signal(_animatingCellsLock);
[self addBarrageCell:barrageCell WithPositionPriority:barrageCell.barrageDescriptor.positionPriority];
CGRect cellFrame = [self calculationBarrageCellFrame:barrageCell];
barrageCell.frame = cellFrame;
[barrageCell addBarrageAnimationWithDelegate:self];
[self recordTrackInfoWithBarrageCell:barrageCell];
_lastestCell = barrageCell;
}
- (void)addBarrageCell:(OCBarrageCell *)barrageCell WithPositionPriority:(OCBarragePositionPriority)positionPriority {
switch (positionPriority) {
case OCBarragePositionMiddle: {
[self insertSubview:barrageCell aboveSubview:_middlePositionView];
}
break;
case OCBarragePositionHigh: {
[self insertSubview:barrageCell belowSubview:_highPositionView];
}
break;
case OCBarragePositionVeryHigh: {
[self insertSubview:barrageCell belowSubview:_veryHighPositionView];
}
break;
default: {
[self insertSubview:barrageCell belowSubview:_lowPositionView];
}
break;
}
}
- (CGRect)calculationBarrageCellFrame:(OCBarrageCell *)barrageCell {
CGRect cellFrame = barrageCell.bounds;
cellFrame.origin.x = CGRectGetMaxX(self.frame);
if (![[NSValue valueWithRange:barrageCell.barrageDescriptor.renderRange] isEqualToValue:[NSValue valueWithRange:NSMakeRange(0, 0)]]) {
CGFloat cellHeight = CGRectGetHeight(barrageCell.bounds);
CGFloat minOriginY = barrageCell.barrageDescriptor.renderRange.location;
CGFloat maxOriginY = barrageCell.barrageDescriptor.renderRange.length;
if (maxOriginY > CGRectGetHeight(self.bounds)) {
maxOriginY = CGRectGetHeight(self.bounds);
}
if (minOriginY < 0) {
minOriginY = 0;
}
CGFloat renderHeight = maxOriginY - minOriginY;
if (renderHeight < 0) {
renderHeight = cellHeight;
}
int trackCount = floorf(renderHeight/cellHeight);
int trackIndex = arc4random_uniform(trackCount);//用户改变行高(比如弹幕文字大小不会引起显示bug, 因为虽然是同一个类, 但是trackCount变小了, 所以不会出现trackIndex*cellHeight超出屏幕边界的情况)
dispatch_semaphore_wait(_trackInfoLock, DISPATCH_TIME_FOREVER);
OCBarrageTrackInfo *trackInfo = [_trackNextAvailableTime objectForKey:kNextAvailableTimeKey(NSStringFromClass([barrageCell class]), trackIndex)];
if (trackInfo && trackInfo.nextAvailableTime > CACurrentMediaTime()) {//当前行暂不可用
NSMutableArray *availableTrackInfos = [NSMutableArray array];
for (OCBarrageTrackInfo *info in _trackNextAvailableTime.allValues) {
if (CACurrentMediaTime() > info.nextAvailableTime && [info.trackIdentifier containsString:NSStringFromClass([barrageCell class])]) {//只在同类弹幕中判断是否有可用的轨道
[availableTrackInfos addObject:info];
}
}
if (availableTrackInfos.count > 0) {
OCBarrageTrackInfo *randomInfo = [availableTrackInfos objectAtIndex:arc4random_uniform((int)availableTrackInfos.count)];
trackIndex = randomInfo.trackIndex;
} else {
if (_trackNextAvailableTime.count < trackCount) {//刚开始不是每一条轨道都跑过弹幕, 还有空轨道
NSMutableArray *numberArray = [NSMutableArray array];
for (int index = 0; index < trackCount; index++) {
OCBarrageTrackInfo *emptyTrackInfo = [_trackNextAvailableTime objectForKey:kNextAvailableTimeKey(NSStringFromClass([barrageCell class]), index)];
if (!emptyTrackInfo) {
[numberArray addObject:[NSNumber numberWithInt:index]];
}
}
if (numberArray.count > 0) {
trackIndex = [[numberArray objectAtIndex:arc4random_uniform((int)numberArray.count)] intValue];
}
}
//真的是没有可用的轨道了
}
}
dispatch_semaphore_signal(_trackInfoLock);
barrageCell.trackIndex = trackIndex;
cellFrame.origin.y = trackIndex*cellHeight+minOriginY;
} else {
switch (self.renderPositionStyle) {
case OCBarrageRenderPositionRandom: {
CGFloat maxY = CGRectGetHeight(self.bounds) - CGRectGetHeight(cellFrame);
int originY = floorl(maxY);
cellFrame.origin.y = arc4random_uniform(originY);
}
break;
case OCBarrageRenderPositionIncrease: {
if (_lastestCell) {
CGRect lastestFrame = _lastestCell.frame;
cellFrame.origin.y = CGRectGetMaxY(lastestFrame);
} else {
cellFrame.origin.y = 0.0;
}
}
break;
default: {
CGFloat renderViewHeight = CGRectGetHeight(self.bounds);
CGFloat cellHeight = CGRectGetHeight(barrageCell.bounds);
int trackCount = floorf(renderViewHeight/cellHeight);
int trackIndex = arc4random_uniform(trackCount);//用户改变行高(比如弹幕文字大小不会引起显示bug, 因为虽然是同一个类, 但是trackCount变小了, 所以不会出现trackIndex*cellHeight超出屏幕边界的情况)
dispatch_semaphore_wait(_trackInfoLock, DISPATCH_TIME_FOREVER);
OCBarrageTrackInfo *trackInfo = [_trackNextAvailableTime objectForKey:kNextAvailableTimeKey(NSStringFromClass([barrageCell class]), trackIndex)];
if (trackInfo && trackInfo.nextAvailableTime > CACurrentMediaTime()) {//当前行暂不可用
NSMutableArray *availableTrackInfos = [NSMutableArray array];
for (OCBarrageTrackInfo *info in _trackNextAvailableTime.allValues) {
if (CACurrentMediaTime() > info.nextAvailableTime && [info.trackIdentifier containsString:NSStringFromClass([barrageCell class])]) {//只在同类弹幕中判断是否有可用的轨道
[availableTrackInfos addObject:info];
}
}
if (availableTrackInfos.count > 0) {
OCBarrageTrackInfo *randomInfo = [availableTrackInfos objectAtIndex:arc4random_uniform((int)availableTrackInfos.count)];
trackIndex = randomInfo.trackIndex;
} else {
if (_trackNextAvailableTime.count < trackCount) {//刚开始不是每一条轨道都跑过弹幕, 还有空轨道
NSMutableArray *numberArray = [NSMutableArray array];
for (int index = 0; index < trackCount; index++) {
OCBarrageTrackInfo *emptyTrackInfo = [_trackNextAvailableTime objectForKey:kNextAvailableTimeKey(NSStringFromClass([barrageCell class]), index)];
if (!emptyTrackInfo) {
[numberArray addObject:[NSNumber numberWithInt:index]];
}
}
if (numberArray.count > 0) {
trackIndex = [[numberArray objectAtIndex:arc4random_uniform((int)numberArray.count)] intValue];
}
}
//真的是没有可用的轨道了
}
}
dispatch_semaphore_signal(_trackInfoLock);
barrageCell.trackIndex = trackIndex;
cellFrame.origin.y = trackIndex*cellHeight;
}
break;
}
}
if (CGRectGetMaxY(cellFrame) > CGRectGetHeight(self.bounds)) {
cellFrame.origin.y = 0.0; //超过底部, 回到顶部
} else if (cellFrame.origin.y < 0) {
cellFrame.origin.y = 0.0;
}
return cellFrame;
}
- (void)clearIdleCells {
dispatch_semaphore_wait(_idleCellsLock, DISPATCH_TIME_FOREVER);
NSTimeInterval timeInterval = [[NSDate date] timeIntervalSince1970];
NSEnumerator *enumerator = [self.idleCells reverseObjectEnumerator];
OCBarrageCell *cell;
while (cell = [enumerator nextObject]){
CGFloat time = timeInterval - cell.idleTime;
if (time > 5.0 && cell.idleTime > 0) {
[self.idleCells removeObject:cell];
}
}
if (self.idleCells.count == 0) {
_autoClear = NO;
} else {
[self performSelector:@selector(clearIdleCells) withObject:nil afterDelay:5.0];
}
dispatch_semaphore_signal(_idleCellsLock);
}
- (void)recordTrackInfoWithBarrageCell:(OCBarrageCell *)barrageCell {
NSString *nextAvalibleTimeKey = kNextAvailableTimeKey(NSStringFromClass([barrageCell class]), barrageCell.trackIndex);
CFTimeInterval duration = barrageCell.barrageAnimation.duration;
NSValue *fromValue = nil;
NSValue *toValue = nil;
if ([barrageCell.barrageAnimation isKindOfClass:[CABasicAnimation class]]) {
fromValue = [(CABasicAnimation *)barrageCell.barrageAnimation fromValue];
toValue = [(CABasicAnimation *)barrageCell.barrageAnimation toValue];
} else if ([barrageCell.barrageAnimation isKindOfClass:[CAKeyframeAnimation class]]) {
fromValue = [[(CAKeyframeAnimation *)barrageCell.barrageAnimation values] firstObject];
toValue = [[(CAKeyframeAnimation *)barrageCell.barrageAnimation values] lastObject];
} else {
}
const char *fromeValueType = [fromValue objCType];
const char *toValueType = [toValue objCType];
if (!fromeValueType || !toValueType) {
return;
}
NSString *fromeValueTypeString = [NSString stringWithCString:fromeValueType encoding:NSUTF8StringEncoding];
NSString *toValueTypeString = [NSString stringWithCString:toValueType encoding:NSUTF8StringEncoding];
if (![fromeValueTypeString isEqualToString:toValueTypeString]) {
return;
}
if ([fromeValueTypeString containsString:@"CGPoint"]) {
CGPoint fromPoint = [fromValue CGPointValue];
CGPoint toPoint = [toValue CGPointValue];
dispatch_semaphore_wait(_trackInfoLock, DISPATCH_TIME_FOREVER);
OCBarrageTrackInfo *trackInfo = [_trackNextAvailableTime objectForKey:nextAvalibleTimeKey];
if (!trackInfo) {
trackInfo = [[OCBarrageTrackInfo alloc] init];
trackInfo.trackIdentifier = nextAvalibleTimeKey;
trackInfo.trackIndex = barrageCell.trackIndex;
}
trackInfo.barrageCount++;
trackInfo.nextAvailableTime = CGRectGetWidth(barrageCell.bounds);
CGFloat distanceX = fabs(toPoint.x - fromPoint.x);
CGFloat distanceY = fabs(toPoint.y - fromPoint.y);
CGFloat distance = MAX(distanceX, distanceY);
CGFloat speed = distance/duration;
if (distanceX == distance) {
CFTimeInterval time = CGRectGetWidth(barrageCell.bounds)/speed;
trackInfo.nextAvailableTime = CACurrentMediaTime() + time + 0.1;//多加一点时间
[_trackNextAvailableTime setValue:trackInfo forKey:nextAvalibleTimeKey];
} else if (distanceY == distance) {
// CFTimeInterval time = CGRectGetHeight(barrageCell.bounds)/speed;
} else {
}
dispatch_semaphore_signal(_trackInfoLock);
return;
} else if ([fromeValueTypeString containsString:@"CGVector"]) {
return;
} else if ([fromeValueTypeString containsString:@"CGSize"]) {
return;
} else if ([fromeValueTypeString containsString:@"CGRect"]) {
return;
} else if ([fromeValueTypeString containsString:@"CGAffineTransform"]) {
return;
} else if ([fromeValueTypeString containsString:@"UIEdgeInsets"]) {
return;
} else if ([fromeValueTypeString containsString:@"UIOffset"]) {
return;
}
}
#pragma mark ----- CAAnimationDelegate
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag {
!self.animationStopBlock ?: self.animationStopBlock();
if (!flag) {
return;
}
if (self.renderStatus == OCBarrageRenderStoped) {
return;
}
OCBarrageCell *animationedCell = nil;
dispatch_semaphore_wait(_animatingCellsLock, DISPATCH_TIME_FOREVER);
for (OCBarrageCell *cell in self.animatingCells) {
CAAnimation *barrageAnimation = [cell barrageAnimation];
if (barrageAnimation == anim) {
animationedCell = cell;
[self.animatingCells removeObject:cell];
break;
}
}
dispatch_semaphore_signal(_animatingCellsLock);
if (!animationedCell) {
return;
}
dispatch_semaphore_wait(_trackInfoLock, DISPATCH_TIME_FOREVER);
OCBarrageTrackInfo *trackInfo = [_trackNextAvailableTime objectForKey:kNextAvailableTimeKey(NSStringFromClass([animationedCell class]), animationedCell.trackIndex)];
if (trackInfo) {
trackInfo.barrageCount--;
}
dispatch_semaphore_signal(_trackInfoLock);
[animationedCell removeFromSuperview];
[animationedCell prepareForReuse];
dispatch_semaphore_wait(_idleCellsLock, DISPATCH_TIME_FOREVER);
animationedCell.idleTime = [[NSDate date] timeIntervalSince1970];
[self.idleCells addObject:animationedCell];
dispatch_semaphore_signal(_idleCellsLock);
if (!_autoClear) {
[self performSelector:@selector(clearIdleCells) withObject:nil afterDelay:5.0];
_autoClear = YES;
}
}
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
if (event.type == UIEventTypeTouches) {
UITouch *touch = [touches.allObjects firstObject];
CGPoint touchPoint = [touch locationInView:self];
[self trigerActionWithPoint:touchPoint];
}
}
- (BOOL)trigerActionWithPoint:(CGPoint)touchPoint
{
dispatch_semaphore_wait(_animatingCellsLock, DISPATCH_TIME_FOREVER);
BOOL anyTriger = NO;
NSEnumerator *enumerator = [self.animatingCells reverseObjectEnumerator];
OCBarrageCell *cell = nil;
while (cell = [enumerator nextObject]){
if ([cell.layer.presentationLayer hitTest:touchPoint]) {
if (cell.barrageDescriptor.touchAction) {
cell.barrageDescriptor.touchAction(cell.barrageDescriptor);
anyTriger = YES;
}
if (cell.barrageDescriptor.cellTouchedAction) {
cell.barrageDescriptor.cellTouchedAction(cell.barrageDescriptor, cell);
anyTriger = YES;
}
break;
}
}
dispatch_semaphore_signal(_animatingCellsLock);
return anyTriger;
}
#pragma mark ----- getter
- (NSMutableArray<OCBarrageCell *> *)animatingCells {
if (!_animatingCells) {
_animatingCells = [[NSMutableArray alloc] init];
}
return _animatingCells;
}
- (NSMutableArray<OCBarrageCell *> *)idleCells {
if (!_idleCells) {
_idleCells = [[NSMutableArray alloc] init];
}
return _idleCells;
}
- (OCBarrageRenderStatus)renderStatus {
return _renderStatus;
}
@end
@@ -0,0 +1,21 @@
//
// OCBarrageTextCell.h
// TestApp
//
// Created by QMTV on 2017/8/23.
// Copyright © 2017年 LFC. All rights reserved.
//
#import "OCBarrageCell.h"
#import "OCBarrageTextDescriptor.h"
NS_ASSUME_NONNULL_BEGIN
@interface OCBarrageTextCell : OCBarrageCell
@property (nonatomic, strong) UILabel *textLabel;
@property (nonatomic, strong, nullable) OCBarrageTextDescriptor *textDescriptor;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,100 @@
//
// OCBarrageTextCell.m
// TestApp
//
// Created by QMTV on 2017/8/23.
// Copyright © 2017年 LFC. All rights reserved.
//
#import "OCBarrageTextCell.h"
@implementation OCBarrageTextCell
- (instancetype)init {
self = [super init];
if (self) {
}
return self;
}
- (void)prepareForReuse {
[super prepareForReuse];
}
- (void)updateSubviewsData {
if (!_textLabel) {
[self addSubview:self.textLabel];
}
if (self.textDescriptor.textShadowOpened) {
self.textLabel.layer.shadowColor = self.textDescriptor.shadowColor.CGColor;
self.textLabel.layer.shadowOffset = self.textDescriptor.shadowOffset;
self.textLabel.layer.shadowRadius = self.textDescriptor.shadowRadius;
self.textLabel.layer.shadowOpacity = self.textDescriptor.shadowOpacity;
}
[self.textLabel setAttributedText:self.textDescriptor.attributedText];
}
- (void)layoutContentSubviews {
// CGRect textFrame = [self.textDescriptor.attributedText.string boundingRectWithSize:CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX) options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading attributes:[self.textDescriptor.attributedText attributesAtIndex:0 effectiveRange:NULL] context:nil];
// self.textLabel.frame = textFrame;
self.textLabel.frame = CGRectMake(0, 0, self.textLabel.intrinsicContentSize.width, self.textLabel.intrinsicContentSize.height + 20);
}
- (void)convertContentToImage {
UIImage *contentImage = [self.layer convertContentToImageWithSize:_textLabel.frame.size];
[self.layer setContents:(__bridge id)contentImage.CGImage];
}
- (void)removeSubViewsAndSublayers {
[super removeSubViewsAndSublayers];
_textLabel = nil;
}
- (void)addBarrageAnimationWithDelegate:(id<CAAnimationDelegate>)animationDelegate {
if (!self.superview) {
return;
}
CGPoint startCenter = CGPointMake(CGRectGetMaxX(self.superview.bounds) + CGRectGetWidth(self.bounds)/2, self.center.y);
CGPoint endCenter = CGPointMake(-(CGRectGetWidth(self.bounds)/2), self.center.y);
CGFloat animationDuration = self.barrageDescriptor.animationDuration;
if (self.barrageDescriptor.fixedSpeed > 0.0) {//如果是固定速度那就用固定速度
if (self.barrageDescriptor.fixedSpeed > 100.0) {
self.barrageDescriptor.fixedSpeed = 100.0;
}
animationDuration = (startCenter.x - endCenter.x)/([UIScreen mainScreen].scale*2)/self.barrageDescriptor.fixedSpeed;
}
CAKeyframeAnimation *walkAnimation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
walkAnimation.values = @[[NSValue valueWithCGPoint:startCenter], [NSValue valueWithCGPoint:endCenter]];
walkAnimation.keyTimes = @[@(0.0), @(1.0)];
walkAnimation.duration = animationDuration;
walkAnimation.repeatCount = 1;
walkAnimation.delegate = animationDelegate;
walkAnimation.removedOnCompletion = NO;
walkAnimation.fillMode = kCAFillModeForwards;
[self.layer addAnimation:walkAnimation forKey:kBarrageAnimation];
}
- (UILabel *)textLabel {
if (!_textLabel) {
_textLabel = [[UILabel alloc] init];
_textLabel.textAlignment = NSTextAlignmentCenter;
}
return _textLabel;
}
- (void)setBarrageDescriptor:(OCBarrageDescriptor *)barrageDescriptor {
[super setBarrageDescriptor:barrageDescriptor];
self.textDescriptor = (OCBarrageTextDescriptor *)barrageDescriptor;
}
@end
@@ -0,0 +1,37 @@
//
// OCBarrageTextDescriptor.h
// TestApp
//
// Created by QMTV on 2017/8/23.
// Copyright © 2017年 LFC. All rights reserved.
//
#import "OCBarrageDescriptor.h"
NS_ASSUME_NONNULL_BEGIN
@interface OCBarrageTextDescriptor : OCBarrageDescriptor {
NSMutableDictionary *_textAttribute;
}
@property (nonatomic, strong, nullable) UIFont *textFont;
@property (nonatomic, strong, nullable) UIColor *textColor;
/*
* 关闭文字阴影可大幅提升性能, 推荐使用strokeColor, 与shadowColor相比strokeColor性能更强悍
*/
@property (nonatomic, assign) BOOL textShadowOpened;//默认NO
@property (nonatomic, strong, nullable) UIColor *shadowColor;//默认黑色
@property (nonatomic, assign) CGSize shadowOffset;//默认CGSizeZero
@property (nonatomic, assign) CGFloat shadowRadius;//默认2.0
@property (nonatomic, assign) CGFloat shadowOpacity;//默认0.5
@property (nonatomic, strong, nullable) UIColor *strokeColor;
@property (nonatomic, assign) int strokeWidth;//笔画宽度(粗细),取值为 NSNumber 对象(整数),负值填充效果,正值中空效果
@property (nonatomic, copy, nullable) NSString *text;
@property (nonatomic, copy, nullable) NSAttributedString *attributedText;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,191 @@
//
// OCBarrageTextDescriptor.m
// TestApp
//
// Created by QMTV on 2017/8/23.
// Copyright © 2017年 LFC. All rights reserved.
//
#import "OCBarrageTextDescriptor.h"
@implementation OCBarrageTextDescriptor
@synthesize textFont = _textFont, textColor = _textColor, shadowColor = _shadowColor, attributedText = _attributedText;
- (instancetype)init {
self = [super init];
if (self) {
_textAttribute = [NSMutableDictionary dictionary];
_shadowColor = [UIColor blackColor];
_shadowOffset = CGSizeZero;
_shadowRadius = 2.0;
_shadowOpacity = 0.5;
}
return self;
}
#pragma mark ----- setter
- (void)setTextShadowOpened:(BOOL)textShadowOpened {
_textShadowOpened = textShadowOpened;
if (textShadowOpened) {
[_textAttribute removeObjectForKey:NSStrokeColorAttributeName];
[_textAttribute removeObjectForKey:NSStrokeWidthAttributeName];
}
}
- (void)setTextFont:(UIFont *)textFont {
_textFont = textFont;
[_textAttribute setValue:textFont forKey:NSFontAttributeName];
}
- (void)setTextColor:(UIColor *)textColor {
_textColor = textColor;
[_textAttribute setValue:textColor forKey:NSForegroundColorAttributeName];
}
- (void)setStrokeColor:(UIColor *)strokeColor {
_strokeColor = strokeColor;
if (_textShadowOpened) {
return;
}
[_textAttribute setValue:strokeColor forKey:NSStrokeColorAttributeName];
}
- (void)setStrokeWidth:(int)strokeWidth {
_strokeWidth = strokeWidth;
if (_textShadowOpened) {
return;
}
[_textAttribute setValue:[NSNumber numberWithInt:strokeWidth] forKey:NSStrokeWidthAttributeName];
}
#pragma mark ----- getter
- (NSString *)text {
if (!_text) {
_text = _attributedText.string;
}
return _text;
}
- (UIFont *)textFont {
if (!_textFont) {
_textFont = [UIFont systemFontOfSize:17.0];
}
return _textFont;
}
- (UIColor *)textColor {
if (!_textColor) {
_textColor = [UIColor whiteColor];
}
return _textColor;
}
- (UIColor *)shadowColor {
if (!_shadowColor) {
_shadowColor = [UIColor blackColor];
}
return _shadowColor;
}
- (NSAttributedString *)attributedText {
if (!_attributedText) {
if (!_text) {
return nil;
}
_attributedText = [[NSAttributedString alloc] initWithString:_text attributes:_textAttribute];
}
//修复阿拉伯文字显示的bug.
NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init];
[paragraphStyle setBaseWritingDirection:NSWritingDirectionLeftToRight];
NSMutableAttributedString *tempText = [[NSMutableAttributedString alloc] initWithAttributedString:_attributedText];
[tempText addAttributes:@{NSParagraphStyleAttributeName:paragraphStyle} range:NSMakeRange(0, tempText.string.length)];
return [tempText copy];
}
- (id)mutableCopy {
return [self copy];
}
@end
/**
* API: Character Attributes , NSAttributedString 共有21个属性
*
* 1. NSFontAttributeName ->设置字体属性,默认值:字体:Helvetica(Neue) 字号:12
*
*
* 2. NSParagraphStyleAttributeName ->设置文本段落排版格式,取值为 NSParagraphStyle 对象(详情见下面的API说明)
*
*
* 3. NSForegroundColorAttributeName ->设置字体颜色,取值为 UIColor对象,默认值为黑色
*
*
* 4. NSBackgroundColorAttributeName ->设置字体所在区域背景颜色,取值为 UIColor对象,默认值为nil, 透明色
*
*
* 5. NSLigatureAttributeName ->设置连体属性,取值为NSNumber 对象(整数),0 表示没有连体字符,1 表示使用默认的连体字符
*
*
* 6. NSKernAttributeName ->设置字符间距,取值为 NSNumber 对象(整数),正值间距加宽,负值间距变窄
*
*
* 7. NSStrikethroughStyleAttributeName ->设置删除线,取值为 NSNumber 对象(整数)
*
*
* 8. NSStrikethroughColorAttributeName ->设置删除线颜色,取值为 UIColor 对象,默认值为黑色
*
*
* 9. NSUnderlineStyleAttributeName ->设置下划线,取值为 NSNumber 对象(整数),枚举常量 NSUnderlineStyle中的值,与删除线类似
*
*
* 10. NSUnderlineColorAttributeName ->设置下划线颜色,取值为 UIColor 对象,默认值为黑色
*
*
* 11. NSStrokeWidthAttributeName ->设置笔画宽度(粗细),取值为 NSNumber 对象(整数),负值填充效果,正值中空效果
*
*
* 12. NSStrokeColorAttributeName ->填充部分颜色,不是字体颜色,取值为 UIColor 对象
*
*
* 13. NSShadowAttributeName ->设置阴影属性,取值为 NSShadow 对象
*
*
* 14. NSTextEffectAttributeName ->设置文本特殊效果,取值为 NSString 对象,目前只有图版印刷效果可用
*
*
* 15. NSBaselineOffsetAttributeName ->设置基线偏移值,取值为 NSNumber (float),正值上偏,负值下偏
*
*
* 16. NSObliquenessAttributeName ->设置字形倾斜度,取值为 NSNumber (float),正值右倾,负值左倾
*
*
* 17. NSExpansionAttributeName ->设置文本横向拉伸属性,取值为 NSNumber (float),正值横向拉伸文本,负值横向压缩文本
*
*
* 18. NSWritingDirectionAttributeName ->设置文字书写方向,从左向右书写或者从右向左书写
*
*
* 19. NSVerticalGlyphFormAttributeName ->设置文字排版方向,取值为 NSNumber 对象(整数),0 表示横排文本,1 表示竖排文本
*
*
* 20. NSLinkAttributeName ->设置链接属性,点击后调用浏览器打开指定URL地址
*
*
* 21. NSAttachmentAttributeName ->设置文本附件,取值为NSTextAttachment对象,常用于文字图片混排
*
*/
@@ -0,0 +1,24 @@
//
// OCBarrageTrackInfo.h
// OCBarrage
//
// Created by QMTV on 2017/8/25.
// Copyright © 2017年 LFC. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface OCBarrageTrackInfo : NSObject
@property (nonatomic, assign) int trackIndex;
@property (nonatomic, copy, nullable) NSString *trackIdentifier;
@property (nonatomic, assign) CFTimeInterval nextAvailableTime;//下次可用的时间
@property (nonatomic, assign) NSInteger barrageCount;//当前行的弹幕数量
@property (nonatomic, assign) CGFloat trackHeight;//轨道高度
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,13 @@
//
// OCBarrageTrackInfo.m
// OCBarrage
//
// Created by QMTV on 2017/8/25.
// Copyright © 2017年 LFC. All rights reserved.
//
#import "OCBarrageTrackInfo.h"
@implementation OCBarrageTrackInfo
@end
@@ -0,0 +1,18 @@
//
// TFAgreementAlertView.h
// TFReader
//
// Created by 谢腾飞 on 2020/12/17.
// Copyright © 2020 xtfei_2011@126.com. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "TFAlertView.h"
NS_ASSUME_NONNULL_BEGIN
@interface TFAgreementAlertView : TFAlertView
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,159 @@
//
// TFAgreementAlertView.m
// TFReader
//
// Created by 谢腾飞 on 2020/12/17.
// Copyright © 2020 xtfei_2011@126.com. All rights reserved.
//
#import "TFAgreementAlertView.h"
#import "TFWebViewController.h"
#import "AppDelegate.h"
#import <MOBFoundation/MobSDK+Privacy.h>
@implementation TFAgreementAlertView
- (void)createSubviews
{
[super createSubviews];
/*** 穿山甲的背景视图,解决穿山甲加载广告期间的空白页面 ***/
UIView *backgroundView = ({
NSString *lauchStoryboardName = [[[NSBundle mainBundle] infoDictionary] valueForKey:@"UILaunchStoryboardName"];
switch (TFLanguageManager.localizedLanguage) {
case TFLanguageTypeSimplifiedChinese:
lauchStoryboardName = @"LaunchScreen_ZH";
break;
case TFLanguageTypeTraditionalChinese:
lauchStoryboardName = @"LaunchScreen_ZW";
break;
case TFLanguageTypeEnglish:
lauchStoryboardName = @"LaunchScreen_EN";
break;
case TFLanguageTypeTail:
lauchStoryboardName = @"LaunchScreen_TH";
break;
default:
lauchStoryboardName = [[[NSBundle mainBundle] infoDictionary] valueForKey:@"UILaunchStoryboardName"];
break;
}
UIViewController *launchScreen = [[UIStoryboard storyboardWithName:lauchStoryboardName bundle:nil] instantiateInitialViewController];
UIView *backgroundView = launchScreen.view;
backgroundView.backgroundColor = [UIColor whiteColor];
backgroundView.frame = [UIScreen mainScreen].bounds;
backgroundView;
});
[self addSubview:backgroundView];
[self sendSubviewToBack:backgroundView];
UIView *shadowView = [[UIView alloc] init];
shadowView.backgroundColor = kColorRGBA(0, 0, 0, 0.3);
[backgroundView addSubview:shadowView];
[shadowView mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(backgroundView);
}];
self.alertDisappearType = TFAlertViewDisappearTypeConfirm;
self.closeButton.hidden = YES;
self.alertTitle = TFLocalizedString(@"用户隐私保护提示");
NSString *prefix = [NSString stringWithFormat:@"%@%@", App_Name, TFLocalizedString(@"隐私政策")];
NSString *suffix = TFLocalizedString(@"软件许可及服务协议");
NSMutableAttributedString *str = [[NSMutableAttributedString alloc] initWithString:[NSString stringWithFormat:TFLocalizedString(@"为了更好的保护您的隐私和信息安全,根据国家相关法律规定和国家标准制定并执行《%@》及《%@》,请您在使用前务必仔细阅读并透彻理解,如果您继续使用本产品即代表您同意本平台的协议条款,我们会全力保护您的个人信息安全。"), prefix, suffix]];
NSRange range1 = [str.string rangeOfString:prefix];
NSRange range2 = [str.string rangeOfString:suffix];
[str setColor:kColorRGB(102, 102, 102)];
[str setFont:kMainFont];
[str setLineSpacing:4];
[str setTextHighlightRange:range1 color:kMainColor backgroundColor:[UIColor whiteColor] tapAction:^(UIView * _Nonnull containerView, NSAttributedString * _Nonnull text, NSRange range, CGRect rect) {
if (![TFViewHelper getWindowRootController].presentedViewController) {
TFWebViewController *web = [[TFWebViewController alloc] init];
web.navTitle = TFLocalizedString(@"隐私政策");
AppDelegate *delegate = (AppDelegate *)kRCodeSync([UIApplication sharedApplication].delegate);
web.URLString = delegate.checkSettingModel.protocol_list.privacy ?: [NSString stringWithFormat:@"%@%@?language=%@", APIURL, Privacy_Policy, TFLanguageManager.serverLocalized];
web.isPresentState = YES;
[[TFViewHelper getWindowRootController] presentViewController:web animated:YES completion:nil];
}
}];
[str setTextHighlightRange:range2 color:kMainColor backgroundColor:[UIColor whiteColor] tapAction:^(UIView * _Nonnull containerView, NSAttributedString * _Nonnull text, NSRange range, CGRect rect) {
if (![TFViewHelper getWindowRootController].presentedViewController) {
TFWebViewController *web = [[TFWebViewController alloc] init];
web.navTitle = TFLocalizedString(@"软件许可");
web.URLString = [NSString stringWithFormat:@"%@%@?language=%@", APIURL, Notify_Note, TFLanguageManager.serverLocalized];
web.isPresentState = YES;
[[TFViewHelper getWindowRootController] presentViewController:web animated:YES completion:nil];
}
}];
self.attributedStr = str;
self.contentScrollView.hidden = YES;
UIScrollView *mainScrollView = [[UIScrollView alloc] init];
mainScrollView.showsHorizontalScrollIndicator = NO;
[self addSubview:mainScrollView];
[mainScrollView mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.contentScrollView).offset(kMargin);
make.left.right.bottom.equalTo(self.contentScrollView);
}];
YYLabel *detailView = [[YYLabel alloc] init];
detailView.backgroundColor = [UIColor whiteColor];
detailView.numberOfLines = 0;
detailView.preferredMaxLayoutWidth = SCREEN_WIDTH - 3 * kMargin - 2 * kMargin;
detailView.attributedText = str;
[mainScrollView addSubview:detailView];
[detailView mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.width.equalTo(mainScrollView);
}];
self.cancelTitle = TFLocalizedString(@"拒绝并退出");
self.confirmTitle = TFLocalizedString(@"我已经阅读并同意");
[self.cancelButton addTarget:self action:@selector(cancelButtonClick) forControlEvents:UIControlEventTouchUpInside];
[self.confirmButton addTarget:self action:@selector(confirmButtonClick) forControlEvents:UIControlEventTouchUpInside];
}
- (void)showAlertView
{
[super showAlertView];
}
- (void)willMoveToWindow:(nullable UIWindow *)newWindow
{
[super willMoveToWindow:newWindow];
if (!newWindow) { // 跳转到协议时newWindow会为空
[UIApplication sharedApplication].statusBarHidden = NO;
} else {
[UIApplication sharedApplication].statusBarHidden = YES;
}
}
- (void)cancelButtonClick
{
UIWindow *window = [UIApplication sharedApplication].keyWindow;
[UIView animateWithDuration:kAnimatedDuration animations:^{
window.alpha = 0;
window.frame = CGRectMake(0, window.bounds.size.height / 2, window.bounds.size.width, 0.5);
} completion:^(BOOL finished) {
exit(0);
}];
}
- (void)confirmButtonClick
{
[self closeAlertView];
TFSystemInfoManager.isAgree = YES;
!self.confirmButtonClickBlock ?: self.confirmButtonClickBlock();
[MobSDK uploadPrivacyPermissionStatus:YES onResult:nil];
}
@end
@@ -0,0 +1,71 @@
//
// TFAlertView.h
// TFReader
//
// Created by 谢腾飞 on 2020/12/17.
// Copyright © 2020 xtfei_2011@126.com. All rights reserved.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
// 按钮样式
typedef NS_ENUM(NSUInteger, TFAlertButtonType) {
TFAlertButtonTypeNone, // 底部无按钮
TFAlertButtonTypeSingleCancel, // 底部单一取消按钮
TFAlertButtonTypeSingleConfirm, // 底部单一确认按钮
TFAlertButtonTypeDouble // 底部双按钮
};
// 弹框消失状态
typedef NS_ENUM(NSUInteger, TFAlertViewDisappearType) {
TFAlertViewDisappearTypeNormal, // 点击按钮或弹框以外位置消失
TFAlertViewDisappearTypeConfirm, // 只能点击按钮消失
TFAlertViewDisappearTypeNever // 不可消失
};
@class TFAlertView;
typedef void(^ClickButtonBlock)(TFAlertView *view);
@interface TFAlertView : UIView
@property (nonatomic ,copy) void(^confirmButtonClickBlock)(void);
@property (nonatomic ,copy) void(^cancelButtonClickBlock)(void);
// 按钮样式
@property (nonatomic ,assign) TFAlertButtonType alertBtnType;
// 弹框消失状态
@property (nonatomic ,assign) TFAlertViewDisappearType alertDisappearType;
// 是否显示按钮分割线
@property (nonatomic ,assign) BOOL showDivider;
@property (nonatomic ,copy) NSString *alertTitle;
@property (nonatomic ,copy) NSString *alertDetailContent;
@property (nonatomic ,strong) NSMutableAttributedString *attributedStr;
@property (nonatomic ,copy) NSString *cancelTitle;
@property (nonatomic ,copy) NSString *confirmTitle;
- (void)showAlertView;
- (void)closeAlertView;
- (instancetype)initInController;
- (instancetype)initWithFrame:(CGRect)frame UNAVAILABLE_ATTRIBUTE;
/*** 子类使用变量 ***/
@property (nonatomic ,assign ,readonly) CGFloat alertViewWidth;
@property (nonatomic ,assign ,readonly) CGFloat alertViewBtnHeight;
@property (nonatomic ,strong) UIView *alertBackView;
@property (nonatomic ,strong) UIButton *closeButton;
@property (nonatomic ,strong) UIButton *confirmButton;
@property (nonatomic ,strong) UIButton *cancelButton;
@property (nonatomic ,strong) UILabel *alertTitleLabel;
@property (nonatomic ,strong) YYLabel *contentLabel;
@property (nonatomic ,strong) UIScrollView *contentScrollView;
- (void)createSubviews;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,394 @@
//
// TFAlertView.m
// TFReader
//
// Created by 谢腾飞 on 2020/12/17.
// Copyright © 2020 xtfei_2011@126.com. All rights reserved.
//
#import "TFAlertView.h"
@interface TFAlertView ()<UIGestureRecognizerDelegate>
@property (nonatomic ,strong) UITapGestureRecognizer *dismissTapGesture;
@property (nonatomic ,assign) CGFloat alertViewBtnWidth;
@property (nonatomic ,assign) BOOL inController;
@end
@implementation TFAlertView
- (instancetype)init
{
if (self = [super init]) {
_inController = NO;
[self initialize];
[self createSubviews];
}
return self;
}
- (instancetype)initInController
{
if (self = [super init]) {
_inController = YES;
[self initialize];
[self createSubviews];
}
return self;
}
- (void)initialize
{
_showDivider = YES;
_alertViewBtnHeight = 50.0f;
_alertViewWidth = SCREEN_WIDTH - 3 * kMargin;
self.alertBtnType = TFAlertButtonTypeDouble;
self.alertDisappearType = TFAlertViewDisappearTypeNormal;
self.frame = CGRectMake(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
self.backgroundColor = kBlackTransparentColor;
if (_inController) {
[kMainWindow.rootViewController.view addSubview:self];
} else {
[kMainWindow addSubview:self];
}
if (!self.superview) {
UIViewController *vc = [UIApplication sharedApplication].windows.firstObject.rootViewController;
[vc.view addSubview:self];
}
self.dismissTapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(closeAlertView)];
self.dismissTapGesture.numberOfTapsRequired = 1;
self.dismissTapGesture.delegate = self;
[self addGestureRecognizer:self.dismissTapGesture];
}
- (void)createSubviews
{
// 添加背景
[self addSubview:self.alertBackView];
// 添加关闭按钮
[self.alertBackView addSubview:self.closeButton];
// 添加标题
[self.alertBackView addSubview:self.alertTitleLabel];
// 添加内容
[self.alertBackView addSubview:self.contentScrollView];
[self.contentScrollView addSubview:self.contentLabel];
// 取消按钮
[self.alertBackView addSubview:self.cancelButton];
// 确认按钮
[self.alertBackView addSubview:self.confirmButton];
}
// 显示弹框
- (void)showAlertView
{
[self.alertBackView mas_makeConstraints:^(MASConstraintMaker *make) {
make.width.mas_equalTo(self.alertViewWidth);
make.bottom.mas_equalTo(self.confirmButton.mas_bottom).with.offset(0);
make.centerX.mas_equalTo(self.mas_centerX);
make.centerY.mas_equalTo(self.mas_centerY).with.offset(0);
}];
[self.closeButton mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.mas_equalTo(self.alertBackView.mas_top);
make.right.mas_equalTo(self.alertBackView.mas_right);
make.width.height.mas_equalTo(50);
}];
[self.alertTitleLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(self.alertBackView.mas_left).with.offset(kMargin);
make.right.mas_equalTo(self.alertBackView.mas_right).with.offset(- kMargin);
make.top.mas_equalTo(self.alertBackView.mas_top).with.offset(kMargin);
if (self.alertTitle.length > 0) {
make.height.mas_equalTo(30);
} else {
make.height.mas_equalTo(CGFLOAT_MIN);
}
}];
[self.contentScrollView mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerX.mas_equalTo(self.alertTitleLabel.centerX);
make.top.mas_equalTo(self.alertTitleLabel.mas_bottom);
make.width.mas_equalTo(self.alertViewWidth - 2 * kMargin);
make.height.mas_equalTo([self getContentScrollViewHeight]);
}];
[self.contentLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.top.mas_equalTo(0);
make.width.mas_equalTo(self.contentScrollView.mas_width);
make.height.mas_equalTo([self getContentLabelHeight]);
}];
[self.cancelButton mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(self.alertBackView.mas_left).with.offset(0);
make.top.mas_equalTo(self.contentScrollView.mas_bottom).with.offset(kMargin);
make.height.mas_equalTo(_alertViewBtnHeight);
if (self.alertBtnType == TFAlertButtonTypeSingleConfirm) {
make.width.mas_equalTo(CGFLOAT_MIN);
} else {
make.width.mas_equalTo(self.alertViewBtnWidth);
}
}];
[self.confirmButton mas_makeConstraints:^(MASConstraintMaker *make) {
make.right.mas_equalTo(self.alertBackView.mas_right).with.offset(0);
make.top.mas_equalTo(self.contentScrollView.mas_bottom).with.offset(kMargin);
make.height.mas_equalTo(_alertViewBtnHeight);
if (self.alertBtnType == TFAlertButtonTypeSingleCancel) {
make.width.mas_equalTo(CGFLOAT_MIN);
} else {
make.width.mas_equalTo(self.alertViewBtnWidth);
}
}];
if (self.alertBtnType != TFAlertButtonTypeNone && self.showDivider) {
UIView *line = [[UIView alloc] init];
line.backgroundColor = kGrayLineColor;
[self.alertBackView addSubview:line];
[line mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(self.alertBackView.mas_left);
make.right.mas_equalTo(self.alertBackView.mas_right);
make.height.mas_equalTo(kCellLineHeight + 0.1f);
make.bottom.mas_equalTo(self.alertBackView.mas_bottom).with.offset(- _alertViewBtnHeight);
}];
if (self.alertBtnType == TFAlertButtonTypeDouble) {
UIView *line = [[UIView alloc] init];
line.backgroundColor = kGrayLineColor;
[self.alertBackView addSubview:line];
[line mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(self.confirmButton.mas_left);
make.top.mas_equalTo(self.confirmButton.mas_top);
make.height.mas_equalTo(self.confirmButton.mas_height);
make.width.mas_equalTo(kCellLineHeight + 0.1f);
}];
}
}
}
// 关闭弹框
- (void)closeAlertView
{
[self removeFromSuperview];
}
- (CGFloat)getContentLabelHeight
{
if (self.attributedStr.length > 0) {
return [TFViewHelper getDynamicHeightWithLabelFont:kMainFont labelWidth:SCREEN_WIDTH - 3 * kMargin labelText:self.attributedStr.string] + 2 * kMargin;
} else if (self.alertDetailContent.length > 0) {
return [TFViewHelper getDynamicHeightWithLabelFont:kMainFont labelWidth:SCREEN_WIDTH - 3 * kMargin labelText:self.alertDetailContent] + kMargin;
}
return CGFLOAT_MIN;
}
- (CGFloat)getContentScrollViewHeight
{
if ([self getContentLabelHeight] > (SCREEN_HEIGHT / 3)) {
[self.contentScrollView setContentSize:CGSizeMake(0, [self getContentLabelHeight])];
return SCREEN_HEIGHT / 3;
}
return [self getContentLabelHeight];
}
//- (NSString *)alertTitle
//{
// if (!_alertTitle) {
// return TFLocalizedString(@"提示");
// }
// return _alertTitle;
//}
- (void)setAlertTitle:(NSString *)alertTitle
{
_alertTitle = alertTitle;
self.alertTitleLabel.text = alertTitle;
}
- (void)setAlertDetailContent:(NSString *)alertDetailContent
{
_alertDetailContent = alertDetailContent;
self.contentLabel.text = alertDetailContent;
}
- (void)setAttributedStr:(NSMutableAttributedString *)attributedStr
{
_attributedStr = attributedStr;
self.contentLabel.attributedText = attributedStr;
}
- (void)setCancelTitle:(NSString *)cancelTitle
{
[self.cancelButton setTitle:cancelTitle forState:UIControlStateNormal];
}
- (void)setConfirmTitle:(NSString *)confirmTitle
{
[self.confirmButton setTitle:confirmTitle forState:UIControlStateNormal];
}
- (void)setAlertBtnType:(TFAlertButtonType)alertBtnType
{
_alertBtnType = alertBtnType;
switch (alertBtnType) {
case TFAlertButtonTypeNone:
self.alertViewBtnWidth = CGFLOAT_MIN;
_alertViewBtnHeight = CGFLOAT_MIN;
break;
case TFAlertButtonTypeSingleConfirm:
case TFAlertButtonTypeSingleCancel:
self.alertViewBtnWidth = self.alertViewWidth;
_alertViewBtnHeight = 44.0f;
break;
case TFAlertButtonTypeDouble:
self.alertViewBtnWidth = (self.alertViewWidth) / 2;
_alertViewBtnHeight = 44.0f;
break;
default:
break;
}
}
- (void)setAlertDisappearType:(TFAlertViewDisappearType)alertDisappearType
{
_alertDisappearType = alertDisappearType;
if (alertDisappearType != TFAlertViewDisappearTypeNormal) {
[self removeGestureRecognizer:self.dismissTapGesture];
}
}
#pragma mark - lazy
- (UIView *)alertBackView
{
if (!_alertBackView) {
_alertBackView = [[UIView alloc] init];
_alertBackView.backgroundColor = [UIColor whiteColor];
_alertBackView.layer.cornerRadius = 8;
[self addSubview:_alertBackView];
}
return _alertBackView;
}
- (UIButton *)closeButton
{
if (!_closeButton) {
_closeButton = [UIButton buttonWithType:UIButtonTypeCustom];
_closeButton.tintColor = kGrayTextLightColor;
[_closeButton setImageEdgeInsets:UIEdgeInsetsMake(15, 15, 15, 15)];
[_closeButton setImage:[[UIImage imageNamed:@"public_close"] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate] forState:UIControlStateNormal];
[_closeButton addTarget:self action:@selector(closeAlertView) forControlEvents:UIControlEventTouchUpInside];
}
return _closeButton;
}
- (UILabel *)alertTitleLabel
{
if (!_alertTitleLabel) {
_alertTitleLabel = [[UILabel alloc] init];
_alertTitleLabel.text = self.alertTitle;
_alertTitleLabel.backgroundColor = [UIColor clearColor];
_alertTitleLabel.font = kBoldFont16;
_alertTitleLabel.textColor = kBlackColor;
_alertTitleLabel.textAlignment = NSTextAlignmentCenter;
_alertTitleLabel.numberOfLines = 1;
}
return _alertTitleLabel;
}
- (UIScrollView *)contentScrollView
{
if (!_contentScrollView) {
_contentScrollView = [[UIScrollView alloc] init];
_contentScrollView.showsVerticalScrollIndicator = NO;
_contentScrollView.showsHorizontalScrollIndicator = NO;
}
return _contentScrollView;
}
- (YYLabel *)contentLabel
{
if (!_contentLabel) {
_contentLabel = [[YYLabel alloc] init];
_contentLabel.numberOfLines = 0;
_contentLabel.font = kMainFont;
_contentLabel.textColor = kGrayTextLightColor;
_contentLabel.textAlignment = NSTextAlignmentCenter;
}
return _contentLabel;
}
- (UIButton *)confirmButton
{
if (!_confirmButton) {
_confirmButton = [UIButton buttonWithType:UIButtonTypeCustom];
_confirmButton.backgroundColor = kWhiteColor;
_confirmButton.layer.cornerRadius = 8;
[_confirmButton.titleLabel setFont:kMainFont];
[_confirmButton setTitle:TFLocalizedString(@"确定") forState:UIControlStateNormal];
[_confirmButton setTitleColor:kMainColor forState:UIControlStateNormal];
[_confirmButton addTarget:self action:@selector(confirmButtonClick) forControlEvents:UIControlEventTouchUpInside];
}
return _confirmButton;
}
- (UIButton *)cancelButton
{
if (!_cancelButton) {
_cancelButton = [UIButton buttonWithType:UIButtonTypeCustom];
_cancelButton.backgroundColor = kWhiteColor;
_cancelButton.layer.cornerRadius = 8;
[_cancelButton.titleLabel setFont:kMainFont];
[_cancelButton setTitle:TFLocalizedString(@"取消") forState:UIControlStateNormal];
[_cancelButton setTitleColor:kBlackColor forState:UIControlStateNormal];
[_cancelButton addTarget:self action:@selector(cancelButtonClick) forControlEvents:UIControlEventTouchUpInside];
}
return _cancelButton;
}
#pragma mark - action
- (void)cancelButtonClick
{
if (self.cancelButtonClickBlock) {
self.cancelButtonClickBlock();
}
[self closeAlertView];
}
- (void)confirmButtonClick
{
if (self.confirmButtonClickBlock) {
self.confirmButtonClickBlock();
}
if (self.alertDisappearType == TFAlertViewDisappearTypeNever) {
return;
}
[self closeAlertView];
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
if ([touch.view isEqual:self.confirmButton] || [touch.view isEqual:self]) {
return YES;
} else {
return NO;
}
}
@end
@@ -0,0 +1,18 @@
//
// TFEvaluationAlertView.h
// TFReader
//
// Created by 谢腾飞 on 2020/12/17.
// Copyright © 2020 xtfei_2011@126.com. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "TFAlertView.h"
NS_ASSUME_NONNULL_BEGIN
@interface TFEvaluationAlertView : TFAlertView
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,102 @@
//
// TFEvaluationAlertView.m
// TFReader
//
// Created by 谢腾飞 on 2020/12/17.
// Copyright © 2020 xtfei_2011@126.com. All rights reserved.
//
#import "TFEvaluationAlertView.h"
@interface TFEvaluationAlertView ()
@property (nonatomic ,strong) UIImageView *topImageView;
@property (nonatomic ,strong) UIButton *rejectBtn;
@end
@implementation TFEvaluationAlertView
- (void)createSubviews
{
[super createSubviews];
[self addSubview:self.alertBackView];
self.showDivider = NO;
self.alertTitleLabel.font = kFont30;
self.alertTitleLabel.text = TFLocalizedString(@"应用好评");
self.alertDetailContent = TFLocalizedString(@"使用还满意么?满意请点个赞呗");
self.cancelTitle = TFLocalizedString(@"我要吐槽");
self.cancelButton.layer.borderColor = kColorRGBA(62, 120, 232, 1).CGColor;
self.cancelButton.layer.borderWidth = 0.5;
self.cancelButton.layer.cornerRadius = self.alertViewBtnHeight / 2;
self.cancelButton.backgroundColor = [UIColor whiteColor];
[self.cancelButton setTitleColor:kColorRGBA(62, 120, 232, 1) forState:UIControlStateNormal];
self.confirmTitle = TFLocalizedString(@"五星好评");
self.confirmButton.layer.cornerRadius = self.alertViewBtnHeight / 2;
self.confirmButton.backgroundColor = kColorRGBA(62, 120, 232, 1);
[self.confirmButton setTitleColor:kWhiteColor forState:UIControlStateNormal];
self.rejectBtn = [UIButton buttonWithType:UIButtonTypeCustom];
[self.rejectBtn setTitle:TFLocalizedString(@"残忍拒绝") forState:UIControlStateNormal];
[self.rejectBtn setTitleColor:kGrayTextLightColor forState:UIControlStateNormal];
[self.rejectBtn.titleLabel setFont:kMainFont];
[self.rejectBtn addTarget:self action:@selector(closeAlertView) forControlEvents:UIControlEventTouchUpInside];
[self.alertBackView addSubview:self.rejectBtn];
}
- (void)showAlertView
{
[super showAlertView];
self.topImageView = [[UIImageView alloc] init];
self.topImageView.image = [UIImage imageNamed:@"public_evaluation.png"];
self.topImageView.contentMode = UIViewContentModeScaleAspectFill;
[self.alertBackView addSubview:self.topImageView];
[self.topImageView mas_makeConstraints:^(MASConstraintMaker *make) {
make.width.mas_equalTo(self.alertBackView.mas_width).with.multipliedBy(0.8);
make.height.mas_equalTo(self.alertBackView.mas_width).with.multipliedBy(0.5);
make.centerX.mas_equalTo(self.alertBackView.mas_centerX);
make.bottom.mas_equalTo(self.alertBackView.mas_top).with.offset(2 * kMargin);
}];
[self.alertTitleLabel mas_updateConstraints:^(MASConstraintMaker *make) {
make.top.mas_equalTo(self.alertBackView.mas_top).with.offset(3 * kMargin);
make.height.mas_equalTo(self.alertTitleLabel.intrinsicContentSize.height);
}];
[self.confirmButton mas_remakeConstraints:^(MASConstraintMaker *make) {
make.top.mas_equalTo(self.contentScrollView.mas_bottom).with.offset(kMargin);
make.centerX.mas_equalTo(self.alertTitleLabel.mas_centerX);
make.width.mas_equalTo(self.alertBackView.mas_width).multipliedBy(0.5);
make.height.mas_equalTo(self.alertViewBtnHeight);
}];
[self.cancelButton mas_remakeConstraints:^(MASConstraintMaker *make) {
make.centerX.mas_equalTo(self.confirmButton.mas_centerX);
make.top.mas_equalTo(self.confirmButton.mas_bottom).with.offset(kHalfMargin);
make.width.mas_equalTo(self.confirmButton.mas_width);
make.height.mas_equalTo(self.confirmButton.mas_height);
}];
[self.rejectBtn mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerX.mas_equalTo(self.confirmButton.mas_centerX);
make.top.mas_equalTo(self.cancelButton.mas_bottom).with.offset(5);
make.width.mas_equalTo(self.confirmButton.mas_width);
make.height.mas_equalTo(self.confirmButton.mas_height);
}];
[self.alertBackView mas_remakeConstraints:^(MASConstraintMaker *make) {
make.width.mas_equalTo(self.alertViewWidth);
make.bottom.mas_equalTo(self.rejectBtn.mas_bottom).with.offset(kHalfMargin);
make.centerX.mas_equalTo(self.mas_centerX);
make.centerY.mas_equalTo(self.mas_centerY);
}];
}
@end
@@ -0,0 +1,20 @@
//
// TFGiftAlertView.h
// TFReader
//
// Created by 谢腾飞 on 2020/12/17.
// Copyright © 2020 xtfei_2011@126.com. All rights reserved.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@class WXYZ_TickectAlertModel, WXYZ_GiftMonthlyPassListModel;
@interface TFGiftAlertView : TFAlertView
- (void)setAlertModel:(WXYZ_TickectAlertModel *)alertModel giftModel:(WXYZ_GiftMonthlyPassListModel *)giftModel production_id:(NSInteger)production_id ticketBlock:(void(^)(NSInteger number))ticketBlock;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,97 @@
//
// TFGiftAlertView.m
// TFReader
//
// Created by 谢腾飞 on 2020/12/17.
// Copyright © 2020 xtfei_2011@126.com. All rights reserved.
//
#import "TFGiftAlertView.h"
#import "WXYZ_TickectAlertModel.h"
#import "WXYZ_GiftMonthlyPassModel.h"
#import "TFReaderBookManager.h"
#import "TFRechargeViewController.h"
@implementation TFGiftAlertView
- (void)createSubviews
{
[super createSubviews];
self.alertBtnType = TFAlertButtonTypeSingleConfirm;
}
- (void)setAlertModel:(WXYZ_TickectAlertModel *)alertModel giftModel:(WXYZ_GiftMonthlyPassListModel *)giftModel production_id:(NSInteger)production_id ticketBlock:(void(^)(NSInteger number))ticketBlock
{
self.alertTitle = alertModel.title ? : @"";
NSString *str = [alertModel.desc componentsJoinedByString:@"\n"];
NSRange range = [str rangeOfString:@"###.*###" options:NSRegularExpressionSearch];
if (range.length == 0) range = NSMakeRange(0, 0);
NSString *suffix = [[str substringWithRange:range] stringByReplacingOccurrencesOfString:@"#" withString:@""];
NSMutableString *prefix = [NSMutableString stringWithString:[str stringByReplacingCharactersInRange:range withString:@""]];
NSMutableParagraphStyle *style = [[NSMutableParagraphStyle alloc] init];
style.lineSpacing = 9.0;
[prefix insertString:suffix atIndex:range.location];
NSMutableAttributedString *t_atr = [[NSMutableAttributedString alloc] initWithString:prefix attributes:@{NSFontAttributeName : kFont13, NSForegroundColorAttributeName : kGrayTextColor, NSParagraphStyleAttributeName : style}];
[t_atr addAttributes:@{NSForegroundColorAttributeName : kMainColor} range:NSMakeRange(range.location, suffix.length)];
t_atr.alignment = NSTextAlignmentCenter;
self.attributedStr = t_atr;
self.confirmTitle = alertModel.items.firstObject.title ?: @"";
[self.contentScrollView mas_remakeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.alertTitleLabel.mas_bottom).offset(0);
make.left.equalTo(self.alertBackView).offset(kHalfMargin);
make.right.equalTo(self.alertBackView).offset(-kHalfMargin);
make.height.mas_equalTo([TFViewHelper getDynamicHeightWithLabelFont:kMainFont labelWidth:SCREEN_WIDTH - 3 * kMargin labelText:self.attributedStr.string] + kLabelHeight);
}];
self.contentScrollView.scrollEnabled = NO;
[self.alertBackView mas_remakeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self.alertBackView.superview).offset(kMargin * 2);
make.right.equalTo(self.alertBackView.superview).offset(-kMargin * 2);
make.bottom.mas_equalTo(self.confirmButton.mas_bottom).with.offset(0).priorityLow();
make.center.equalTo(self.alertBackView.superview);
}];
[self.contentLabel mas_remakeConstraints:^(MASConstraintMaker *make) {
make.top.left.right.bottom.equalTo(self.contentScrollView);
}];
[self.confirmButton mas_remakeConstraints:^(MASConstraintMaker *make) {
make.right.mas_equalTo(self.alertBackView.mas_right).with.offset(0);
make.top.mas_equalTo(self.contentLabel.mas_bottom).offset(kHalfMargin);
make.height.mas_equalTo(45.0);
make.width.equalTo(self.alertBackView);
}];
self.confirmButtonClickBlock = ^{
if ([alertModel.items.firstObject.action isEqualToString:@"recharge"]) {
[[NSNotificationCenter defaultCenter] postNotificationName:NSNotification_Reader_Push object:@""];
[[TFViewHelper getCurrentNavigationController] pushViewController:[[TFRechargeViewController alloc] init] animated:YES];
} else {
NSDictionary *params = @{
@"book_id" : @(production_id),
@"chapter_id" : @([TFReaderBookManager sharedManager].chapter_id),
@"num" : @(giftModel.num),
@"use_gold" : @"1"
};
[TFNetworkTools POST:Book_Reward_Ticket_Vote parameters:params model:nil success:^(BOOL isSuccess, NSDictionary * _Nullable t_model, TFNetworkRequestModel * _Nonnull requestModel) {
if (isSuccess) {
NSString *text = [NSString stringWithFormat:@"%@", t_model[@"data"][@"ticket_num"]];
[[NSNotificationCenter defaultCenter] postNotificationName:@"changeTicket" object:text];
[TFPromptManager showPromptViewWithStatus:TFPromptStatusSuccess promptTitle:TFLocalizedString(@"投票成功")];
[TFReaderBookManager sharedManager].ticket_num = text ?: @"";
!ticketBlock ?: ticketBlock([text integerValue]);
} else {
[TFPromptManager showPromptViewWithStatus:TFPromptStatusError promptTitle:requestModel.msg];
}
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
[TFPromptManager showPromptWithError:error defaultText:nil];
}];
}
};
}
@end
@@ -0,0 +1,20 @@
//
// TFSignAlertView.h
// TFReader
//
// Created by 谢腾飞 on 2020/12/17.
// Copyright © 2020 xtfei_2011@126.com. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "TFAlertView.h"
NS_ASSUME_NONNULL_BEGIN
@interface TFSignAlertView : TFAlertView
@property (nonatomic ,strong) NSArray *bookList;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,200 @@
//
// TFSignAlertView.m
// TFReader
//
// Created by 谢腾飞 on 2020/12/17.
// Copyright © 2020 xtfei_2011@126.com. All rights reserved.
//
#import "TFSignAlertView.h"
#import "TFCollectionManager.h"
@interface TFSignAlertView ()
@property (nonatomic ,strong) UIView *bookBackView;
@end
@implementation TFSignAlertView
- (void)createSubviews
{
[super createSubviews];
self.bookBackView = [[UIView alloc] init];
self.bookBackView.backgroundColor = [UIColor clearColor];
[self.alertBackView addSubview:self.bookBackView];
self.cancelTitle = TFLocalizedString(@"不用了");
self.confirmTitle = TFLocalizedString(@"全部加入书架");
WS(weakSelf)
self.confirmButtonClickBlock = ^{
[weakSelf addBooks];
};
}
- (void)showAlertView
{
if (_bookList.count == 0) {
[self closeAlertView];
return;
}
[super showAlertView];
CGFloat bookWidth = (self.alertViewWidth - 2 * kMargin - 2 * kHalfMargin) / 3;
CGFloat bookHeigh = kGeometricHeight(bookWidth, 3, 4);
[self.contentLabel mas_updateConstraints:^(MASConstraintMaker *make) {
make.height.mas_equalTo(30);
}];
[self.bookBackView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(kMargin);
make.top.mas_equalTo(self.contentLabel.mas_bottom).with.offset(kMargin);
make.width.mas_equalTo(self.alertBackView.mas_width).with.offset(- 2 * kMargin);
make.height.mas_equalTo(bookHeigh + 40);
}];
[self.cancelButton mas_remakeConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(self.alertBackView.mas_left).with.offset(0);
make.top.mas_equalTo(self.bookBackView.mas_bottom).with.offset(kMargin);
make.height.mas_equalTo(self.alertViewBtnHeight);
make.width.mas_equalTo(self.alertViewWidth / 2);
}];
[self.confirmButton mas_remakeConstraints:^(MASConstraintMaker *make) {
make.right.mas_equalTo(self.alertBackView.mas_right).with.offset(0);
make.top.mas_equalTo(self.bookBackView.mas_bottom).with.offset(kMargin);
make.height.mas_equalTo(self.alertViewBtnHeight);
make.width.mas_equalTo(self.alertViewWidth / 2);
}];
}
- (void)setBookList:(NSArray *)bookList
{
_bookList = bookList;
if (kObjectIsEmpty(bookList)) return;
CGFloat bookWidth = (self.alertViewWidth - (2 * kMargin) - (2 * kHalfMargin)) / 3.0;
CGFloat bookHeigh = kGeometricHeight(bookWidth, 3, 4);
int buttonNum = 3;//每行多少按钮
CGFloat button_W = bookWidth;//按钮宽
CGFloat space_X = kHalfMargin;//按钮间距
UIView *backView = [[UIView alloc] init];
backView.backgroundColor = [UIColor clearColor];
[self.bookBackView addSubview:backView];
[backView mas_makeConstraints:^(MASConstraintMaker *make) {
make.center.equalTo(self.bookBackView);
}];
int max = (int)(bookList.count < 3 ? bookList.count : 3);
for (int i = 0; i < max; i ++) {
int loc = i % buttonNum;//列号
CGFloat button_X = (space_X + button_W) * loc;
TFProductionModel *t_model = [bookList objectOrNilAtIndex:i];
// 图片
TFProductionCoverView *bookImageView = [[TFProductionCoverView alloc] initWithProductionType:t_model.productionType coverDirection:TFProductionCoverDirectionVertical];
bookImageView.userInteractionEnabled = YES;
bookImageView.coverImageUrl = t_model.cover;
[backView addSubview:bookImageView];
[bookImageView mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.mas_equalTo(0);
make.left.mas_equalTo(button_X);
make.width.mas_equalTo(bookWidth);
make.height.mas_equalTo(bookHeigh);
if (i == max - 1) {
make.right.equalTo(backView);
}
}];
// 书名
UILabel *titleLabel = [[UILabel alloc] init];
titleLabel.numberOfLines = 1;
titleLabel.text = [NSString stringWithFormat:@"%@\n", t_model.name];
titleLabel.backgroundColor = kWhiteColor;
titleLabel.font = kFont12;
titleLabel.textAlignment = NSTextAlignmentLeft;
[backView addSubview:titleLabel];
[titleLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerX.mas_equalTo(bookImageView.mas_centerX);
make.top.mas_equalTo(bookImageView.mas_bottom);
make.width.mas_equalTo(bookImageView.mas_width);
make.height.mas_equalTo(20);
make.bottom.equalTo(backView);
}];
UILabel *connerLabel = [[UILabel alloc] init];
connerLabel.font = kFont8;
connerLabel.layer.cornerRadius = 4.0f;
connerLabel.textAlignment = NSTextAlignmentCenter;
connerLabel.textColor = kWhiteColor;
connerLabel.clipsToBounds = YES;
[bookImageView addSubview:connerLabel];
[connerLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.right.mas_equalTo(bookImageView.mas_right).with.offset(- kQuarterMargin);
make.top.mas_equalTo(bookImageView.mas_top).with.offset(kQuarterMargin);
make.width.mas_equalTo(30);
make.height.mas_equalTo(15);
}];
if (t_model.productionType == TFProductionTypeNovel) {
connerLabel.backgroundColor = kMainColor;
connerLabel.text = TFLocalizedString(@"小说");
[connerLabel mas_updateConstraints:^(MASConstraintMaker *make) {
make.width.mas_equalTo([TFViewHelper getDynamicWidthWithLabel:connerLabel]);
}];
}
if (t_model.productionType == TFProductionTypeComic) {
connerLabel.backgroundColor = kRedColor;
connerLabel.text = TFLocalizedString(@"漫画");
[connerLabel mas_updateConstraints:^(MASConstraintMaker *make) {
make.width.mas_equalTo([TFViewHelper getDynamicWidthWithLabel:connerLabel]);
}];
}
if (t_model.productionType == TFProductionTypeAudio) {
connerLabel.backgroundColor = [UIColor colorWithHexString:@"#56a0ef"];
connerLabel.text = TFLocalizedString(@"听书");
[connerLabel mas_updateConstraints:^(MASConstraintMaker *make) {
make.width.mas_equalTo([TFViewHelper getDynamicWidthWithLabel:connerLabel]);
}];
}
}
}
- (void)addBooks
{
for (TFProductionModel *t_model in self.bookList) {
switch (t_model.productionType) {
case TFProductionTypeNovel:
[[TFCollectionManager shareManagerWithProductionType:TFProductionTypeNovel] addCollectionWithProductionModel:t_model atIndex:0];
break;
case TFProductionTypeComic:
[[TFCollectionManager shareManagerWithProductionType:TFProductionTypeComic] addCollectionWithProductionModel:t_model];
break;
case TFProductionTypeAudio:
[[TFCollectionManager shareManagerWithProductionType:TFProductionTypeAudio] addCollectionWithProductionModel:t_model];
break;
default:
break;
}
}
if (self.bookList) {
[[NSNotificationCenter defaultCenter] postNotificationName:Notification_Reload_Rack_Production object:nil];
}
}
@end
@@ -0,0 +1,22 @@
//
// TFTextFieldAlertView.h
// TFReader
//
// Created by 谢腾飞 on 2020/12/17.
// Copyright © 2020 xtfei_2011@126.com. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "TFAlertView.h"
NS_ASSUME_NONNULL_BEGIN
@interface TFTextFieldAlertView : TFAlertView
@property (nonatomic ,copy) void(^endEditedBlock)(NSString *inputText);
@property (nonatomic ,copy) NSString *placeHoldTitle;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,107 @@
//
// TFTextFieldAlertView.m
// TFReader
//
// Created by 谢腾飞 on 2020/12/17.
// Copyright © 2020 xtfei_2011@126.com. All rights reserved.
//
#import "TFTextFieldAlertView.h"
#import "TFKeyboardManager.h"
@interface TFTextFieldAlertView ()
@property (nonatomic ,strong) UITextField *textField;
@property (nonatomic ,strong) TFKeyboardManager *keyboardManager;
@end
@implementation TFTextFieldAlertView
- (void)createSubviews
{
[super createSubviews];
self.textField = [[UITextField alloc] init];
self.textField.backgroundColor = [UIColor clearColor];
self.textField.font = kMainFont;
self.textField.textColor = [UIColor blackColor];
self.textField.textAlignment = NSTextAlignmentLeft;
self.textField.clearButtonMode = UITextFieldViewModeWhileEditing;
self.textField.layer.borderColor = kColorRGBA(235, 235, 241, 1).CGColor;
self.textField.layer.borderWidth = 1;
self.textField.layer.cornerRadius = 8;
self.textField.leftView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 20)];
self.textField.leftViewMode = UITextFieldViewModeAlways;
[self.alertBackView addSubview:self.textField];
[self.confirmButton addTarget:self action:@selector(confirmButtonClick) forControlEvents:UIControlEventTouchUpInside];
WS(weakSelf)
self.keyboardManager = [[TFKeyboardManager alloc] initObserverWithAdaptiveMovementView:self.alertBackView];
self.keyboardManager.spacingFromKeyboard = 80;
self.keyboardManager.keyboardHeightChanged = ^(CGFloat keyboardHeight, CGFloat shouldMoveDistance, CGRect shouldMoveFrame) {
weakSelf.alertBackView.frame = shouldMoveFrame;
};
}
- (void)showAlertView
{
[super showAlertView];
[self.textField mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(self.alertTitleLabel.mas_left);
make.right.mas_equalTo(self.alertTitleLabel.mas_right);
make.top.mas_equalTo(self.contentScrollView.mas_bottom).with.offset(kHalfMargin);
make.height.mas_equalTo(self.alertViewBtnHeight);
}];
[self.cancelButton mas_remakeConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(self.alertBackView.mas_left).with.offset(0);
make.top.mas_equalTo(self.textField.mas_bottom).with.offset(kMargin);
make.height.mas_equalTo(self.alertViewBtnHeight);
make.width.mas_equalTo(self.alertViewWidth / 2);
}];
[self.confirmButton mas_remakeConstraints:^(MASConstraintMaker *make) {
make.right.mas_equalTo(self.alertBackView.mas_right).with.offset(0);
make.top.mas_equalTo(self.textField.mas_bottom).with.offset(kMargin);
make.height.mas_equalTo(self.alertViewBtnHeight);
make.width.mas_equalTo(self.alertViewWidth / 2);
}];
}
- (void)setPlaceHoldTitle:(NSString *)placeHoldTitle
{
_placeHoldTitle = placeHoldTitle;
self.textField.text = placeHoldTitle;
}
- (void)confirmButtonClick
{
self.textField.text = [self.textField.text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
if (self.textField.text.length == 0) {
[TFPromptManager showPromptViewWithStatus:TFPromptStatusError promptTitle:TFLocalizedString(@"昵称不能为空")];
return;
}
if ([self.placeHoldTitle isEqualToString:self.textField.text]) {
[TFPromptManager showPromptViewWithStatus:TFPromptStatusError promptTitle:TFLocalizedString(@"昵称没有变化哦")];
return;
}
if (self.endEditedBlock) {
self.endEditedBlock(self.textField.text);
}
[self closeAlertView];
}
- (void)dealloc
{
[self.keyboardManager stopKeyboardObserver];
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
@end
@@ -0,0 +1,20 @@
//
// TFUpdateAlertView.h
// TFReader
//
// Created by 谢腾飞 on 2020/12/17.
// Copyright © 2020 xtfei_2011@126.com. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "TFAlertView.h"
NS_ASSUME_NONNULL_BEGIN
@interface TFUpdateAlertView : TFAlertView
@property (nonatomic ,copy) NSString *updateMessage;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,107 @@
//
// TFUpdateAlertView.m
// TFReader
//
// Created by 谢腾飞 on 2020/12/17.
// Copyright © 2020 xtfei_2011@126.com. All rights reserved.
//
#import "TFUpdateAlertView.h"
@interface TFUpdateAlertView ()
@property (nonatomic ,strong) UIImageView *topImageView;
@end
@implementation TFUpdateAlertView
- (void)createSubviews
{
[super createSubviews];
self.closeButton.hidden = YES;
self.showDivider = NO;
self.contentScrollView.showsVerticalScrollIndicator = YES;
[self addSubview:self.alertBackView];
self.topImageView = [[UIImageView alloc] init];
self.topImageView.image = [UIImage imageNamed:TFLocalizedString(@"alert_update_top")];
[self.alertBackView addSubview:self.topImageView];
[self.alertBackView sendSubviewToBack:self.topImageView];
self.alertTitle = @"";
self.contentLabel.textAlignment = NSTextAlignmentLeft;
self.cancelButton.backgroundColor = [UIColor clearColor];
[self.cancelButton setTitleColor:kColorRGBA(62, 120, 232, 1) forState:UIControlStateNormal];
self.cancelTitle = TFLocalizedString(@"再等等");
self.confirmButton.layer.cornerRadius = self.alertViewBtnHeight / 2;
self.confirmButton.backgroundColor = kColorRGBA(62, 120, 232, 1);
[self.confirmButton setTitleColor:kWhiteColor forState:UIControlStateNormal];
[self.alertBackView addSubview:self.confirmButton];
self.confirmTitle = TFLocalizedString(@"去更新");
}
- (void)showAlertView
{
[super showAlertView];
[self.alertBackView mas_updateConstraints:^(MASConstraintMaker *make) {
make.bottom.mas_equalTo(self.confirmButton.mas_bottom).with.offset(kMargin);
make.centerY.mas_equalTo(self.mas_centerY).with.offset(self.alertViewWidth * 0.12);
}];
[self.topImageView mas_makeConstraints:^(MASConstraintMaker *make) {
make.width.mas_equalTo(self.alertBackView.mas_width);
make.height.mas_equalTo(self.alertBackView.mas_width).with.multipliedBy(0.4788);
make.centerX.mas_equalTo(self.alertBackView.mas_centerX);
make.bottom.mas_equalTo(self.alertBackView.mas_top).with.offset(kMargin);
}];
CGFloat labelHeight = [TFViewHelper getDynamicHeightWithLabelFont:kMainFont labelWidth:SCREEN_WIDTH - 3 * kMargin labelText:self.alertDetailContent] + kMargin;
if (labelHeight < self.alertViewWidth * 0.24) {
labelHeight = self.alertViewWidth * 0.24;
[self.contentLabel mas_updateConstraints:^(MASConstraintMaker *make) {
make.height.mas_equalTo(labelHeight);
}];
} else if (labelHeight > SCREEN_HEIGHT / 3) {
labelHeight = SCREEN_HEIGHT / 3;
}
[self.contentScrollView mas_updateConstraints:^(MASConstraintMaker *make) {
make.height.mas_equalTo(labelHeight);
}];
[self.cancelButton mas_updateConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(self.alertBackView.mas_left).with.offset(kMargin);
if (self.alertBtnType == TFAlertButtonTypeSingleConfirm) {
make.width.mas_equalTo(CGFLOAT_MIN);
} else {
make.width.mas_equalTo(self.alertViewWidth / 2 - kMargin - kHalfMargin);
}
}];
[self.confirmButton mas_updateConstraints:^(MASConstraintMaker *make) {
make.right.mas_equalTo(self.alertBackView.mas_right).with.offset(- kMargin);
if (self.alertBtnType == TFAlertButtonTypeSingleCancel) {
make.width.mas_equalTo(CGFLOAT_MIN);
} else if (self.alertBtnType == TFAlertButtonTypeSingleConfirm) {
make.width.mas_equalTo(self.alertViewWidth - 2 * kMargin);
} else {
make.width.mas_equalTo(self.alertViewWidth / 2 - kMargin - kHalfMargin);
}
}];
}
- (void)setUpdateMessage:(NSString *)updateMessage
{
_updateMessage = updateMessage;
self.alertDetailContent = updateMessage;
}
@end
@@ -0,0 +1,98 @@
//
// TFButton.h
// TFReader
//
// Created by 谢腾飞 on 2020/12/16.
// Copyright © 2020 xtfei_2011@126.com. All rights reserved.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
typedef NS_ENUM(NSUInteger, TFButtonIndicator) {
TFButtonIndicatorTitleLeft, // 文字左 图片右
TFButtonIndicatorTitleRight, // 文字右 图片左
TFButtonIndicatorTitleTop, // 文字上 图片下
TFButtonIndicatorTitleBottom, // 文字下 图片上
TFButtonIndicatorImageLeftBothLeft, // 图片左 文字右 (同时靠左)
TFButtonIndicatorImageLeftBothRight, // 图片左 文字右 (同时靠右)
TFButtonIndicatorImageRightBothLeft, // 文字左 图片右 (同时靠左)
TFButtonIndicatorImageRightBothRight // 文字左 图片右 (同时靠右)
};
@interface TFButton : UIButton
// 主标题
@property (nonatomic ,copy) NSString *buttonTitle;
// 子标题
@property (nonatomic ,copy) NSString *buttonSubTitle;
// 主标题字号
@property (nonatomic ,strong) UIFont *buttonTitleFont;
// 子标题字号
@property (nonatomic ,strong) UIFont *buttonSubTitleFont;
// 主标题颜色
@property (nonatomic ,strong) UIColor *buttonTitleColor;
// 子标题颜色
@property (nonatomic ,strong) UIColor *buttonSubTitleColor;
// 整体颜色 (与buttonTitleColor && buttonSubTitleColor 互斥)
@property (nonatomic ,strong) UIColor *buttonTintColor;
// 按钮图片名称
@property (nonatomic ,copy) NSString *buttonImageName;
// 图片宽度
@property (nonatomic ,assign) NSInteger buttonImageViewWidth;
// 图片高度
@property (nonatomic ,assign) NSInteger buttonImageViewHeight;
// 图片缩放 默认0.7 最大为1.0
@property (nonatomic ,assign) CGFloat buttonImageScale;
// 图文间距 默认 10
@property (nonatomic ,assign) NSInteger graphicDistance;
// 整体偏移量 正数为整体向右偏移 负数为整体向左偏移
@property (nonatomic ,assign) CGFloat horizontalMigration;
// 边距 默认0
@property (nonatomic ,assign) CGFloat buttonMargin;
// 角标背景颜色
@property (nonatomic ,strong) UIColor *cornerBackColor;
// 角标文字颜色
@property (nonatomic ,strong) UIColor *cornerTextColor;
// 角标字号
@property (nonatomic ,strong) UIFont *cornerTextFont;
// 角标文字
@property (nonatomic ,copy) NSString *cornerTitle;
// 是否翻转图片
@property (nonatomic ,assign) BOOL transformImageView;
// 主标题显示行数 默认一行
@property (nonatomic ,assign) NSInteger buttonTitleNumberOfLines;
// 按钮标识
@property (nonatomic ,copy) NSString *buttonTag;
- (instancetype)initWithButtonTitle:(NSString *)buttonTitle buttonImageName:(NSString *)buttonImageName buttonIndicator:(TFButtonIndicator)buttonIndicatior;
- (instancetype)initWithFrame:(CGRect)frame buttonTitle:(NSString *)buttonTitle buttonImageName:(NSString *)buttonImageName buttonIndicator:(TFButtonIndicator)buttonIndicatior;
- (instancetype)initWithFrame:(CGRect)frame buttonTitle:(NSString *)buttonTitle buttonImageName:(NSString *)buttonImageName buttonIndicator:(TFButtonIndicator)buttonIndicatior showMaskView:(BOOL)showMaskView;
- (instancetype)initWithFrame:(CGRect)frame buttonTitle:(NSString *)buttonTitle buttonSubTitle:(NSString *)buttonSubTitle buttonImageName:(NSString *)buttonImageName buttonIndicator:(TFButtonIndicator)buttonIndicatior showMaskView:(BOOL)showMaskView;
// 撤销遮盖层
- (void)undoMaskView;
// 图片 开始旋转
- (void)startSpin;
// 图片 停止旋转
- (void)stopSpin;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,747 @@
//
// TFButton.m
// TFReader
//
// Created by 谢腾飞 on 2020/12/16.
// Copyright © 2020 xtfei_2011@126.com. All rights reserved.
//
#import "TFButton.h"
@interface TFButton ()
{
BOOL _showMaskView;
TFButtonIndicator _buttonIndicatior;
}
@property (nonatomic ,strong) UIView *bottomHoldView;
@property (nonatomic ,strong) UIImageView *buttonImageView;
@property (nonatomic ,strong) UILabel *buttonTitleLabel;
@property (nonatomic ,strong) UILabel *buttonSubTitleLabel;
@property (nonatomic ,strong) UILabel *cornerMarkLabel;
@property (nonatomic ,strong) UIView *maskView;
@end
@implementation TFButton
- (instancetype)initWithButtonTitle:(NSString *)buttonTitle buttonImageName:(NSString *)buttonImageName buttonIndicator:(TFButtonIndicator)buttonIndicatior
{
return [self initWithFrame:CGRectZero buttonTitle:buttonTitle buttonSubTitle:@"" buttonImageName:buttonImageName buttonIndicator:buttonIndicatior showMaskView:NO];
}
- (instancetype)initWithFrame:(CGRect)frame buttonTitle:(NSString *)buttonTitle buttonImageName:(NSString *)buttonImageName buttonIndicator:(TFButtonIndicator)buttonIndicatior
{
return [self initWithFrame:frame buttonTitle:buttonTitle buttonSubTitle:@"" buttonImageName:buttonImageName buttonIndicator:buttonIndicatior showMaskView:NO];
}
- (instancetype)initWithFrame:(CGRect)frame buttonTitle:(NSString *)buttonTitle buttonImageName:(NSString *)buttonImageName buttonIndicator:(TFButtonIndicator)buttonIndicatior showMaskView:(BOOL)showMaskView
{
return [self initWithFrame:frame buttonTitle:buttonTitle buttonSubTitle:@"" buttonImageName:buttonImageName buttonIndicator:buttonIndicatior showMaskView:showMaskView];
}
- (instancetype)initWithFrame:(CGRect)frame buttonTitle:(NSString *)buttonTitle buttonSubTitle:(NSString *)buttonSubTitle buttonImageName:(NSString *)buttonImageName buttonIndicator:(TFButtonIndicator)buttonIndicatior showMaskView:(BOOL)showMaskView
{
if (self = [super initWithFrame:frame]) {
self.graphicDistance = 10;
self.buttonImageScale = 0.7;
self.horizontalMigration = 0;
self.buttonTitle = buttonTitle;
self.buttonSubTitle = buttonSubTitle;
self.buttonImageName = buttonImageName;
_showMaskView = showMaskView;
_buttonIndicatior = buttonIndicatior;
[self createSubviews];
}
return self;
}
- (void)createSubviews
{
[self addSubview:self.bottomHoldView];
self.buttonTitleLabel.text = _buttonTitle;
[self.bottomHoldView addSubview:self.buttonTitleLabel];
if (_buttonImageName.length > 0) {
if ([_buttonImageName hasPrefix:@"http"]) {
[self.buttonImageView setImageWithURL:[NSURL URLWithString:_buttonImageName] placeholder:HoldImage options:YYWebImageOptionSetImageWithFadeAnimation completion:nil];
} else {
self.buttonImageView.image = [[UIImage imageNamed:_buttonImageName] imageWithRenderingMode:UIImageRenderingModeAutomatic];
}
[self.bottomHoldView addSubview:self.buttonImageView];
}
self.cornerMarkLabel.hidden = YES;
[self.buttonImageView addSubview:self.cornerMarkLabel];
[self.cornerMarkLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerX.mas_equalTo(self.buttonImageView.mas_right);
make.centerY.mas_equalTo(self.buttonImageView.mas_top);
make.height.mas_equalTo(CGFLOAT_MIN);
make.width.mas_equalTo(CGFLOAT_MIN);
}];
if (_buttonSubTitle.length > 0) {
self.buttonSubTitleLabel.text = _buttonSubTitle;
[self.bottomHoldView addSubview:self.buttonSubTitleLabel];
}
if (_showMaskView) {
[self addSubview:self.maskView];
}
}
- (void)layoutSubviews
{
[super layoutSubviews];
switch (_buttonIndicatior) {
case TFButtonIndicatorTitleLeft: // 文字左 图片右
[self createCustomButtonIndicatorTitleLeft];
break;
case TFButtonIndicatorTitleRight: // 文字右 图片左
[self createCustomButtonIndicatorTitleRight];
break;
case TFButtonIndicatorTitleTop: // 文字上 图片下
[self createCustomButtonIndicatorTitleTop];
break;
case TFButtonIndicatorTitleBottom: // 文字下 图片上
[self createCustomButtonIndicatorTitleBottom];
break;
case TFButtonIndicatorImageLeftBothLeft: // 图片左 文字右 (同时靠左)
[self createCustomButtonIndicatorImageLeftBothLeft];
break;
case TFButtonIndicatorImageLeftBothRight: // 图片左 文字右 (同时靠右)
[self createCustomButtonIndicatorImageLeftBothRight];
break;
case TFButtonIndicatorImageRightBothLeft: // 文字左 图片右 (同时靠左)
[self createCustomButtonIndicatorImageRightBothLeft];
break;
case TFButtonIndicatorImageRightBothRight: // 文字左 图片右 (同时靠右)
[self createCustomButtonIndicatorImageRightBothRight];
break;
default:
break;
}
}
- (void)undoMaskView
{
self.maskView.hidden = YES;
[self.maskView removeAllSubviews];
[self.maskView removeFromSuperview];
self.maskView = nil;
}
// 图片边长
- (CGFloat)getButtonImageViewSideLength
{
if (self.height >= self.width) {
return self.width * self.buttonImageScale;
}
return self.height * self.buttonImageScale;
}
// 控件边距
- (CGFloat)getViewMargin
{
if (self.height >= self.width) {
return self.width * (1 - self.buttonImageScale);
}
return self.height * (1 - self.buttonImageScale);
}
// 文字左 图片右
- (void)createCustomButtonIndicatorTitleLeft
{
if (_buttonImageName.length > 0) {
[self.bottomHoldView mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerX.mas_equalTo(self.mas_centerX).with.offset(self.horizontalMigration);
make.centerY.mas_equalTo(self.mas_centerY);
make.height.mas_equalTo(self.mas_height);
make.left.mas_equalTo(self.buttonTitleLabel.mas_left);
make.right.mas_equalTo(self.buttonImageView.mas_right);
}];
[self.buttonImageView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(self.buttonTitleLabel.mas_right).with.offset(self.graphicDistance);
make.centerY.mas_equalTo(self.bottomHoldView.mas_centerY);
make.width.height.mas_equalTo([self getButtonImageViewSideLength]);
}];
self.buttonTitleLabel.textAlignment = NSTextAlignmentRight;
[self.buttonTitleLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.mas_equalTo(0);
make.left.mas_equalTo(0);
make.width.mas_equalTo([TFViewHelper getDynamicWidthWithLabel:self.buttonTitleLabel] > self.width?self.width:[TFViewHelper getDynamicWidthWithLabel:self.buttonTitleLabel]);
make.height.mas_equalTo(self.mas_height);
}];
} else {
[self.bottomHoldView mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerX.mas_equalTo(self.mas_centerX).with.offset(self.horizontalMigration);
make.centerY.mas_equalTo(self.mas_centerY);
make.height.mas_equalTo(self.mas_height);
make.left.mas_equalTo(self.buttonTitleLabel.mas_left);
make.right.mas_equalTo(self.buttonTitleLabel.mas_right);
}];
[self.buttonTitleLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.mas_equalTo(0);
make.centerX.mas_equalTo(self.mas_centerX);
make.width.mas_equalTo([TFViewHelper getDynamicWidthWithLabel:self.buttonTitleLabel] > self.width?self.width:[TFViewHelper getDynamicWidthWithLabel:self.buttonTitleLabel]);
make.height.mas_equalTo(self.mas_height);
}];
}
if (_buttonSubTitle.length > 0) {
self.buttonSubTitleLabel.text = _buttonSubTitle;
self.buttonSubTitleLabel.textAlignment = NSTextAlignmentRight;
[self.buttonSubTitleLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(self.buttonTitleLabel.mas_left);
make.bottom.mas_equalTo(self.bottomHoldView.mas_bottom);
make.width.mas_equalTo(self.buttonTitleLabel.mas_width);
make.height.mas_equalTo(self.mas_height).multipliedBy(0.4);
}];
[self.buttonTitleLabel mas_updateConstraints:^(MASConstraintMaker *make) {
make.height.mas_equalTo(self.mas_height).multipliedBy(0.6);
}];
}
}
// 文字右 图片左
- (void)createCustomButtonIndicatorTitleRight
{
if (_buttonImageName.length > 0) {
[self.bottomHoldView mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerX.mas_equalTo(self.mas_centerX).with.offset(self.horizontalMigration);
make.centerY.mas_equalTo(self.mas_centerY);
make.height.mas_equalTo(self.mas_height);
make.left.mas_equalTo(self.buttonImageView.mas_left);
make.right.mas_equalTo(self.buttonTitleLabel.mas_right);
}];
[self.buttonImageView mas_remakeConstraints:^(MASConstraintMaker *make) {
make.right.mas_equalTo(self.buttonTitleLabel.mas_left).with.offset(- self.graphicDistance);
make.centerY.mas_equalTo(self.bottomHoldView.mas_centerY);
make.width.height.mas_equalTo([self getButtonImageViewSideLength]);
}];
self.buttonTitleLabel.textAlignment = NSTextAlignmentRight;
[self.buttonTitleLabel mas_remakeConstraints:^(MASConstraintMaker *make) {
make.top.mas_equalTo(0);
make.right.mas_equalTo(self.bottomHoldView.mas_right);
make.width.mas_equalTo([TFViewHelper getDynamicWidthWithLabel:self.buttonTitleLabel] > self.width?self.width:[TFViewHelper getDynamicWidthWithLabel:self.buttonTitleLabel]);
make.height.mas_equalTo(self.mas_height).priorityMedium();
}];
} else {
[self.bottomHoldView mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerX.mas_equalTo(self.mas_centerX).with.offset(self.horizontalMigration);
make.centerY.mas_equalTo(self.mas_centerY);
make.height.mas_equalTo(self.mas_height);
make.left.mas_equalTo(self.buttonTitleLabel.mas_left);
make.right.mas_equalTo(self.buttonTitleLabel.mas_right);
}];
[self.buttonTitleLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.mas_equalTo(0);
make.centerX.mas_equalTo(self.mas_centerX);
make.width.mas_equalTo([TFViewHelper getDynamicWidthWithLabel:self.buttonTitleLabel] > self.width?self.width:[TFViewHelper getDynamicWidthWithLabel:self.buttonTitleLabel]);
make.height.mas_equalTo(self.mas_height).priorityMedium();
}];
}
if (_buttonSubTitle.length > 0) {
self.buttonSubTitleLabel.text = _buttonSubTitle;
self.buttonSubTitleLabel.textAlignment = NSTextAlignmentRight;
[self.buttonSubTitleLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(self.buttonTitleLabel.mas_left);
make.bottom.mas_equalTo(self.bottomHoldView.mas_bottom);
make.width.mas_equalTo([TFViewHelper getDynamicWidthWithLabel:self.buttonSubTitleLabel]);
make.height.mas_equalTo(self.mas_height).multipliedBy(0.4);
}];
[self.buttonTitleLabel mas_updateConstraints:^(MASConstraintMaker *make) {
make.height.mas_equalTo(self.mas_height).multipliedBy(0.6);
}];
}
}
// 文字上 图片下
- (void)createCustomButtonIndicatorTitleTop
{
[self.buttonTitleLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.mas_equalTo(0);
make.right.mas_equalTo(self.mas_right);
make.left.mas_equalTo(self.mas_left);
make.bottom.mas_equalTo(self.mas_bottom);
}];
if (_buttonImageName.length > 0) {
[self.buttonImageView mas_makeConstraints:^(MASConstraintMaker *make) {
make.right.mas_equalTo(self.buttonTitleLabel.mas_left);
make.centerY.mas_equalTo(self.mas_centerY);
make.width.height.mas_equalTo([self getButtonImageViewSideLength] / 2);
}];
[self.buttonTitleLabel mas_updateConstraints:^(MASConstraintMaker *make) {
make.bottom.mas_equalTo(self.buttonImageView.mas_top);
}];
}
if (_buttonSubTitle.length > 0) {
self.buttonSubTitleLabel.text = _buttonSubTitle;
[self.buttonSubTitleLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(self.buttonTitleLabel.mas_left);
make.top.mas_equalTo(self.mas_centerY);
make.width.mas_equalTo(self.buttonTitleLabel.mas_width);
make.height.mas_equalTo(self.mas_height).multipliedBy(0.5);
}];
[self.buttonTitleLabel mas_updateConstraints:^(MASConstraintMaker *make) {
make.height.mas_equalTo(self.mas_height).multipliedBy(0.5);
}];
}
}
// 文字下 图片上
- (void)createCustomButtonIndicatorTitleBottom
{
[self.bottomHoldView mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.mas_equalTo(self);
}];
if (_buttonImageName.length > 0) {
[self.buttonImageView mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.mas_equalTo(self.buttonMargin);
make.centerX.mas_equalTo(self.mas_centerX);
make.width.height.mas_equalTo([self getButtonImageViewSideLength]);
}];
self.buttonTitleLabel.textAlignment = NSTextAlignmentCenter;
[self.buttonTitleLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(0);
make.right.mas_equalTo(self.bottomHoldView.mas_right);
make.top.mas_equalTo(self.buttonImageView.mas_bottom).with.offset(self.graphicDistance);
make.bottom.mas_equalTo(self.bottomHoldView.mas_bottom).with.offset(- self.buttonMargin);
}];
} else {
[self.buttonTitleLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(0);
make.bottom.mas_equalTo(self.bottomHoldView.mas_bottom).with.offset(- self.buttonMargin);
make.width.mas_equalTo([TFViewHelper getDynamicWidthWithLabel:self.buttonTitleLabel] > self.width?self.width:[TFViewHelper getDynamicWidthWithLabel:self.buttonTitleLabel]);
make.height.mas_equalTo(self.mas_height);
}];
}
}
// 图片左 文字右 (同时靠左)
- (void)createCustomButtonIndicatorImageLeftBothLeft
{
self.buttonTitleLabel.textAlignment = NSTextAlignmentLeft;
if (_buttonImageName.length > 0) {
[self.buttonImageView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(self.mas_left);
make.centerY.mas_equalTo(self.mas_centerY);
make.width.height.mas_equalTo([self getButtonImageViewSideLength]);
}];
[self.buttonTitleLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(self.buttonImageView.mas_right).with.offset(self.graphicDistance);
make.right.mas_equalTo(self.mas_right);
make.centerY.mas_equalTo(self.mas_centerY);
make.height.mas_equalTo(self.mas_height);
}];
} else {
[self.buttonTitleLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.mas_equalTo(0);
make.left.mas_equalTo(0);
make.right.mas_equalTo(self.mas_right);
make.height.mas_equalTo(self.mas_height);
}];
}
if (_buttonSubTitle.length > 0) {
self.buttonSubTitleLabel.text = _buttonSubTitle;
self.buttonSubTitleLabel.textAlignment = NSTextAlignmentRight;
[self.buttonSubTitleLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(self.buttonTitleLabel.mas_left);
make.top.mas_equalTo(self.mas_centerY);
make.width.mas_equalTo(self.buttonTitleLabel.mas_width);
make.height.mas_equalTo(self.mas_height).multipliedBy(0.4);
}];
[self.buttonTitleLabel mas_updateConstraints:^(MASConstraintMaker *make) {
make.height.mas_equalTo(self.mas_height).multipliedBy(0.6);
}];
}
}
// 图片左 文字右 (同时靠右)
- (void)createCustomButtonIndicatorImageLeftBothRight
{
[self.buttonTitleLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.mas_equalTo(0);
make.right.mas_equalTo(self.mas_right);
make.left.mas_equalTo(self.mas_left);
make.height.mas_equalTo(self.mas_height);
}];
if (_buttonImageName.length > 0) {
[self.buttonImageView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(self.mas_left);
make.centerY.mas_equalTo(self.mas_centerY);
make.width.height.mas_equalTo([self getButtonImageViewSideLength]);
}];
self.buttonTitleLabel.textAlignment = NSTextAlignmentLeft;
[self.buttonTitleLabel mas_updateConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(self.buttonImageView.mas_right).with.offset(self.graphicDistance);
}];
}
if (_buttonSubTitle.length > 0) {
self.buttonSubTitleLabel.text = _buttonSubTitle;
self.buttonSubTitleLabel.textAlignment = NSTextAlignmentLeft;
[self.buttonSubTitleLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(self.buttonTitleLabel.mas_left);
make.top.mas_equalTo(self.mas_centerY);
make.width.mas_equalTo(self.buttonTitleLabel.mas_width);
make.height.mas_equalTo(self.mas_height).multipliedBy(0.4);
}];
[self.buttonTitleLabel mas_updateConstraints:^(MASConstraintMaker *make) {
make.height.mas_equalTo(self.mas_height).multipliedBy(0.6);
}];
}
}
// 文字左 图片右 (同时靠左)
- (void)createCustomButtonIndicatorImageRightBothLeft
{
[self.buttonTitleLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.mas_equalTo(0);
make.left.mas_equalTo(0);
make.right.mas_equalTo(self.mas_right);
make.height.mas_equalTo(self.mas_height);
}];
if (_buttonImageName.length > 0) {
[self.buttonImageView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(self.buttonTitleLabel.mas_right).with.offset(self.graphicDistance);
make.centerY.mas_equalTo(self.mas_centerY);
make.width.height.mas_equalTo([self getButtonImageViewSideLength]);
}];
self.buttonTitleLabel.textAlignment = NSTextAlignmentRight;
[self.buttonTitleLabel mas_updateConstraints:^(MASConstraintMaker *make) {
make.right.mas_equalTo(self.mas_right).with.offset(- self.width / 2 + self.graphicDistance / 2);
}];
}
if (_buttonSubTitle.length > 0) {
self.buttonSubTitleLabel.text = _buttonSubTitle;
self.buttonSubTitleLabel.textAlignment = NSTextAlignmentRight;
[self.buttonSubTitleLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(self.buttonTitleLabel.mas_left);
make.top.mas_equalTo(self.mas_centerY);
make.width.mas_equalTo(self.buttonTitleLabel.mas_width);
make.height.mas_equalTo(self.mas_height).multipliedBy(0.4);
}];
[self.buttonTitleLabel mas_updateConstraints:^(MASConstraintMaker *make) {
make.height.mas_equalTo(self.mas_height).multipliedBy(0.6);
}];
}
}
// 文字左 图片右 (同时靠右)
- (void)createCustomButtonIndicatorImageRightBothRight
{
self.buttonTitleLabel.textAlignment = NSTextAlignmentRight;
if (_buttonImageName.length > 0) {
[self.buttonImageView mas_makeConstraints:^(MASConstraintMaker *make) {
make.right.mas_equalTo(self.mas_right);
make.centerY.mas_equalTo(self.mas_centerY);
make.width.height.mas_equalTo([self getButtonImageViewSideLength]);
}];
[self.buttonTitleLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.mas_equalTo(0);
make.left.mas_equalTo(0);
make.right.mas_equalTo(self.buttonImageView.mas_left).with.offset(- self.graphicDistance);
make.height.mas_equalTo(self.mas_height);
}];
} else {
[self.buttonTitleLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.mas_equalTo(0);
make.left.mas_equalTo(0);
make.right.mas_equalTo(self.mas_right);
make.height.mas_equalTo(self.mas_height);
}];
}
if (_buttonSubTitle.length > 0) {
self.buttonSubTitleLabel.text = _buttonSubTitle;
self.buttonSubTitleLabel.textAlignment = NSTextAlignmentRight;
[self.buttonSubTitleLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(self.buttonTitleLabel.mas_left);
make.top.mas_equalTo(self.mas_centerY);
make.width.mas_equalTo(self.buttonTitleLabel.mas_width);
make.height.mas_equalTo(self.mas_height).multipliedBy(0.4);
}];
[self.buttonTitleLabel mas_updateConstraints:^(MASConstraintMaker *make) {
make.height.mas_equalTo(self.mas_height).multipliedBy(0.6);
}];
}
}
#pragma mark - 变量设置
- (void)setButtonTitle:(NSString *)buttonTitle
{
_buttonTitle = buttonTitle;
self.buttonTitleLabel.text = buttonTitle;
}
- (void)setButtonSubTitle:(NSString *)buttonSubTitle
{
_buttonSubTitle = buttonSubTitle;
self.buttonSubTitleLabel.text = buttonSubTitle;
}
- (void)setButtonTitleFont:(UIFont *)buttonTitleFont
{
_buttonTitleFont = buttonTitleFont;
self.buttonTitleLabel.font = buttonTitleFont;
}
- (void)setButtonSubTitleFont:(UIFont *)buttonSubTitleFont
{
_buttonSubTitleFont = buttonSubTitleFont;
self.buttonSubTitleLabel.font = buttonSubTitleFont;
}
- (void)setButtonTitleColor:(UIColor *)buttonTitleColor
{
_buttonTitleColor = buttonTitleColor;
self.buttonTitleLabel.textColor = buttonTitleColor;
}
- (void)setButtonSubTitleColor:(UIColor *)buttonSubTitleColor
{
_buttonSubTitleColor = buttonSubTitleColor;
self.buttonSubTitleLabel.textColor = buttonSubTitleColor;
}
- (void)setButtonTintColor:(UIColor *)buttonTintColor
{
_buttonTintColor = buttonTintColor;
self.buttonTitleLabel.textColor = buttonTintColor;
self.buttonSubTitleLabel.textColor = buttonTintColor;
_buttonImageView.image = [_buttonImageView.image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
self.buttonImageView.tintColor = buttonTintColor;
}
- (void)setButtonImageName:(NSString *)buttonImageName
{
_buttonImageName = buttonImageName;
_buttonImageView.image = [[UIImage imageNamed:buttonImageName] imageWithRenderingMode:UIImageRenderingModeAutomatic];
}
- (void)setButtonImageViewWidth:(NSInteger)buttonImageViewWidth
{
_buttonImageViewWidth = buttonImageViewWidth;
[self.buttonImageView mas_updateConstraints:^(MASConstraintMaker *make) {
make.width.equalTo(@(buttonImageViewWidth)).priorityHigh();
}];
}
- (void)setButtonImageViewHeight:(NSInteger)buttonImageViewHeight
{
_buttonImageViewHeight = buttonImageViewHeight;
[self.buttonImageView mas_updateConstraints:^(MASConstraintMaker *make) {
make.height.equalTo(@(buttonImageViewHeight)).with.priorityHigh();
}];
}
- (void)setHorizontalMigration:(CGFloat)horizontalMigration
{
_horizontalMigration = horizontalMigration;
}
- (void)setButtonImageScale:(CGFloat)buttonImageScale
{
if (buttonImageScale > 1) {
buttonImageScale = 1;
}
if (buttonImageScale < 0) {
buttonImageScale = 0;
}
_buttonImageScale = buttonImageScale;
}
- (void)setGraphicDistance:(NSInteger)graphicDistance
{
_graphicDistance = graphicDistance;
}
- (void)setCornerBackColor:(UIColor *)cornerBackColor
{
_cornerBackColor = cornerBackColor;
self.cornerMarkLabel.backgroundColor = cornerBackColor;
}
- (void)setCornerTextColor:(UIColor *)cornerTextColor
{
_cornerTextColor = cornerTextColor;
self.cornerMarkLabel.textColor = cornerTextColor;
}
- (void)setCornerTitle:(NSString *)cornerTitle
{
_cornerTitle = cornerTitle;
self.cornerMarkLabel.hidden = (cornerTitle.length == 0);
self.cornerMarkLabel.text = cornerTitle;
self.cornerMarkLabel.layer.cornerRadius = (self.getButtonImageViewSideLength / 2) / 2;
[self.cornerMarkLabel mas_updateConstraints:^(MASConstraintMaker *make) {
make.height.mas_equalTo(self.getButtonImageViewSideLength / 2);
make.width.mas_equalTo([TFViewHelper getDynamicWidthWithLabel:self.cornerMarkLabel]);
}];
}
- (void)setCornerTextFont:(UIFont *)cornerTextFont
{
_cornerTextFont = cornerTextFont;
self.cornerMarkLabel.font = cornerTextFont;
}
// 是否翻转图片
- (void)setTransformImageView:(BOOL)transformImageView
{
_transformImageView = transformImageView;
if (transformImageView) {
self.buttonImageView.transform = CGAffineTransformMakeRotation(M_PI);
}
}
- (void)setButtonTitleNumberOfLines:(NSInteger)buttonTitleNumberOfLines
{
_buttonTitleNumberOfLines = buttonTitleNumberOfLines;
self.buttonTitleLabel.numberOfLines = buttonTitleNumberOfLines;
}
static CFAbsoluteTime startTime;
- (void)startSpin {
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"];
animation.fromValue = @(0.0f);
animation.toValue = @(M_PI * 2.0);
animation.duration = 0.8;
animation.autoreverses = NO;
animation.fillMode = kCAFillModeForwards;
animation.repeatCount = MAXFLOAT;
[self.buttonImageView.layer addAnimation:animation forKey:@"spin"];
startTime = CFAbsoluteTimeGetCurrent();
}
- (void)stopSpin
{
CFAbsoluteTime currentTime = CFAbsoluteTimeGetCurrent();
CFTimeInterval inteval = currentTime * 1000.0 - startTime * 1000.0;
if (inteval < 1000.0) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self.buttonImageView.layer removeAnimationForKey:@"spin"];
});
} else {
[self.buttonImageView.layer removeAnimationForKey:@"spin"];
}
}
#pragma mark - LazyLoad
- (UIView *)bottomHoldView
{
if (!_bottomHoldView) {
_bottomHoldView = [[UIView alloc] init];
_bottomHoldView.backgroundColor = [UIColor clearColor];
_bottomHoldView.userInteractionEnabled = NO;
}
return _bottomHoldView;
}
- (UIImageView *)buttonImageView
{
if (!_buttonImageView) {
_buttonImageView = [[UIImageView alloc] init];
}
return _buttonImageView;
}
- (UILabel *)cornerMarkLabel
{
if (!_cornerMarkLabel) {
_cornerMarkLabel = [[UILabel alloc] init];
_cornerMarkLabel.textAlignment = NSTextAlignmentCenter;
_cornerMarkLabel.backgroundColor = kRedColor;
_cornerMarkLabel.font = kFont6;
_cornerMarkLabel.textColor = kWhiteColor;
_cornerMarkLabel.clipsToBounds = YES;
}
return _cornerMarkLabel;
}
- (UILabel *)buttonTitleLabel
{
if (!_buttonTitleLabel) {
_buttonTitleLabel = [[UILabel alloc] init];
_buttonTitleLabel.numberOfLines = 1;
_buttonTitleLabel.font = kFont12;
_buttonTitleLabel.textColor = kBlackColor;
_buttonTitleLabel.textAlignment = NSTextAlignmentCenter;
}
return _buttonTitleLabel;
}
- (UILabel *)buttonSubTitleLabel
{
if (!_buttonSubTitleLabel) {
_buttonSubTitleLabel = [[UILabel alloc] init];
_buttonSubTitleLabel.numberOfLines = 1;
_buttonSubTitleLabel.font = kFont10;
_buttonSubTitleLabel.textColor = kGrayTextColor;
_buttonSubTitleLabel.textAlignment = NSTextAlignmentCenter;
}
return _buttonSubTitleLabel;
}
- (UIView *)maskView
{
if (!_maskView) {
_maskView = [[UIView alloc] init];
_maskView.frame = self.frame;
_maskView.backgroundColor = kGrayViewColor;
}
return _maskView;
}
@end
@@ -0,0 +1,81 @@
//
// TFEmptyBaseView.h
// WXReader
//
// Created by 谢腾飞 on 2020/11/20.
// Copyright © 2020 xtfei_2011@126.com. All rights reserved.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
typedef void (^TFActionTapBlock)(void);
@interface TFEmptyBaseView : UIView
/** 内容物背景视图 */
@property (nonatomic ,strong ,readonly) UIView *contentView;
@property (nonatomic ,copy) NSString *image;
@property (nonatomic ,copy) NSString *title;
@property (nonatomic ,copy) NSString *detail;
@property (nonatomic ,copy) NSString *btnTitle;
@property (nonatomic ,weak ,readonly) id actionBtnTarget;
@property (nonatomic ,assign ,readonly) SEL actionBtnAction;
@property (nonatomic ,copy) TFActionTapBlock tapContentViewBlock;
@property (nonatomic ,copy ,readonly) TFActionTapBlock btnClickBlock;
@property (nonatomic ,strong ,readonly) UIView *customView;
/** 是否隐藏 EmptyView 默认隐藏 */
@property (nonatomic ,assign) BOOL autoShowEmptyView;
// 初始化配置
- (void)prepare;
// 重置Subviews
- (void)setupSubviews;
/***
* 构造方法 - 创建 EmptyView
* @param image 占位图片名称
* @param title 标题
* @param detail 详细描述
* @param btnTitle 按钮的名称
* @param target 响应的对象
* @param action 按钮点击事件
* @return 返回一个 EmptyView
*/
+ (instancetype)emptyActionViewWithImage:(NSString *)image title:(NSString *)title detail:(NSString *)detail btnTitle:(NSString *)btnTitle target:(id)target action:(SEL)action;
/***
* 构造方法2 - 创建 EmptyView
* @param image 占位图片名称
* @param title 占位描述
* @param detail 详细描述
* @param btnTitle 按钮的名称
* @param btnClickBlock 按钮点击事件回调
* @return 返回一个 EmptyView
*/
+ (instancetype)emptyActionViewWithImage:(NSString *)image title:(NSString *)title detail:(NSString *)detail btnTitle:(NSString *)btnTitle btnClickBlock:(TFActionTapBlock)btnClickBlock;
/***
* 构造方法3 - 创建 EmptyView
* @param image 占位图片名称
* @param title 占位描述
* @param detail 详细描述
* @return 返回一个没有点击事件的 EmptyView
*/
+ (instancetype)emptyViewWithImage:(NSString *)image title:(NSString *)title detail:(NSString *)detail;
/***
* 构造方法4 - 创建一个自定义的 EmptyView
* @param customView 自定义view
* @return 返回一个自定义内容的 EmptyView
*/
+ (instancetype)emptyViewWithCustomView:(UIView *)customView;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,177 @@
//
// TFEmptyBaseView.m
// WXReader
//
// Created by 谢腾飞 on 2020/11/20.
// Copyright © 2020 xtfei_2011@126.com. All rights reserved.
//
#import "TFEmptyBaseView.h"
@interface TFEmptyBaseView ()
@end
@implementation TFEmptyBaseView
- (instancetype)init
{
if (self = [super init]) {
self.autoShowEmptyView = YES;
[self prepare];
}
return self;
}
- (void)prepare
{
self.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
}
- (void)layoutSubviews
{
[super layoutSubviews];
UIView *view = self.superview;
if (view && [view isKindOfClass:[UIView class]]){
self.xtfei_width = view.xtfei_width;
self.xtfei_height = view.xtfei_height;
}
[self setupSubviews];
}
- (void)setupSubviews
{
}
- (void)willMoveToSuperview:(UIView *)newSuperview
{
[super willMoveToSuperview:newSuperview];
if (newSuperview && ![newSuperview isKindOfClass:[UIView class]]) return;
if (newSuperview) {
self.xtfei_width = newSuperview.xtfei_width;
self.xtfei_height = newSuperview.xtfei_height;
}
}
#pragma mark 实例化
+ (instancetype)emptyActionViewWithImage:(NSString *)image title:(NSString *)title detail:(NSString *)detail btnTitle:(NSString *)btnTitle target:(id)target action:(SEL)action
{
TFEmptyBaseView *emptyView = [[self alloc] init];
[emptyView creatEmptyViewWithImage:image title:title detail:detail btnTitle:btnTitle target:target action:action];
return emptyView;
}
+ (instancetype)emptyActionViewWithImage:(NSString *)image title:(NSString *)title detail:(NSString *)detail btnTitle:(NSString *)btnTitle btnClickBlock:(TFActionTapBlock)btnClickBlock
{
TFEmptyBaseView *emptyView = [[self alloc] init];
[emptyView creatEmptyViewWithImage:image title:title detail:detail btnTitle:btnTitle btnClickBlock:btnClickBlock];
return emptyView;
}
+ (instancetype)emptyViewWithImage:(NSString *)image title:(NSString *)title detail:(NSString *)detail
{
TFEmptyBaseView *emptyView = [[self alloc] init];
[emptyView creatEmptyViewWithImage:image title:title detail:detail btnTitle:nil btnClickBlock:nil];
return emptyView;
}
+ (instancetype)emptyViewWithCustomView:(UIView *)customView
{
TFEmptyBaseView *emptyView = [[self alloc] init];
[emptyView creatEmptyViewWithCustomView:customView];
return emptyView;
}
- (void)creatEmptyViewWithImage:(NSString *)image title:(NSString *)title detail:(NSString *)detail btnTitle:(NSString *)btnTitle target:(id)target action:(SEL)action
{
_image = image;
_title = title;
_detail = detail;
_btnTitle = btnTitle;
_actionBtnTarget = target;
_actionBtnAction = action;
if (!_contentView) {
_contentView = [[UIView alloc] initWithFrame:CGRectZero];
[self addSubview:_contentView];
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapGestureRecognizer:)];
[_contentView addGestureRecognizer:tap];
}
}
- (void)creatEmptyViewWithImage:(NSString *)image title:(NSString *)title detail:(NSString *)detail btnTitle:(NSString *)btnTitle btnClickBlock:(TFActionTapBlock)btnClickBlock
{
_image = image;
_title = title;
_detail = detail;
_btnTitle = btnTitle;
_btnClickBlock = btnClickBlock;
if (!_contentView) {
_contentView = [[UIView alloc] initWithFrame:CGRectZero];
[self addSubview:_contentView];
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapGestureRecognizer:)];
[_contentView addGestureRecognizer:tap];
}
}
- (void)creatEmptyViewWithCustomView:(UIView *)customView
{
if (!_contentView) {
_contentView = [[UIView alloc] initWithFrame:CGRectZero];
[self addSubview:_contentView];
}
if (!_customView) {
[_contentView addSubview:customView];
}
_customView = customView;
}
#pragma mark Setter
- (void)setImage:(NSString *)image
{
_image = image;
[self layoutSubviews];
}
- (void)setTitle:(NSString *)title
{
_title = title;
[self layoutSubviews];
}
- (void)setDetail:(NSString *)detail
{
_detail = detail;
[self layoutSubviews];
}
- (void)setBtnTitle:(NSString *)btnTitle
{
_btnTitle = btnTitle;
[self layoutSubviews];
}
- (void)tapGestureRecognizer:(UITapGestureRecognizer *)tap
{
if (_tapContentViewBlock) {
_tapContentViewBlock();
}
}
@end
@@ -0,0 +1,100 @@
//
// TFEmptyView.h
// WXReader
//
// Created by 谢腾飞 on 2020/11/20.
// Copyright © 2020 xtfei_2011@126.com. All rights reserved.
//
#import "TFEmptyBaseView.h"
NS_ASSUME_NONNULL_BEGIN
@interface TFEmptyView : TFEmptyBaseView
/**
* 子控件间距 默认20.f
*/
@property (nonatomic ,assign) CGFloat subViewMargin;
/**
* 控件垂直方向偏移 (此属性与contentViewY 互斥,只有一个会有效)
*/
@property (nonatomic ,assign) CGFloat contentViewOffset;
/**
* 控件Y坐标 (此属性与contentViewOffset 互斥,只有一个会有效)
*/
@property (nonatomic ,assign) CGFloat contentViewY;
#pragma Mark image
/**
* 图片可设置固定大小 (默认图片实际大小)
*/
@property (nonatomic ,assign) CGSize imageSize;
#pragma Mark titleLab 相关
/**
* 标题字体, 默认 16.f
*/
@property (nonatomic ,strong) UIFont *titleLabFont;
/**
* 标题文字颜色
*/
@property (nonatomic ,strong) UIColor *titleLabTextColor;
#pragma Mark detailLab 相关
/**
* 详细描述字体,默认 14.f
*/
@property (nonatomic ,strong) UIFont *detailLabFont;
/**
* 详细描述最大行数, 默认 2
*/
@property (nonatomic ,assign) NSInteger detailLabMaxLines;
/**
* 详细描述文字颜色
*/
@property (nonatomic ,strong) UIColor *detailLabTextColor;
#pragma Mark 按钮相关
/**
* 按钮字体, 默认 14.f
*/
@property (nonatomic ,strong) UIFont *actionBtnFont;
/**
* 按钮的高度, 默认 40.f
*/
@property (nonatomic ,assign) CGFloat actionBtnHeight;
/**
* 水平方向内边距, 默认 30.f
*/
@property (nonatomic ,assign) CGFloat actionBtnHorizontalMargin;
/**
* 按钮的圆角大小, 默认 5.f
*/
@property (nonatomic ,assign) CGFloat actionBtnCornerRadius;
/**
* 按钮边框border的宽度, 默认 0
*/
@property (nonatomic ,assign) CGFloat actionBtnBorderWidth;
/**
* 按钮边框颜色
*/
@property (nonatomic ,strong) UIColor *actionBtnBorderColor;
/**
* 按钮文字颜色
*/
@property (nonatomic ,strong) UIColor *actionBtnTitleColor;
/**
* 按钮背景颜色
*/
@property (nonatomic ,strong) UIColor *actionBtnBackGroundColor;
@property (nonatomic ,strong) UIImageView *promptImageView;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,488 @@
//
// TFEmptyView.m
// WXReader
//
// Created by 谢腾飞 on 2020/11/20.
// Copyright © 2020 xtfei_2011@126.com. All rights reserved.
//
#import "TFEmptyView.h"
// 每个子控件之间的间距
#define kSubViewMargin 20.f
// 描述字体
#define kTitleLabFont [UIFont systemFontOfSize:16.f]
// 详细描述字体
#define kDetailLabFont [UIFont systemFontOfSize:14.f]
// 按钮字体大小
#define kActionBtnFont [UIFont systemFontOfSize:14.f]
// 按钮高度
#define kActionBtnHeight 40.f
// 水平方向内边距
#define kActionBtnHorizontalMargin 30.f
// 灰色
#define kGrayColor [UIColor colorWithRed:0.5f green:0.5f blue:0.5f alpha:1.f]
@interface TFEmptyView ()
@property (nonatomic ,strong) UILabel *titleLabel;
@property (nonatomic ,strong) UILabel *detailLabel;
@property (nonatomic ,strong) UIButton *actionButton;
@end
@implementation TFEmptyView
{
CGFloat _contentMaxWidth; // 最大宽度
CGFloat _contentWidth; // 内容物宽度
CGFloat _contentHeight; // 内容物高度
CGFloat _subViweMargin; // 间距
}
- (void)prepare
{
[super prepare];
self.contentViewY = 1000;
}
- (void)setupSubviews
{
[super setupSubviews];
_contentMaxWidth = self.xtfei_width - 30.f;
_contentWidth = 0;
_contentHeight = 0;
_subViweMargin = self.subViewMargin ? self.subViewMargin : kSubViewMargin;
// 占位图片
UIImage *image = [UIImage imageNamed:self.image];
if (image) {
[self setupPromptImageView:image];
} else {
if (_promptImageView) {
[_promptImageView removeFromSuperview];
}
}
// 标题
if (self.title.length) {
[self setupTitleLabel:self.title];
} else {
if (_titleLabel) {
[_titleLabel removeFromSuperview];
}
}
// 详细描述
if (self.detail.length) {
[self setupDetailLabel:self.detail];
} else {
if (_detailLabel) {
[_detailLabel removeFromSuperview];
}
}
// 按钮
if (self.btnTitle.length) {
if (self.actionBtnTarget && self.actionBtnAction) {
[self setupActionBtn:self.btnTitle target:self.actionBtnTarget action:self.actionBtnAction btnClickBlock:nil];
} else if (self.btnClickBlock) {
[self setupActionBtn:self.btnTitle target:nil action:nil btnClickBlock:self.btnClickBlock];
} else {
if (_actionButton) {
[_actionButton removeFromSuperview];
}
}
} else {
if (_actionButton) {
[_actionButton removeFromSuperview];
}
}
// 自定义view
if (self.customView) {
_contentWidth = self.customView.xtfei_width;
_contentHeight = self.customView.xtfei_y + self.customView.xtfei_height;
}
// 设置frame
[self setSubViewFrame];
}
- (void)setSubViewFrame
{
CGFloat scrollViewWidth = self.bounds.size.width;
CGFloat scrollViewHeight = self.bounds.size.height;
self.xtfei_size = CGSizeMake(_contentWidth, _contentHeight);
CGFloat emptyViewCenterX = scrollViewWidth * 0.5f;
CGFloat emptyViewCenterY = scrollViewHeight * 0.5f;
self.center = CGPointMake(emptyViewCenterX, emptyViewCenterY);
self.contentView.frame = self.bounds;
CGFloat centerX = self.contentView.xtfei_width * 0.5f;
if (self.customView) {
self.customView.xtfei_centerX = centerX;
} else {
_promptImageView.xtfei_centerX = centerX;
_titleLabel.xtfei_centerX = centerX;
_detailLabel.xtfei_centerX = centerX;
_actionButton.xtfei_centerX = centerX;
}
if (self.contentViewOffset) {
self.xtfei_centerY += self.contentViewOffset;
}
if (self.contentViewY < 1000) {
self.xtfei_y = self.contentViewY;
}
}
- (void)setupPromptImageView:(UIImage *)img
{
self.promptImageView.image = img;
CGFloat imgViewWidth = img.size.width;
CGFloat imgViewHeight = img.size.height;
self.promptImageView.image = [img imageWithRenderingMode:(UIImageRenderingModeAlwaysOriginal)];
if (self.imageSize.width && self.imageSize.height) {
if (imgViewWidth > imgViewHeight) {
imgViewHeight = (imgViewHeight / imgViewWidth) * self.imageSize.width;
imgViewWidth = self.imageSize.width;
} else {
imgViewWidth = (imgViewWidth / imgViewHeight) * self.imageSize.height;
imgViewHeight = self.imageSize.height;
}
}
self.promptImageView.frame = CGRectMake(0, 0, imgViewWidth, imgViewHeight);
_contentWidth = self.promptImageView.xtfei_size.width;
_contentHeight = self.promptImageView.xtfei_y + self.promptImageView.xtfei_height;
}
- (void)setupTitleLabel:(NSString *)titleStr
{
UIFont *font = self.titleLabFont.pointSize ? self.titleLabFont : kTitleLabFont;
CGFloat fontSize = [TFViewHelper getDynamicHeightWithLabelFont:font labelWidth:SCREEN_WIDTH labelText:titleStr];
UIColor *textColor = self.titleLabTextColor ? self.titleLabTextColor : kBlackColor;
CGFloat width = [self returnTextWidth:titleStr size:CGSizeMake(_contentMaxWidth, fontSize) font:font].width;
self.titleLabel.frame = CGRectMake(0, _contentHeight + _subViweMargin, width, fontSize);
self.titleLabel.font = font;
self.titleLabel.text = titleStr;
self.titleLabel.textColor = textColor;
_contentWidth = width > _contentWidth ? width : _contentWidth;
_contentHeight = self.titleLabel.xtfei_y + self.titleLabel.xtfei_height;
}
- (void)setupDetailLabel:(NSString *)detailStr
{
UIColor *textColor = self.detailLabTextColor ? self.detailLabTextColor : kGrayColor;
UIFont *font = self.detailLabFont.pointSize ? self.detailLabFont : kDetailLabFont;
CGFloat fontSize = font.pointSize;
CGFloat maxHeight = self.detailLabMaxLines ? self.detailLabMaxLines * (fontSize + 5) : 2 * (fontSize + 5);
CGSize size = [self returnTextWidth:detailStr size:CGSizeMake(_contentMaxWidth, maxHeight) font:font];
CGFloat width = size.width;
CGFloat height = size.height;
self.detailLabel.font = font;
self.detailLabel.frame = CGRectMake(0, _contentHeight + _subViweMargin, width, height);
self.detailLabel.text = detailStr;
self.detailLabel.textColor = textColor;
_contentWidth = width > _contentWidth ? width : _contentWidth;
_contentHeight = self.detailLabel.xtfei_y + self.detailLabel.xtfei_height;
}
- (void)setupActionBtn:(NSString *)btnTitle target:(id)target action:(SEL)action btnClickBlock:(TFActionTapBlock)btnClickBlock
{
UIFont *font = self.actionBtnFont.pointSize ? self.actionBtnFont : kActionBtnFont;
CGFloat fontSize = font.pointSize;
UIColor *titleColor = self.actionBtnTitleColor ? self.actionBtnTitleColor : kBlackColor;
UIColor *backGColor = self.actionBtnBackGroundColor ? self.actionBtnBackGroundColor : [UIColor whiteColor];
UIColor *borderColor = self.actionBtnBorderColor ? self.actionBtnBorderColor : [UIColor colorWithRed:0.8f green:0.8f blue:0.8f alpha:1];
CGFloat borderWidth = self.actionBtnBorderWidth ? self.actionBtnBorderWidth : 0.0f;
CGFloat cornerRadius = self.actionBtnCornerRadius ? self.actionBtnCornerRadius : 5.f;
CGFloat horiMargin = self.actionBtnHorizontalMargin ? self.actionBtnHorizontalMargin : kActionBtnHorizontalMargin;
CGFloat height = self.actionBtnHeight ? self.actionBtnHeight : kActionBtnHeight;
CGSize textSize = [self returnTextWidth:btnTitle size:CGSizeMake(_contentMaxWidth, fontSize) font:font];
if (height < textSize.height) {
height = textSize.height + 4;
}
// 按钮的宽高
CGFloat btnWidth = textSize.width + horiMargin * 2;
CGFloat btnHeight = height;
btnWidth = btnWidth > _contentMaxWidth ? _contentMaxWidth : btnWidth;
self.actionButton.frame = CGRectMake(0, _contentHeight + _subViweMargin, btnWidth, btnHeight);
[self.actionButton setTitle:btnTitle forState:UIControlStateNormal];
self.actionButton.titleLabel.font = font;
self.actionButton.backgroundColor = backGColor;
[self.actionButton setTitleColor:titleColor forState:UIControlStateNormal];
self.actionButton.layer.borderColor = borderColor.CGColor;
self.actionButton.layer.borderWidth = borderWidth;
self.actionButton.layer.cornerRadius = cornerRadius;
// 添加事件
if (target && action) {
[self.actionButton addTarget:target action:action forControlEvents:UIControlEventTouchUpInside];
[self.actionButton addTarget:self action:@selector(actionBtnClick:) forControlEvents:UIControlEventTouchUpInside];
} else if (btnClickBlock) {
[self.actionButton addTarget:self action:@selector(actionBtnClick:) forControlEvents:UIControlEventTouchUpInside];
}
_contentWidth = btnWidth > _contentWidth ? btnWidth : _contentWidth;
_contentHeight = self.actionButton.xtfei_y + self.actionButton.xtfei_height;
}
- (UIImageView *)promptImageView
{
if (!_promptImageView) {
_promptImageView = [[UIImageView alloc] init];
_promptImageView.contentMode = UIViewContentModeScaleAspectFit;
[self.contentView addSubview:_promptImageView];
}
return _promptImageView;
}
- (UILabel *)titleLabel
{
if (!_titleLabel) {
_titleLabel = [[UILabel alloc] init];
_titleLabel.textAlignment = NSTextAlignmentCenter;
[self.contentView addSubview:_titleLabel];
}
return _titleLabel;
}
- (UILabel *)detailLabel
{
if (!_detailLabel) {
_detailLabel = [[UILabel alloc] init];
_detailLabel.textAlignment = NSTextAlignmentCenter;
_detailLabel.numberOfLines = 0;
[self.contentView addSubview:_detailLabel];
}
return _detailLabel;
}
- (UIButton *)actionButton
{
if (!_actionButton) {
_actionButton = [[UIButton alloc] init];
_actionButton.layer.masksToBounds = YES;
[self.contentView addSubview:_actionButton];
}
return _actionButton;
}
- (void)setSubViewMargin:(CGFloat)subViewMargin
{
if (_subViewMargin != subViewMargin) {
_subViewMargin = subViewMargin;
if (_promptImageView || _titleLabel || _detailLabel || _actionButton || self.customView) {
[self setupSubviews];
}
}
}
- (void)setContentViewOffset:(CGFloat)contentViewOffset
{
if (_contentViewOffset != contentViewOffset) {
_contentViewOffset = contentViewOffset;
if (_promptImageView || _titleLabel || _detailLabel || _actionButton || self.customView) {
self.xtfei_centerY += self.contentViewOffset;
}
}
}
- (void)setContentViewY:(CGFloat)contentViewY
{
if (_contentViewY != contentViewY) {
_contentViewY = contentViewY;
if (_promptImageView || _titleLabel || _detailLabel || _actionButton || self.customView) {
self.xtfei_y = self.contentViewY;
}
}
}
- (void)setImageSize:(CGSize)imageSize
{
if (_imageSize.width != imageSize.width || _imageSize.height != imageSize.height) {
_imageSize = imageSize;
if (_promptImageView) {
[self setupSubviews];
}
}
}
- (void)setTitleLabFont:(UIFont *)titleLabFont
{
if (_titleLabFont != titleLabFont) {
_titleLabFont = titleLabFont;
if (_titleLabel) {
[self setupSubviews];
}
}
}
- (void)setTitleLabTextColor:(UIColor *)titleLabTextColor
{
if (_titleLabTextColor != titleLabTextColor) {
_titleLabTextColor = titleLabTextColor;
if (_titleLabel) {
_titleLabel.textColor = titleLabTextColor;
}
}
}
- (void)setDetailLabFont:(UIFont *)detailLabFont
{
if (_detailLabFont != detailLabFont) {
_detailLabFont = detailLabFont;
if (_detailLabel) {
[self setupSubviews];
}
}
}
- (void)setDetailLabMaxLines:(NSInteger)detailLabMaxLines
{
if (_detailLabMaxLines != detailLabMaxLines) {
_detailLabMaxLines = detailLabMaxLines;
if (_detailLabel) {
[self setupSubviews];
}
}
}
- (void)setDetailLabTextColor:(UIColor *)detailLabTextColor
{
if (_detailLabTextColor != detailLabTextColor) {
_detailLabTextColor = detailLabTextColor;
if (_detailLabel) {
_detailLabel.textColor = detailLabTextColor;
}
}
}
- (void)setActionBtnFont:(UIFont *)actionBtnFont
{
if (_actionBtnFont != actionBtnFont) {
_actionBtnFont = actionBtnFont;
if (_actionButton) {
[self setupSubviews];
}
}
}
- (void)setActionBtnHeight:(CGFloat)actionBtnHeight
{
if (_actionBtnHeight != actionBtnHeight) {
_actionBtnHeight = actionBtnHeight;
if (_actionButton) {
[self setupSubviews];
}
}
}
- (void)setActionBtnHorizontalMargin:(CGFloat)actionBtnHorizontalMargin
{
if (_actionBtnHorizontalMargin != actionBtnHorizontalMargin) {
_actionBtnHorizontalMargin = actionBtnHorizontalMargin;
if (_actionButton) {
[self setupSubviews];
}
}
}
- (void)setActionBtnCornerRadius:(CGFloat)actionBtnCornerRadius
{
if (_actionBtnCornerRadius != actionBtnCornerRadius) {
_actionBtnCornerRadius = actionBtnCornerRadius;
if (_actionButton) {
_actionButton.layer.cornerRadius = actionBtnCornerRadius;
}
}
}
- (void)setActionBtnBorderWidth:(CGFloat)actionBtnBorderWidth
{
if (actionBtnBorderWidth != _actionBtnBorderWidth) {
_actionBtnBorderWidth = actionBtnBorderWidth;
if (_actionButton) {
_actionButton.layer.borderWidth = actionBtnBorderWidth;
}
}
}
- (void)setActionBtnBorderColor:(UIColor *)actionBtnBorderColor
{
if (_actionBtnBorderColor != actionBtnBorderColor) {
_actionBtnBorderColor = actionBtnBorderColor;
if (_actionButton) {
_actionButton.layer.borderColor = actionBtnBorderColor.CGColor;
}
}
}
- (void)setActionBtnTitleColor:(UIColor *)actionBtnTitleColor
{
if (_actionBtnTitleColor != actionBtnTitleColor) {
_actionBtnTitleColor = actionBtnTitleColor;
if (_actionButton) {
[_actionButton setTitleColor:actionBtnTitleColor forState:UIControlStateNormal];
}
}
}
- (void)setActionBtnBackGroundColor:(UIColor *)actionBtnBackGroundColor
{
if (actionBtnBackGroundColor != _actionBtnBackGroundColor) {
_actionBtnBackGroundColor = actionBtnBackGroundColor;
if (_actionButton) {
[_actionButton setBackgroundColor:actionBtnBackGroundColor];
}
}
}
- (void)actionBtnClick:(UIButton *)sender
{
if (self.btnClickBlock) {
self.btnClickBlock();
}
}
- (CGSize)returnTextWidth:(NSString *)text size:(CGSize)size font:(UIFont *)font
{
return [text boundingRectWithSize:size options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName:font} context:nil].size;
}
@end
@@ -0,0 +1,15 @@
//
// TFEmptyViewHeader.h
// WXReader
//
// Created by 谢腾飞 on 2020/11/20.
// Copyright © 2020 xtfei_2011@126.com. All rights reserved.
//
#ifndef TFEmptyViewHeader_h
#define TFEmptyViewHeader_h
#import "TFEmptyView.h"
#import "UIView+TFEmptyView.h"
#endif /* TFEmptyViewHeader_h */
@@ -0,0 +1,42 @@
//
// UIView+TFEmptyView.h
// WXReader
//
// Created by 谢腾飞 on 2020/11/20.
// Copyright © 2020 xtfei_2011@126.com. All rights reserved.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@class TFEmptyView;
@interface UIView (TFEmptyView)
/**
* 空页面占位图控件
*/
@property (nonatomic ,strong) TFEmptyView *xtfei_emptyView;
#pragma Mark 使用下面的四个方法请将EmptyView 的 autoShowEmptyView 值置为NO
/**
* 一般用于开始请求网络时调用,xtfei_startLoading 调用时会暂时隐藏 emptyView
* 当调用 tf_endLoading 方法时,tf_endLoading 方法内部会根据当前的 tableView/collectionView 的 DataSource 来自动判断是否显示 emptyView
*/
- (void)xtfei_startLoading;
/**
* 在想要刷新 emptyView 状态时调用
* 注意: tf_endLoading 的调用时机,有刷新 UI 的地方要等到刷新 UI 的方法之后调用,
*/
- (void)xtfei_endLoading;
// 调用下面两个手动显隐的方法,不受DataSource的影响,单独设置显示与隐藏(前提是关闭autoShowEmptyView
/**
* 手动调用显示 emptyView
*/
- (void)xtfei_showEmptyView;
/**
* 手动调用隐藏 emptyView
*/
- (void)xtfei_hideEmptyView;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,246 @@
//
// UIView+TFEmptyView.m
// WXReader
//
// Created by 谢腾飞 on 2020/11/20.
// Copyright © 2020 xtfei_2011@126.com. All rights reserved.
//
#import "UIView+TFEmptyView.h"
#import <objc/runtime.h>
#import "TFEmptyView.h"
@implementation UIView (TFEmptyView)
+ (void)exchangeInstanceMethod1:(SEL)method1 method2:(SEL)method2
{
method_exchangeImplementations(class_getInstanceMethod(self, method1), class_getInstanceMethod(self, method2));
}
static char kEmptyViewKey;
- (void)setXtfei_emptyView:(TFEmptyView *)xtfei_emptyView
{
if (xtfei_emptyView != self.xtfei_emptyView) {
objc_setAssociatedObject(self, &kEmptyViewKey, xtfei_emptyView, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
for (UIView *view in self.subviews) {
if ([view isKindOfClass:[TFEmptyView class]]) {
[view removeFromSuperview];
}
}
[self addSubview:self.xtfei_emptyView];
}
}
- (TFEmptyView *)xtfei_emptyView
{
return objc_getAssociatedObject(self, &kEmptyViewKey);
}
- (NSInteger)totalDataCount
{
NSInteger totalCount = 0;
if ([self isKindOfClass:[UITableView class]]) {
UITableView *tableView = (UITableView *)self;
for (NSInteger section = 0; section < tableView.numberOfSections; section++) {
totalCount += [tableView numberOfRowsInSection:section];
}
} else if ([self isKindOfClass:[UICollectionView class]]) {
UICollectionView *collectionView = (UICollectionView *)self;
for (NSInteger section = 0; section < collectionView.numberOfSections; section++) {
totalCount += [collectionView numberOfItemsInSection:section];
}
}
return totalCount;
}
- (void)getDataAndSet
{
if (!self.xtfei_emptyView) return;
if ([self totalDataCount] == 0) {
[self show];
} else {
[self hide];
}
}
- (void)show
{
if (!self.xtfei_emptyView.autoShowEmptyView) {
self.xtfei_emptyView.hidden = YES;
return;
}
[self xtfei_showEmptyView];
}
- (void)hide
{
if (!self.xtfei_emptyView.autoShowEmptyView) {
self.xtfei_emptyView.hidden = YES;
return;
}
[self xtfei_hideEmptyView];
}
- (void)xtfei_showEmptyView
{
[self.xtfei_emptyView.superview layoutSubviews];
self.xtfei_emptyView.hidden = NO;
[self bringSubviewToFront:self.xtfei_emptyView];
UITableView *tableView = (UITableView *)self;
if ([tableView isKindOfClass:UITableView.class]) {
tableView.mj_footer.hidden = YES;
}
}
- (void)xtfei_hideEmptyView
{
self.xtfei_emptyView.hidden = YES;
UITableView *tableView = (UITableView *)self;
if ([tableView isKindOfClass:UITableView.class]) {
tableView.mj_footer.hidden = NO;
}
}
- (void)xtfei_startLoading
{
self.xtfei_emptyView.hidden = YES;
}
- (void)xtfei_endLoading
{
UITableView *tableView = (UITableView *)self;
if ([tableView isKindOfClass:UITableView.class]) {
tableView.mj_footer.hidden = ![self totalDataCount];
}
self.xtfei_emptyView.hidden = [self totalDataCount];
}
@end
#pragma Mark UITableView
@implementation UITableView (TFEmptyView)
+ (void)load
{
[self exchangeInstanceMethod1:@selector(reloadData) method2:@selector(xtfei_reloadData)];
[self exchangeInstanceMethod1:@selector(insertSections:withRowAnimation:) method2:@selector(xtfei_insertSections:withRowAnimation:)];
[self exchangeInstanceMethod1:@selector(deleteSections:withRowAnimation:) method2:@selector(xtfei_deleteSections:withRowAnimation:)];
[self exchangeInstanceMethod1:@selector(reloadSections:withRowAnimation:) method2:@selector(xtfei_reloadSections:withRowAnimation:)];
[self exchangeInstanceMethod1:@selector(insertRowsAtIndexPaths:withRowAnimation:) method2:@selector(xtfei_insertRowsAtIndexPaths:withRowAnimation:)];
[self exchangeInstanceMethod1:@selector(deleteRowsAtIndexPaths:withRowAnimation:) method2:@selector(xtfei_deleteRowsAtIndexPaths:withRowAnimation:)];
[self exchangeInstanceMethod1:@selector(reloadRowsAtIndexPaths:withRowAnimation:) method2:@selector(xtfei_reloadRowsAtIndexPaths:withRowAnimation:)];
}
- (void)xtfei_reloadData
{
[self xtfei_reloadData];
[self getDataAndSet];
}
- (void)xtfei_insertSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation
{
[self xtfei_insertSections:sections withRowAnimation:animation];
[self getDataAndSet];
}
- (void)xtfei_deleteSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation
{
[self xtfei_deleteSections:sections withRowAnimation:animation];
[self getDataAndSet];
}
- (void)xtfei_reloadSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation
{
[self xtfei_reloadSections:sections withRowAnimation:animation];
[self getDataAndSet];
}
- (void)xtfei_insertRowsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation
{
[self xtfei_insertRowsAtIndexPaths:indexPaths withRowAnimation:animation];
[self getDataAndSet];
}
- (void)xtfei_deleteRowsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation
{
[self xtfei_deleteRowsAtIndexPaths:indexPaths withRowAnimation:animation];
[self getDataAndSet];
}
- (void)xtfei_reloadRowsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation
{
[self xtfei_reloadRowsAtIndexPaths:indexPaths withRowAnimation:animation];
[self getDataAndSet];
}
@end
#pragma Mark UICollectionView
@implementation UICollectionView (TFEmptyView)
+ (void)load
{
[self exchangeInstanceMethod1:@selector(reloadData) method2:@selector(xtfei_reloadData)];
[self exchangeInstanceMethod1:@selector(insertSections:) method2:@selector(xtfei_insertSections:)];
[self exchangeInstanceMethod1:@selector(deleteSections:) method2:@selector(xtfei_deleteSections:)];
[self exchangeInstanceMethod1:@selector(reloadSections:) method2:@selector(xtfei_reloadSections:)];
[self exchangeInstanceMethod1:@selector(insertItemsAtIndexPaths:) method2:@selector(xtfei_insertItemsAtIndexPaths:)];
[self exchangeInstanceMethod1:@selector(deleteItemsAtIndexPaths:) method2:@selector(xtfei_deleteItemsAtIndexPaths:)];
[self exchangeInstanceMethod1:@selector(reloadItemsAtIndexPaths:) method2:@selector(xtfei_reloadItemsAtIndexPaths:)];
}
- (void)xtfei_reloadData
{
[self xtfei_reloadData];
[self getDataAndSet];
}
- (void)xtfei_insertSections:(NSIndexSet *)sections
{
[self xtfei_insertSections:sections];
[self getDataAndSet];
}
- (void)xtfei_deleteSections:(NSIndexSet *)sections
{
[self xtfei_deleteSections:sections];
[self getDataAndSet];
}
- (void)xtfei_reloadSections:(NSIndexSet *)sections
{
[self xtfei_reloadSections:sections];
[self getDataAndSet];
}
- (void)xtfei_insertItemsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths
{
[self xtfei_insertItemsAtIndexPaths:indexPaths];
[self getDataAndSet];
}
- (void)xtfei_deleteItemsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths
{
[self xtfei_deleteItemsAtIndexPaths:indexPaths];
[self getDataAndSet];
}
- (void)xtfei_reloadItemsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths
{
[self xtfei_reloadItemsAtIndexPaths:indexPaths];
[self getDataAndSet];
}
@end
@@ -0,0 +1,27 @@
//
// TFFiltrateCollectionView.h
// TFReader
//
// Created by 谢腾飞 on 2020/12/25.
// Copyright © 2020 xtfei_2011@126.com. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "TFSortModel.h"
NS_ASSUME_NONNULL_BEGIN
@protocol TFFiltrateCollectionViewDelegate <NSObject>
- (void)selectFiltrateViewWithIndexPath:(NSIndexPath *)indexPath selectKeyword:(NSString *)keyword selectValue:(NSString *)value;
@end
@interface TFFiltrateCollectionView : UIView
@property (nonatomic ,strong) TFSearchBoxModel *searchModel;
@property (nonatomic ,weak) id <TFFiltrateCollectionViewDelegate> delegate;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,135 @@
//
// TFFiltrateCollectionView.m
// TFReader
//
// Created by 谢腾飞 on 2020/12/25.
// Copyright © 2020 xtfei_2011@126.com. All rights reserved.
//
#import "TFFiltrateCollectionView.h"
#import "TFFiltrateItemViewCell.h"
@interface TFFiltrateCollectionView ()<UICollectionViewDataSource, UICollectionViewDelegate>
@property (nonatomic ,strong) UICollectionViewFlowLayout *flowLayout;
@property (nonatomic ,strong) UICollectionView *collectionView;
@end
@implementation TFFiltrateCollectionView
- (instancetype)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame]) {
self.backgroundColor = [UIColor whiteColor];
[self createSubViews];
}
return self;
}
- (void)createSubViews
{
[self addSubview:self.collectionView];
[self.collectionView mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.mas_equalTo(self);
}];
UIView *line = [[UIView alloc] init];
line.backgroundColor = kGrayLineColor;
[self addSubview:line];
[line mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(kHalfMargin);
make.bottom.equalTo(self.collectionView);
make.width.mas_equalTo(self.mas_width);
make.height.mas_equalTo(kCellLineHeight);
}];
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
return self.searchModel.searchList.count;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
TFOptionListModel *t_list = [self.searchModel.searchList objectOrNilAtIndex:indexPath.row];
TFFiltrateItemViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"TFFiltrateItemViewCell" forIndexPath:indexPath];
cell.optionModel = t_list;
return cell;
}
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
TFOptionListModel *t_list = [self.searchModel.searchList objectOrNilAtIndex:indexPath.row];
if ([self.delegate respondsToSelector:@selector(selectFiltrateViewWithIndexPath:selectKeyword:selectValue:)]) {
[self.delegate selectFiltrateViewWithIndexPath:indexPath selectKeyword:self.searchModel.field selectValue:t_list.value];
}
}
- (void)setSearchModel:(TFSearchBoxModel *)searchModel
{
#if !TF_Free_Mode
NSMutableArray *t_array = [searchModel.searchList mutableCopy];
for (TFOptionListModel *t_model in t_array) {
if ([t_model.display isEqualToString:TFLocalizedString(@"免费")]) {
[t_array removeObject:t_model];
break;
}
}
searchModel.searchList = [t_array copy];
#elif !TF_Super_Member_Mode
NSMutableArray *t_array = [searchModel.searchList mutableCopy];
for (TFOptionListModel *t_model in t_array) {
if ([t_model.display isEqualToString:TFLocalizedString(@"会员")]) {
[t_array removeObject:t_model];
break;
}
}
searchModel.searchList = [t_array copy];
#endif
_searchModel = searchModel;
[self.collectionView reloadData];
}
- (UICollectionView *)collectionView
{
if (!_collectionView) {
_collectionView = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:self.flowLayout];
_collectionView.userInteractionEnabled = YES;
_collectionView.backgroundColor = [UIColor clearColor];
_collectionView.showsVerticalScrollIndicator = NO;
_collectionView.showsHorizontalScrollIndicator = NO;
_collectionView.alwaysBounceHorizontal = YES;
_collectionView.delegate = self;
_collectionView.dataSource = self;
[_collectionView registerClass:[TFFiltrateItemViewCell class] forCellWithReuseIdentifier:@"TFFiltrateItemViewCell"];
if (@available(iOS 11.0, *)) {
_collectionView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
}
}
return _collectionView;
}
- (UICollectionViewFlowLayout *)flowLayout
{
if (!_flowLayout) {
_flowLayout = [[UICollectionViewFlowLayout alloc] init];
_flowLayout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
_flowLayout.estimatedItemSize = CGSizeMake(30, 30);
_flowLayout.minimumLineSpacing = kQuarterMargin;
_flowLayout.sectionInset = UIEdgeInsetsMake(0, kQuarterMargin, 0, kQuarterMargin);
}
return _flowLayout;
}
@end
@@ -0,0 +1,20 @@
//
// TFFiltrateItemViewCell.h
// TFReader
//
// Created by 谢腾飞 on 2020/12/25.
// Copyright © 2020 xtfei_2011@126.com. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "TFSortModel.h"
NS_ASSUME_NONNULL_BEGIN
@interface TFFiltrateItemViewCell : UICollectionViewCell
@property (nonatomic ,strong) TFOptionListModel *optionModel;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,88 @@
//
// TFFiltrateItemViewCell.m
// TFReader
//
// Created by 谢腾飞 on 2020/12/25.
// Copyright © 2020 xtfei_2011@126.com. All rights reserved.
//
#import "TFFiltrateItemViewCell.h"
@interface TFFiltrateItemViewCell ()
@property (nonatomic ,strong) UILabel *titleLabel;
@end
@implementation TFFiltrateItemViewCell
- (instancetype)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame]) {
self.backgroundColor = [UIColor clearColor];
self.titleLabel = [[UILabel alloc] init];
self.titleLabel.backgroundColor = [UIColor whiteColor];
self.titleLabel.textColor = kBlackColor;
self.titleLabel.textAlignment = NSTextAlignmentCenter;
self.titleLabel.font = kFont12;
self.titleLabel.userInteractionEnabled = YES;
self.titleLabel.numberOfLines = 1;
self.titleLabel.layer.cornerRadius = 10;
self.titleLabel.layer.borderWidth = 0.5;
self.titleLabel.layer.borderColor = kWhiteColor.CGColor;
self.titleLabel.clipsToBounds = YES;
[self.contentView addSubview:self.titleLabel];
[self.titleLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.mas_equalTo(self.contentView.mas_top).with.offset(kQuarterMargin);
make.left.mas_equalTo(self.contentView.mas_left).with.offset(kQuarterMargin);
make.width.mas_equalTo(30);
make.bottom.mas_equalTo(self.contentView.mas_bottom).with.offset(- kQuarterMargin);
make.right.mas_equalTo(self.contentView.mas_right).with.offset(- kQuarterMargin).priorityLow();
}];
}
return self;
}
- (void)setOptionModel:(TFOptionListModel *)optionModel
{
_optionModel = optionModel;
self.titleLabel.text = optionModel.display ? : @"";
[self.titleLabel mas_updateConstraints:^(MASConstraintMaker *make) {
make.width.mas_equalTo([TFViewHelper getDynamicWidthWithLabel:self.titleLabel] + kHalfMargin);
}];
if (optionModel.checked) {
[self setSelectedState];
} else {
[self setNormalState];
}
}
- (UICollectionViewLayoutAttributes *)preferredLayoutAttributesFittingAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes
{
UICollectionViewLayoutAttributes *attributes = [super preferredLayoutAttributesFittingAttributes:layoutAttributes];
attributes.frame = CGRectMake(0, 0, [TFViewHelper getDynamicWidthWithLabel:self.titleLabel] + kHalfMargin, self.height);
return attributes;
}
- (void)setSelectedState
{
self.titleLabel.textColor = kMainColor;
self.titleLabel.backgroundColor = [UIColor whiteColor];
self.titleLabel.layer.borderColor = kMainColor.CGColor;
self.titleLabel.layer.borderWidth = 0.5;
}
- (void)setNormalState
{
self.titleLabel.textColor = kBlackColor;
self.titleLabel.backgroundColor = [UIColor whiteColor];
self.titleLabel.layer.borderColor = [UIColor whiteColor].CGColor;
self.titleLabel.layer.borderWidth = 0.5;
}
@end
@@ -0,0 +1,24 @@
//
// TFFiltrateView.h
// TFReader
//
// Created by 谢腾飞 on 2020/12/25.
// Copyright © 2020 xtfei_2011@126.com. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "TFSortModel.h"
#import "TFFiltrateCollectionView.h"
NS_ASSUME_NONNULL_BEGIN
typedef void(^TFFiltrateViewSelectBlock)(NSString *keyword, NSString *value);
@interface TFFiltrateView : UIView
@property (nonatomic ,strong) NSArray <TFSearchBoxModel *>*search_box;
@property (nonatomic ,copy) TFFiltrateViewSelectBlock selectBlock;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,58 @@
//
// TFFiltrateView.m
// TFReader
//
// Created by 谢腾飞 on 2020/12/25.
// Copyright © 2020 xtfei_2011@126.com. All rights reserved.
//
#import "TFFiltrateView.h"
#define SearchBarHeight 40
@interface TFFiltrateView ()<TFFiltrateCollectionViewDelegate>
@property (nonatomic ,strong) NSMutableArray *filtrateViewArray;
@end
@implementation TFFiltrateView
- (instancetype)init
{
if (self = [super init]) {
}
return self;
}
- (void)selectFiltrateViewWithIndexPath:(NSIndexPath *)indexPath selectKeyword:(NSString *)keyword selectValue:(NSString *)value
{
if (self.selectBlock) {
self.selectBlock(keyword, value);
}
}
- (void)setSearch_box:(NSArray<TFSearchBoxModel *> *)search_box
{
_search_box = search_box;
if (!self.filtrateViewArray) {
self.filtrateViewArray = [NSMutableArray array];
for (NSInteger i = 0; i < self.search_box.count; i ++) {
TFFiltrateCollectionView *filtrateCollectionView = [[TFFiltrateCollectionView alloc] initWithFrame:CGRectMake(0, SearchBarHeight * i, SCREEN_WIDTH, SearchBarHeight)];
filtrateCollectionView.searchModel = [search_box objectOrNilAtIndex:i];
filtrateCollectionView.delegate = self;
[self addSubview:filtrateCollectionView];
[self.filtrateViewArray addObject:filtrateCollectionView];
}
} else {
for (NSInteger i = 0; i < self.filtrateViewArray.count; i++) {
TFFiltrateCollectionView *filtrateCollectionView = [self.filtrateViewArray objectOrNilAtIndex:i];
filtrateCollectionView.searchModel = [search_box objectOrNilAtIndex:i];
}
}
}
@end
@@ -0,0 +1,47 @@
//
// TFLanguageManager.h
// WXReader
//
// Created by 谢腾飞 on 2020/11/20.
// Copyright © 2020 xtfei_2011@126.com. All rights reserved.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
typedef NS_ENUM(NSUInteger, TFLanguageType) {
TFLanguageTypeDefault = 0, /** 跟随系统 */
TFLanguageTypeSimplifiedChinese = 1, /** 简体中文 */
TFLanguageTypeTraditionalChinese = 2, /** 繁体中文 */
TFLanguageTypeEnglish = 3, /** 英文 */
TFLanguageTypeTail = 4, /** 泰语 */
};
#define TFLocalizedString(key) [TFLanguageManager stringWithKey:key]
@interface TFLanguageManager : NSObject
// 与服务端的约定字段(上传服务端时使用)
+ (NSString *)serverLocalized;
// 获取当前语言包中的文字
// @param key 需要翻译的文字
+ (NSString *)stringWithKey:(NSString *)key;
// 获取指定语言环境下的文字
// @param key 需要翻译的文字
// @param language 语言类型
+ (NSString *)stringWithKey:(NSString *)key languageType:(TFLanguageType)type;
// 获取用户本地语言
+ (TFLanguageType)userLanguage;
// 获取APP本地语言,如果没有找到则返回默认语言
+ (TFLanguageType)localizedLanguage;
// 设置APP语言
+ (void)setLanguageType:(TFLanguageType)type;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,162 @@
//
// TFLanguageManager.m
// WXReader
//
// Created by 谢腾飞 on 2020/11/20.
// Copyright © 2020 xtfei_2011@126.com. All rights reserved.
//
#import "TFLanguageManager.h"
#import "WXYZ_TouchAssistantView.h"
static NSString *kUserLanguage = @"kUserLanguage";
@interface TFLanguageManager ()
// 语言包 Bundle
@property (nonatomic ,strong ,class) NSBundle *languageBundle;
// 本地语言名称
@property (nonatomic ,strong ,class) NSString *userLanguageName;
@end
@implementation TFLanguageManager
@dynamic languageBundle;
@dynamic userLanguageName;
static NSBundle *_languageBundle = nil;
+ (NSString *)stringWithKey:(NSString *)key
{
if (self.languageBundle) {
return [self.languageBundle localizedStringForKey:key value:nil table:@"InfoPlist"] ?: key;
} else {
return NSLocalizedString(key, nil);
}
}
+ (void)setLanguageType:(TFLanguageType)type
{
[[NSUserDefaults standardUserDefaults] setValue:type == TFLanguageTypeDefault ? nil : NSStringFromLocalizedLanguage(type) forKey:kUserLanguage];
_languageBundle = nil;
[[WXYZ_TouchAssistantView sharedManager] closeButtonClick];
[[NSNotificationCenter defaultCenter] postNotificationName:Notification_Switch_Language object:nil];
}
+ (NSString *)stringWithKey:(NSString *)key languageType:(TFLanguageType)type
{
if (type == TFLanguageTypeDefault) {
return TFLocalizedString(key);
}
NSString *t_language = NSStringFromLocalizedLanguage(type);
NSString *path = [[NSBundle mainBundle] pathForResource:t_language ofType:@"lproj"];
NSBundle *languageBundle = [NSBundle bundleWithPath:path];
if (languageBundle) {
return [languageBundle localizedStringForKey:key value:nil table:@"InfoPlist"] ? : key;
} else {
return NSLocalizedString(key, nil);
}
}
+ (TFLanguageType)userLanguage
{
return TFLanguageTypeFromNSString([[NSUserDefaults standardUserDefaults] valueForKey:kUserLanguage]);
}
+ (TFLanguageType)localizedLanguage
{
TFLanguageType language = TFLanguageTypeFromNSString([[NSUserDefaults standardUserDefaults] valueForKey:kUserLanguage]);
NSString *t_launguage = NSStringFromLocalizedLanguage(language);
return TFLanguageTypeFromNSString(t_launguage);
}
+ (NSString *)serverLocalized
{
return [self serverLanguageType:[self localizedLanguage]];
}
+ (NSString *)serverLanguageType:(TFLanguageType)type
{
switch (type) {
case TFLanguageTypeSimplifiedChinese:
return @"zh";
case TFLanguageTypeTraditionalChinese:
return @"tw";
case TFLanguageTypeEnglish:
return @"en";
case TFLanguageTypeTail:
return @"th";
case TFLanguageTypeDefault: {
NSString *t_str = NSStringFromLocalizedLanguage(type);
TFLanguageType t_language = TFLanguageTypeFromNSString(t_str);
return [self serverLanguageType:t_language];
}
}
}
// 根据语言名称字符串获取指定语言枚举类型
TFLanguageType TFLanguageTypeFromNSString(NSString *language) {
if ([language hasPrefix:@"en"]) { // 英文
return TFLanguageTypeEnglish;
}
if ([language hasPrefix:@"zh-Hans"]) { // 简体中文
return TFLanguageTypeSimplifiedChinese;
}
if ([language hasPrefix:@"zh-Hant"]) { // 繁体中文
return TFLanguageTypeTraditionalChinese;
}
if ([language hasPrefix:@"th"]) { // 泰语
return TFLanguageTypeTail;
}
return TFLanguageTypeDefault;
}
// 根据语言枚举类型获取指定语言名称(如果匹配不到则会返回默认语言)
NSString * NSStringFromLocalizedLanguage(TFLanguageType language) {
switch (language) {
case TFLanguageTypeEnglish:
return @"en";
case TFLanguageTypeSimplifiedChinese:
return @"zh-Hans";
case TFLanguageTypeTraditionalChinese:
return @"zh-Hant";
case TFLanguageTypeTail:
return @"th";
case TFLanguageTypeDefault: {
NSArray *languages = [NSLocale preferredLanguages];
TFLanguageType t_language = TFLanguageTypeFromNSString(languages.firstObject);
if (t_language == TFLanguageTypeDefault) return TF_Default_Language;
return NSStringFromLocalizedLanguage(t_language);
}
}
}
+ (NSBundle *)languageBundle
{
if (!_languageBundle) {
NSString *path = [[NSBundle mainBundle] pathForResource:self.userLanguageName ofType:@"lproj"];
_languageBundle = [NSBundle bundleWithPath:path];
}
return _languageBundle;
}
+ (NSString *)userLanguageName
{
NSString *userLanguage = [[NSUserDefaults standardUserDefaults] valueForKey:kUserLanguage];
if (userLanguage) return userLanguage;
return NSStringFromLocalizedLanguage(TFLanguageTypeDefault);
}
@end
@@ -0,0 +1,19 @@
//
// TFNightModeView.h
// TFReader
//
// Created by 谢腾飞 on 2020/12/23.
// Copyright © 2020 xtfei_2011@126.com. All rights reserved.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface TFNightModeView : UIView
interface_singleton
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,30 @@
//
// TFNightModeView.m
// TFReader
//
// Created by 谢腾飞 on 2020/12/23.
// Copyright © 2020 xtfei_2011@126.com. All rights reserved.
//
#import "TFNightModeView.h"
@implementation TFNightModeView
implementation_singleton(TFNightModeView)
- (instancetype)init
{
if (self = [super init]) {
self.frame = CGRectMake(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
self.backgroundColor = kBlackTransparentColor;
self.userInteractionEnabled = YES;
}
return self;
}
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
return nil;
}
@end
@@ -0,0 +1,42 @@
//
// TFPromptManager.h
// TFReader
//
// Created by 谢腾飞 on 2020/12/10.
// Copyright © 2020 xtfei_2011@126.com. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "TFPromptView.h"
NS_ASSUME_NONNULL_BEGIN
@interface TFPromptManager : NSObject
+ (void)showPromptViewWithStatus:(TFPromptStatus)status promptTitle:(NSString *)promptTitle;
+ (void)showLimitPromptViewWithStatus:(TFPromptStatus)status promptTitle:(NSString *)promptTitle;
+ (void)showPromptViewWithStatus:(TFPromptStatus)status promptTitle:(NSString *)promptTitle duration:(CGFloat)duration;
+ (void)showPromptViewWithStatus:(TFPromptStatus)status promptTitle:(NSString *)promptTitle completionHandler:(TFPromptDissmissBlock)completionHandler;
+ (void)showPromptViewWithStatus:(TFPromptStatus)status promptTitle:(NSString *)promptTitle duration:(CGFloat)duration completionHandler:(TFPromptDissmissBlock)completionHandler;
+ (void)showPromptWithError:(NSError *)error defaultText:(NSString * _Nullable)text;
+ (void)showPromptViewWithPromptMaskType:(TFPromptMaskType)type loadingStyle:(TFPromptStyle)style;
+ (void)stopAnimating;
+ (void)hiddenAlert;
// 展示小说阅读器Loading(仅限小说阅读器使用)
+ (UIView *)showLoading:(UIView * _Nullable)rootView;
// 隐藏小说阅读器Loading(仅限小说阅读器使用)
+ (void)hideLoading;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,210 @@
//
// TFPromptManager.m
// TFReader
//
// Created by 谢腾飞 on 2020/12/10.
// Copyright © 2020 xtfei_2011@126.com. All rights reserved.
//
#import "TFPromptManager.h"
#import "TFReaderSettingHelper.h"
#import "UIImage+Color.h"
#define Alert_Duration 1.0f
static TFPromptView *_promptView = nil;
@implementation TFPromptManager
static NSDictionary *_errorInfo;
+ (void)showPromptWithError:(NSError *)error defaultText:(NSString * _Nullable)text
{
_errorInfo = _errorInfo ?: [NSDictionary dictionaryWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"ErrorCode" ofType:@"plist"]];
NSString *_text = _errorInfo[[NSString stringWithFormat:@"%zd", error.code]];
_text = _text ?: (error.localizedDescription ?: (text ?: TFLocalizedString(@"请求失败")));
[self showPromptViewWithStatus:TFPromptStatusError promptTitle:_text];
}
+ (void)showPromptViewWithStatus:(TFPromptStatus)status promptTitle:(NSString *)promptTitle
{
[self showPromptViewWithStatus:status promptTitle:promptTitle duration:Alert_Duration];
}
+ (void)showLimitPromptViewWithStatus:(TFPromptStatus)status promptTitle:(NSString *)promptTitle
{
[self showPromptViewWithStatus:status promptTitle:promptTitle duration:Alert_Duration];
}
+ (void)showPromptViewWithStatus:(TFPromptStatus)status promptTitle:(NSString *)promptTitle duration:(CGFloat)duration
{
[self showPromptViewWithStatus:status promptTitle:promptTitle duration:duration completionHandler:nil];
}
+ (void)showPromptViewWithStatus:(TFPromptStatus)status promptTitle:(NSString *)promptTitle completionHandler:(TFPromptDissmissBlock)completionHandler
{
[self showPromptViewWithStatus:status promptTitle:promptTitle duration:Alert_Duration completionHandler:completionHandler];
}
+ (void)showPromptViewWithStatus:(TFPromptStatus)status promptTitle:(NSString *)promptTitle duration:(CGFloat)duration completionHandler:(TFPromptDissmissBlock)completionHandler
{
if (!promptTitle || promptTitle.length == 0) {
return;
}
if ([_promptView.promptTitle isEqualToString:promptTitle]) {
return;
}
if (_promptView) {
[_promptView hiddenPromptView];
}
if (status == TFPromptStatusLoading) {
duration = 30;
}
dispatch_async(dispatch_get_main_queue(), ^{
WS(weakSelf)
_promptView = [[TFPromptView alloc] init];
_promptView.promptTitle = promptTitle;
_promptView.promptStatus = status;
_promptView.alertDuration = duration;
_promptView.alertDissmissBlock = ^{
[weakSelf hiddenAlert];
if (completionHandler) {
completionHandler();
}
};
[_promptView showPromptView];
});
}
static UIActivityIndicatorView *activityIndicator;
UIView *maskView;
+ (void)showPromptViewWithPromptMaskType:(TFPromptMaskType)type loadingStyle:(TFPromptStyle)style
{
if (@available(iOS 13.0, *)) {
activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleLarge];
} else {
activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
}
activityIndicator.center = [self currentWindow].center;
// 设置动画颜色
switch (style) {
case TFPromptStyleLight: {
activityIndicator.color = [UIColor whiteColor];
}
break;
case TFPromptStyleDark: {
activityIndicator.color = [UIColor darkGrayColor];
}
break;
}
// 设置动画的蒙层
switch (type) {
case TFPromptMaskTypeNone: {
[[self currentWindow] addSubview:activityIndicator];
}
break;
case TFPromptMaskTypeBlack:
case TFPromptMaskTypeClear: {
maskView = [[UIView alloc] initWithFrame:[UIScreen mainScreen].bounds];
maskView.backgroundColor = type == TFPromptMaskTypeBlack ? [UIColor colorWithRed:0.0 green:0.0 blue:0.0 alpha:0.5] : [UIColor clearColor];
[[self currentWindow] addSubview:maskView];
[[self currentWindow] addSubview:activityIndicator];
}
break;
}
[activityIndicator startAnimating];
}
+ (void)stopAnimating
{
[activityIndicator stopAnimating];
if (maskView.superview) {
[maskView removeFromSuperview];
maskView = nil;
}
}
+ (void)hiddenAlert
{
if (_promptView.isShowing) {
[_promptView hiddenPromptView];
_promptView = nil;
}
}
+ (UIWindow *)currentWindow
{
if (__IPHONE_13_0) {
return [UIApplication sharedApplication].windows.firstObject;
} else {
return [UIApplication sharedApplication].keyWindow;
}
}
static UIView *_loadingView;
+ (UIView *)showLoading:(UIView *)rootView
{
UIView *mainView = [[UIView alloc] init];
_loadingView = mainView;
mainView.backgroundColor = kColorRGBA(0, 0, 0, 0);
if (rootView) {
[rootView addSubview:mainView];
} else {
[kMainWindow addSubview:mainView];
}
[mainView mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(mainView.superview);
}];
NSMutableArray<YYImage *> *arr = [NSMutableArray array];
for (int i = 0; i < 47; i++) {
YYImage *image = [YYImage imageNamed:[NSString stringWithFormat:@"%@%d", @"loading", i]];
image = [image imageWithColor:[[[TFReaderSettingHelper sharedManager] getReaderTextColor] colorWithAlphaComponent:0.75]];
[arr addObject:image];
}
YYAnimatedImageView *loadingView = [[YYAnimatedImageView alloc] init];
loadingView.backgroundColor = [UIColor clearColor];
loadingView.animationImages = arr;
loadingView.animationDuration = 2.0;
[loadingView startAnimating];
[mainView addSubview:loadingView];
[loadingView mas_makeConstraints:^(MASConstraintMaker *make) {
make.center.equalTo(mainView);
make.width.mas_equalTo(90);
make.height.equalTo(loadingView.mas_width).multipliedBy(150.0 / 240.0);
}];
__weak typeof(loadingView) weakView = loadingView;
[NSTimer scheduledTimerWithTimeInterval:loadingView.animationDuration block:^(NSTimer * _Nonnull timer) {
if (!weakView.superview) {
[timer invalidate];
timer = nil;
}
NSArray *t_arr = [[loadingView.animationImages reverseObjectEnumerator] allObjects];
weakView.animationImages = t_arr;
} repeats:YES];
return mainView;
}
+ (void)hideLoading
{
[_loadingView removeFromSuperview];
}
@end
@@ -0,0 +1,53 @@
//
// TFPromptView.h
// TFReader
//
// Created by 谢腾飞 on 2020/12/10.
// Copyright © 2020 xtfei_2011@126.com. All rights reserved.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
typedef NS_ENUM(NSUInteger, TFPromptStatus) {
TFPromptStatusSuccess,
TFPromptStatusError,
TFPromptStatusLoading
};
typedef NS_ENUM(NSUInteger, TFPromptMaskType) {
TFPromptMaskTypeNone = 0, // 默认类型,PromptView 显示时可以响应用户交互事件。
TFPromptMaskTypeClear = 1, // 不响应用户交互事件,背景透明。
TFPromptMaskTypeBlack = 2, // 不响应用户交互事件,背景调暗。
};
typedef NS_ENUM(NSUInteger, TFPromptStyle) {
TFPromptStyleLight = 0, // 默认样式,白色的转圈动画。
TFPromptStyleDark = 1, // 灰色的转圈动画。
};
typedef void(^ _Nullable TFPromptDissmissBlock)(void);
@interface TFPromptView : UIView
@property (nonatomic ,copy) NSString *promptTitle;
@property (nonatomic ,assign) TFPromptStatus promptStatus;
@property (nonatomic ,assign) CGFloat alertDuration;
@property (nonatomic ,assign) BOOL showMask;
@property (nonatomic ,assign) BOOL isShowing;
@property (nonatomic ,copy) TFPromptDissmissBlock alertDissmissBlock;
- (void)showPromptView;
- (void)hiddenPromptView;
- (void)removePromptView;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,199 @@
//
// TFPromptView.m
// TFReader
//
// Created by 谢腾飞 on 2020/12/10.
// Copyright © 2020 xtfei_2011@126.com. All rights reserved.
//
#import "TFPromptView.h"
@interface TFPromptView ()
@property (nonatomic ,strong) UIView *promptBottonView;
@property (nonatomic ,strong) UILabel *promptTitleLabel;
@property (nonatomic ,strong) UIImageView *promptImageView;
@property (nonatomic ,strong) UIActivityIndicatorView *indicatorView;
// 下滑返回手势
@property (nonatomic ,strong) UISwipeGestureRecognizer *recognizer;
@end
@implementation TFPromptView
- (instancetype)init
{
if (self = [super init]) {
self.userInteractionEnabled = YES;
self.backgroundColor = kColorRGBA(0, 0, 0, 0.0);
self.frame = CGRectMake(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
[kMainWindow addSubview:self];
[self createSubViews];
self.recognizer = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(recognizerHandle:)];
[self.recognizer setDirection:(UISwipeGestureRecognizerDirectionUp)];
[self addGestureRecognizer:self.recognizer];
}
return self;
}
- (void)createSubViews
{
self.promptBottonView.frame = CGRectMake(0, - PUB_NAVBAR_HEIGHT, SCREEN_WIDTH, PUB_NAVBAR_HEIGHT);
[self addSubview:self.promptBottonView];
[self.promptBottonView addSubview:self.promptImageView];
[self.promptImageView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(kMargin);
make.bottom.mas_equalTo(- kMargin);
make.width.mas_equalTo(20);
make.height.mas_equalTo(20);
}];
[self.promptBottonView addSubview:self.promptTitleLabel];
[self.promptTitleLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(self.promptImageView.mas_right).with.offset(kHalfMargin);
make.centerY.mas_equalTo(self.promptImageView.mas_centerY);
make.right.mas_equalTo(self.mas_right).with.offset(- kHalfMargin);
make.height.mas_equalTo(30);
}];
self.indicatorView.hidden = YES;
[self.promptBottonView addSubview:self.indicatorView];
[self.indicatorView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(kMargin);
make.bottom.mas_equalTo(- kMargin);
make.width.mas_equalTo(20);
make.height.mas_equalTo(20);
}];
}
// 处理手势
- (void)recognizerHandle:(UISwipeGestureRecognizer *)recognizer
{
if(recognizer.direction == UISwipeGestureRecognizerDirectionUp) {
[self hiddenPromptView:0];
}
}
- (void)showPromptView
{
[UIView animateWithDuration:kAnimatedDuration delay:0 usingSpringWithDamping:0.8 initialSpringVelocity:0.0 options:UIViewAnimationOptionShowHideTransitionViews animations:^{
self.promptBottonView.frame = CGRectMake(0, 0, SCREEN_WIDTH, PUB_NAVBAR_HEIGHT);
if (self.showMask) {
self.backgroundColor = kBlackTransparentColor;
}
} completion:^(BOOL finished) {
WS(weakSelf)
self.isShowing = YES;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(self.alertDuration * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[weakSelf hiddenPromptView];
});
}];
}
- (void)hiddenPromptView
{
[self hiddenPromptView:1];
}
- (void)hiddenPromptView:(CGFloat)delay
{
dispatch_async(dispatch_get_main_queue(), ^{
[UIView animateWithDuration:kAnimatedDuration delay:delay usingSpringWithDamping:0.8 initialSpringVelocity:0.0 options:UIViewAnimationOptionShowHideTransitionViews animations:^{
self.promptBottonView.frame = CGRectMake(0, - PUB_NAVBAR_HEIGHT, SCREEN_WIDTH, PUB_NAVBAR_HEIGHT);
if (self.showMask) {
self.backgroundColor = kColorRGBA(0, 0, 0, 0.01);
}
} completion:^(BOOL finished) {
[self removePromptView];
self.isShowing = NO;
}];
});
}
- (void)removePromptView
{
if (self.alertDissmissBlock) {
self.alertDissmissBlock();
}
[self removeAllSubviews];
[self removeFromSuperview];
}
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
UIView *hitView = [super hitTest:point withEvent:event];
if (hitView == self && !self.showMask) {
return nil;
}
return hitView;
}
- (void)setPromptTitle:(NSString *)promptTitle
{
_promptTitle = promptTitle;
self.promptTitleLabel.text = [TFUtilsHelper formatStringWithObject:promptTitle];
}
- (void)setPromptStatus:(TFPromptStatus)promptStatus
{
if (promptStatus == TFPromptStatusSuccess) {
self.promptImageView.image = [UIImage imageNamed:@"tips_success"];
}
if (promptStatus == TFPromptStatusError) {
self.promptImageView.image = [UIImage imageNamed:@"tips_error"];
}
if (promptStatus == TFPromptStatusLoading) {
self.indicatorView.hidden = NO;
[self.indicatorView startAnimating];
self.showMask = YES;
}
}
- (UIView *)promptBottonView
{
if (!_promptBottonView) {
_promptBottonView = [[UIView alloc] init];
_promptBottonView.backgroundColor = kWhiteColor;
}
return _promptBottonView;
}
- (UIImageView *)promptImageView
{
if (!_promptImageView) {
_promptImageView = [[UIImageView alloc] init];
}
return _promptImageView;
}
- (UILabel *)promptTitleLabel
{
if (!_promptTitleLabel) {
_promptTitleLabel = [[UILabel alloc] init];
_promptTitleLabel.textAlignment = NSTextAlignmentLeft;
_promptTitleLabel.textColor = kBlackColor;
_promptTitleLabel.font = kMainFont;
}
return _promptTitleLabel;
}
- (UIActivityIndicatorView *)indicatorView
{
if (!_indicatorView) {
_indicatorView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:(UIActivityIndicatorViewStyleGray)];
_indicatorView.color = kBlackColor;
_indicatorView.hidesWhenStopped = YES;
}
return _indicatorView;
}
@end
@@ -0,0 +1,17 @@
//
// TFRefreshFooter.h
// TFReader
//
// Created by 谢腾飞 on 2020/12/21.
// Copyright © 2020 xtfei_2011@126.com. All rights reserved.
//
#import <MJRefresh/MJRefresh.h>
NS_ASSUME_NONNULL_BEGIN
@interface TFRefreshFooter : MJRefreshAutoNormalFooter
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,23 @@
//
// TFRefreshFooter.m
// TFReader
//
// Created by 谢腾飞 on 2020/12/21.
// Copyright © 2020 xtfei_2011@126.com. All rights reserved.
//
#import "TFRefreshFooter.h"
@implementation TFRefreshFooter
- (void)prepare
{
[super prepare];
self.stateLabel.textColor = [UIColor grayColor];
self.stateLabel.textColor = kGrayTextColor;
self.stateLabel.font = kFont12;
[self setTitle:TFLocalizedString(@"没有更多了") forState:MJRefreshStateNoMoreData];
}
@end
@@ -0,0 +1,17 @@
//
// TFRefreshHeader.h
// TFReader
//
// Created by 谢腾飞 on 2020/12/21.
// Copyright © 2020 xtfei_2011@126.com. All rights reserved.
//
#import <MJRefresh/MJRefresh.h>
NS_ASSUME_NONNULL_BEGIN
@interface TFRefreshHeader : MJRefreshNormalHeader
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,24 @@
//
// TFRefreshHeader.m
// TFReader
//
// Created by 谢腾飞 on 2020/12/21.
// Copyright © 2020 xtfei_2011@126.com. All rights reserved.
//
#import "TFRefreshHeader.h"
@implementation TFRefreshHeader
/*** 初始化 ***/
- (void)prepare
{
[super prepare];
self.automaticallyChangeAlpha = YES;
self.lastUpdatedTimeLabel.textColor = [UIColor grayColor];
self.stateLabel.textColor = [UIColor grayColor];
self.stateLabel.font = self.lastUpdatedTimeLabel.font = kFont12;
}
@end
@@ -0,0 +1,30 @@
//
// UIScrollView+TFRefresh.h
// TFReader
//
// Created by 谢腾飞 on 2020/12/21.
// Copyright © 2020 xtfei_2011@126.com. All rights reserved.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface UIScrollView (TFRefresh)
- (void)showRefreshFooter;
- (void)showRefreshHeader;
- (void)hideRefreshFooter;
- (void)hideRefreshHeader;
- (void)endRefreshing;
/// 底部“没有更多了”的文字控件
- (UILabel *)titleLabel;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,58 @@
//
// UIScrollView+TFRefresh.m
// TFReader
//
// Created by 谢腾飞 on 2020/12/21.
// Copyright © 2020 xtfei_2011@126.com. All rights reserved.
//
#import "UIScrollView+TFRefresh.h"
#import "TFRefreshHeader.h"
#import "TFRefreshFooter.h"
@implementation UIScrollView (TFRefresh)
static UILabel *_titleLabel;
- (UILabel *)titleLabel
{
return _titleLabel;
}
- (void)showRefreshHeader
{
if (self.mj_header.hidden) {
self.mj_header.hidden = NO;
}
}
- (void)showRefreshFooter
{
if (self.mj_footer.hidden) {
self.mj_footer.hidden = NO;
}
_titleLabel.hidden = YES;
self.mj_footer.state = MJRefreshStateIdle;
}
- (void)hideRefreshHeader
{
if (!self.mj_header.hidden) {
self.mj_header.hidden = YES;
}
}
- (void)hideRefreshFooter
{
_titleLabel.hidden = NO;
[self.mj_footer endRefreshingWithNoMoreData];
self.mj_footer.state = MJRefreshStateNoMoreData;
}
- (void)endRefreshing
{
[self.mj_header endRefreshing];
[self.mj_footer endRefreshing];
}
@end
@@ -0,0 +1,33 @@
//
// TFSearchBar.h
// TFReader
//
// Created by 谢腾飞 on 2020/12/24.
// Copyright © 2020 xtfei_2011@126.com. All rights reserved.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@protocol TFSearchBarDelegate <NSObject>
@optional
- (void)cancelButtonClicked;
- (void)searchButtonClickedWithSearchText:(NSString *)searchText;
- (void)searchBarCleanText;
@end
@interface TFSearchBar : UIView<UITextFieldDelegate>
@property (nonatomic ,weak) id <TFSearchBarDelegate> delegate;
@property (nonatomic ,copy) NSString *placeholderText;
@property (nonatomic ,copy) NSString *searchText;
- (void)searchBarResignFirstResponder;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,136 @@
//
// TFSearchBar.m
// TFReader
//
// Created by 谢腾飞 on 2020/12/24.
// Copyright © 2020 xtfei_2011@126.com. All rights reserved.
//
#import "TFSearchBar.h"
#define SearchBar_Height 44
#define SearchBar_Width 50
@interface TFSearchBar ()
@property (nonatomic ,strong) UITextField *searchField;
@end
@implementation TFSearchBar
- (instancetype)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame]) {
self.backgroundColor = [UIColor clearColor];
[self createSubViews];
}
return self;
}
- (void)createSubViews
{
self.searchField = [[UITextField alloc] initWithFrame:CGRectZero];
self.searchField.backgroundColor = kColorRGBA(237, 238, 238, 1);
self.searchField.clearButtonMode = UITextFieldViewModeWhileEditing;
self.searchField.font = kMainFont;
self.searchField.delegate = self;
self.searchField.textColor = kBlackColor;
self.searchField.returnKeyType = UIReturnKeySearch;
[self.searchField addTarget:self action:@selector(changeText:) forControlEvents:UIControlEventEditingChanged];
self.searchField.layer.cornerRadius = 4;
self.searchField.clipsToBounds = YES;
[self addSubview:self.searchField];
[self.searchField mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(kHalfMargin);
make.top.mas_equalTo(5);
make.right.mas_equalTo(self.mas_right).with.offset(- 2 * kHalfMargin - SearchBar_Width);
make.height.mas_equalTo(SearchBar_Height - 10);
}];
UIImageView *leftImageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 20, 20)];
leftImageView.image = [[UIImage imageNamed:@"public_search"] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
leftImageView.tintColor = kColorRGBA(156, 155, 157, 1);
UIView *leftView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 30, SearchBar_Height)];
leftView.backgroundColor = [UIColor clearColor];
[leftView addSubview:leftImageView];
leftImageView.center = leftView.center;
self.searchField.leftViewMode = UITextFieldViewModeAlways;
self.searchField.leftView = leftView;
UIButton *cancelButton = [UIButton buttonWithType:UIButtonTypeCustom];
cancelButton.backgroundColor = [UIColor clearColor];
[cancelButton setTitle:TFLocalizedString(@"取消") forState:UIControlStateNormal];
[cancelButton setTitleColor:kGrayTextColor forState:UIControlStateNormal];
[cancelButton addTarget:self action:@selector(cancelClick) forControlEvents:UIControlEventTouchUpInside];
[cancelButton.titleLabel setFont:kMainFont];
[self addSubview:cancelButton];
[cancelButton mas_makeConstraints:^(MASConstraintMaker *make) {
make.right.mas_equalTo(self.mas_right).with.offset(- kHalfMargin);
make.centerY.equalTo(self.searchField);
make.height.mas_equalTo(self.mas_height);
make.width.mas_equalTo(SearchBar_Width);
}];
}
- (void)cancelClick
{
if ([self.delegate respondsToSelector:@selector(cancelButtonClicked)]) {
[self.delegate cancelButtonClicked];
}
}
- (void)setPlaceholderText:(NSString *)placeholderText
{
_placeholderText = placeholderText;
NSAttributedString *attrString = [[NSAttributedString alloc] initWithString:placeholderText attributes:@{NSForegroundColorAttributeName:kColorRGBA(156, 155, 157, 1),NSFontAttributeName:kMainFont}];
self.searchField.attributedPlaceholder = attrString;
}
- (void)setSearchText:(NSString *)searchText
{
_searchText = searchText;
self.searchField.text = searchText;
}
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
// 如果为回车则将键盘收起
if ([string isEqualToString:@"\n"]) {
if (textField.text.length > 0) {
if ([self.delegate respondsToSelector:@selector(searchButtonClickedWithSearchText:)]) {
[self.delegate searchButtonClickedWithSearchText:self.searchField.text];
}
} else if (_placeholderText.length > 0) {
if ([self.delegate respondsToSelector:@selector(searchButtonClickedWithSearchText:)]) {
[self.delegate searchButtonClickedWithSearchText:_placeholderText];
}
}
[textField resignFirstResponder];
return NO;
}
return YES;
}
- (void)changeText:(UITextField *)sender
{
if ([sender.text isEqualToString:@""]) {
if ([self.delegate respondsToSelector:@selector(searchBarCleanText)]) {
[self.delegate searchBarCleanText];
}
}
}
- (void)searchBarResignFirstResponder
{
if ([self.searchField isFirstResponder]) {
[self.searchField resignFirstResponder];
}
}
@end
@@ -0,0 +1,21 @@
//
// TFSearchView.h
// TFReader
//
// Created by 谢腾飞 on 2020/12/24.
// Copyright © 2020 xtfei_2011@126.com. All rights reserved.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface TFSearchView : UIView
@property (nonatomic ,strong) NSArray *placeholderArray;
@property (nonatomic ,strong) NSString *currentPlaceholder;
@property (nonatomic ,assign) BOOL searchViewDarkColor;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,134 @@
//
// TFSearchView.m
// TFReader
//
// Created by 谢腾飞 on 2020/12/24.
// Copyright © 2020 xtfei_2011@126.com. All rights reserved.
//
#import "TFSearchView.h"
#import "WXYZ_GCDTimer.h"
@interface TFSearchView ()
@property (nonatomic ,strong) WXYZ_GCDTimer *timer;
@property (nonatomic ,weak) UILabel *searchTitle;
@property (nonatomic ,strong) UIImageView *searchIcon;
@end
@implementation TFSearchView
- (instancetype)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame]) {
self.backgroundColor = kColorRGBA(255, 255, 255, 0.3);
self.layer.borderColor = kColorRGBA(235, 235, 241, 1).CGColor;
self.layer.borderWidth = 0;
[self createSubViews];
}
return self;
}
- (void)createSubViews
{
self.searchIcon = [[UIImageView alloc] init];
self.searchIcon.image = [[UIImage imageNamed:@"public_search"] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
self.searchIcon.userInteractionEnabled = YES;
self.searchIcon.tintColor = kWhiteColor;
[self addSubview:self.searchIcon];
[self.searchIcon mas_makeConstraints:^(MASConstraintMaker *make) {
make.right.mas_equalTo(self.mas_right).with.offset(- kHalfMargin);
make.centerY.mas_equalTo(self.mas_centerY);
make.width.height.mas_equalTo(self.mas_height).with.multipliedBy(0.7);
}];
UILabel *searchTitle = [[UILabel alloc] init];
searchTitle.backgroundColor = [UIColor clearColor];
searchTitle.textColor = [UIColor whiteColor];
searchTitle.font = kFont12;
searchTitle.textAlignment = NSTextAlignmentLeft;
[self addSubview:searchTitle];
self.searchTitle = searchTitle;
[searchTitle mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(self.mas_left).with.offset(kHalfMargin);
make.centerY.mas_equalTo(self.mas_centerY);
make.right.mas_equalTo(self.searchIcon.mas_left).with.offset(- kHalfMargin);
make.height.mas_equalTo(self.mas_height);
}];
}
- (void)setSearchViewDarkColor:(BOOL)searchViewDarkColor
{
_searchViewDarkColor = searchViewDarkColor;
if (searchViewDarkColor) {
self.searchIcon.tintColor = kGrayTextLightColor;
self.backgroundColor = kColorRGBA(255, 255, 255, 0.0);
self.searchTitle.textColor = kGrayTextLightColor;
self.layer.borderColor = kColorRGBA(235, 235, 241, 1).CGColor;
self.layer.borderWidth = 0.6;
} else {
self.searchIcon.tintColor = kWhiteColor;
self.backgroundColor = kColorRGBA(255, 255, 255, 0.3);
self.searchTitle.textColor = [UIColor whiteColor];
self.layer.borderColor = kColorRGBA(235, 235, 241, 1).CGColor;
self.layer.borderWidth = 0;
}
}
- (void)setPlaceholderArray:(NSArray *)placeholderArray
{
_placeholderArray = placeholderArray;
if (placeholderArray.count > 0) {
self.searchTitle.text = [placeholderArray objectOrNilAtIndex:0];
if (placeholderArray.count > 1) {
int __block index = 0;
WS(weakSelf)
[self.timer stopTimer];
[self.timer startTimer];
self.timer.timerRunningBlock = ^(NSUInteger runTimes, CGFloat currentTime) {
dispatch_async(dispatch_get_main_queue(), ^{
weakSelf.searchTitle.text = [placeholderArray objectOrNilAtIndex:index];
if (++ index == placeholderArray.count) {
index = 0;
}
});
};
}
}
}
- (NSString *)currentPlaceholder
{
return self.searchTitle.text;
}
- (WXYZ_GCDTimer *)timer
{
if (!_timer) {
_timer = [[WXYZ_GCDTimer alloc] initCycleTimerWithInterval:10 immediatelyCallBack:YES];
}
return _timer;
}
- (void)layoutSubviews
{
[super layoutSubviews];
self.layer.cornerRadius = self.height / 2;
self.clipsToBounds = YES;
}
@end
@@ -0,0 +1,61 @@
//
// TFSliderView.h
// TFReader
//
// Created by 谢腾飞 on 2020/12/25.
// Copyright © 2020 xtfei_2011@126.com. All rights reserved.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@class TFSliderView;
@protocol TFSliderViewDelegate <NSObject>
- (void)sliderValueEndChanged:(CGFloat)endValue slider:(TFSliderView *)sender;
@end
@interface TFSliderView : UIView
@property (nonatomic ,weak) id<TFSliderViewDelegate> delegate;
// 分段式滑块 Default is NO
@property (nonatomic ,assign) BOOL stepSlider;
// 倒置返回值
@property (nonatomic ,assign) BOOL invertedValue;
// 最小值
@property (nonatomic ,assign) CGFloat minimumValue;
// 最大值
@property (nonatomic ,assign) CGFloat maximumValue;
// 滑块值
@property (nonatomic ,assign) CGFloat sliderValue;
// 左侧滑条颜色
@property (nonatomic ,strong) UIColor *minimumTintColor;
// 右侧滑条颜色
@property (nonatomic ,strong) UIColor *maximumTintColor;
// 左侧图片名称
@property (nonatomic ,copy) NSString *leftImageName;
// 右侧图片名称
@property (nonatomic ,copy) NSString *rightImageName;
// 左侧图片
@property (nonatomic ,strong) UIImage *leftImage;
// 右侧图片
@property (nonatomic ,strong) UIImage *rightImage;
- (instancetype)initWithFrame:(CGRect)frame sliderCutPointCount:(NSUInteger)cutPointCount;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,152 @@
//
// TFSliderView.m
// TFReader
//
// Created by 谢腾飞 on 2020/12/25.
// Copyright © 2020 xtfei_2011@126.com. All rights reserved.
//
#import "TFSliderView.h"
#import <TTRangeSlider/TTRangeSlider.h>
#define point_Height 4
#define ThumbImage_Height 20
@interface TFSliderView ()
@property (nonatomic ,strong) TTRangeSlider *rangeSlider;
@property (nonatomic ,assign) NSUInteger cutPointCout;
@property (nonatomic ,strong) UIImageView *leftIconView;
@property (nonatomic ,strong) UIImageView *rightIconView;
@end
@implementation TFSliderView
- (instancetype)initWithFrame:(CGRect)frame sliderCutPointCount:(NSUInteger)cutPointCount
{
if (self = [super initWithFrame:frame]) {
self.cutPointCout = cutPointCount;
[self createSubViews];
}
return self;
}
- (void)createSubViews
{
self.leftIconView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, self.bounds.size.height, self.bounds.size.height)];
self.leftIconView.bounds = CGRectMake(0, 0, CGRectGetWidth(self.leftIconView.frame) + 5, CGRectGetHeight(self.leftIconView.frame) + 5);
[self addSubview:self.leftIconView];
self.rangeSlider = [[TTRangeSlider alloc] initWithFrame:CGRectMake(0, 0, self.bounds.size.width - 2 * self.bounds.size.height, 10)];
self.rangeSlider.center = CGPointMake(self.bounds.size.width / 2, self.bounds.size.height / 2);
self.rangeSlider.handleImage = [UIImage imageNamed:@"book_menu_slider_icon"];
self.rangeSlider.minValue = CGFLOAT_MIN;
self.rangeSlider.maxValue = self.cutPointCout <= 0 ? 10 : self.cutPointCout;
self.rangeSlider.selectedMinimum = CGFLOAT_MIN;
self.rangeSlider.selectedMaximum = self.cutPointCout <= 0 ? 10 : self.cutPointCout;
self.rangeSlider.hideLabels = YES;
self.rangeSlider.disableRange = YES;
self.rangeSlider.enableStep = NO;
self.rangeSlider.step = 1;
self.rangeSlider.handleDiameter = 20;
self.rangeSlider.lineHeight = 5;
self.rangeSlider.selectedHandleDiameterMultiplier = 1.2;
self.rangeSlider.lineBackGroundColor = kGrayLineColor;
self.rangeSlider.tintColorBetweenHandles = kMainColor;
[self.rangeSlider addTarget:self action:@selector(sliderEndChanged:) forControlEvents:UIControlEventValueChanged];
[self addSubview:self.rangeSlider];
self.rightIconView = [[UIImageView alloc] initWithFrame:CGRectMake(self.bounds.size.width - self.bounds.size.height, 0, self.bounds.size.height, self.bounds.size.height)];
[self addSubview:self.rightIconView];
}
- (void)sliderEndChanged:(TTRangeSlider *)sender
{
if ([self.delegate respondsToSelector:@selector(sliderValueEndChanged:slider:)]) {
[self.delegate sliderValueEndChanged:_invertedValue ? fabs(sender.selectedMaximum - self.cutPointCout):sender.selectedMaximum slider:self];
}
}
- (void)slider:(UISlider *)sender
{
sender.value = (int)roundf(sender.value);
}
- (void)setStepSlider:(BOOL)stepSlider
{
if (stepSlider) {
self.rangeSlider.tintColorBetweenHandles = [UIColor clearColor];
self.rangeSlider.lineBackGroundColor = [UIColor clearColor];
self.rangeSlider.enableStep = YES;
NSUInteger buttonNum = self.cutPointCout + 1; // 每行多少按钮
CGFloat button_W = point_Height; // 按钮宽
CGFloat button_H = point_Height; // 按钮高
CGFloat margin_X = ThumbImage_Height / 2; // 第一个按钮的X坐标
CGFloat margin_Y = (self.rangeSlider.height - point_Height) / 2; // 第一个按钮的Y坐标
CGFloat space_X = (self.rangeSlider.width - (3 * margin_X) - point_Height * (self.cutPointCout + 1)) / self.cutPointCout; // 按钮间距
for (NSUInteger i = 0; i < self.cutPointCout + 1; i++) {
NSUInteger row = i / buttonNum; // 行号
NSUInteger loc = i % buttonNum; // 列号
CGFloat button_X = margin_X + (space_X + button_W) * loc;
CGFloat button_Y = margin_Y + button_H * row;
UIView *view = [[UIView alloc] initWithFrame:CGRectMake(button_X, button_Y, button_W, button_H)];
view.backgroundColor = [UIColor blackColor];
view.layer.cornerRadius = point_Height / 2;
view.clipsToBounds = YES;
[self.rangeSlider addSubview:view];
[self.rangeSlider sendSubviewToBack:view];
}
}
}
- (void)setMinimumTintColor:(UIColor *)minimumTintColor
{
self.rangeSlider.tintColorBetweenHandles = minimumTintColor;
}
- (void)setMaximumTintColor:(UIColor *)maximumTintColor
{
self.rangeSlider.tintColorBetweenHandles = maximumTintColor;
}
- (void)setLeftImage:(UIImage *)leftImage
{
self.leftIconView.image = leftImage;
}
- (void)setLeftImageName:(NSString *)leftImageName
{
self.leftIconView.image = [UIImage imageNamed:leftImageName];
}
- (void)setRightImage:(UIImage *)rightImage
{
self.rightIconView.image = rightImage;
}
- (void)setRightImageName:(NSString *)rightImageName
{
self.rightIconView.image = [UIImage imageNamed:rightImageName];
}
- (void)setMinimumValue:(CGFloat)minimumValue
{
self.rangeSlider.minValue = minimumValue;
}
- (void)setMaximumValue:(CGFloat)maximumValue
{
self.rangeSlider.maxValue = maximumValue;
}
- (void)setSliderValue:(CGFloat)sliderValue
{
self.rangeSlider.selectedMaximum = sliderValue;
}
@end
@@ -0,0 +1,55 @@
//
// TFTagboardView.h
// WXReader
//
// Created by 谢腾飞 on 2020/12/1.
// Copyright © 2020 xtfei_2011@126.com. All rights reserved.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
typedef NS_ENUM(NSUInteger, TFTagboardLayoutStyle) {
TFTagboardLayoutStyleNormal, // 默认情况,不换行,不截取
TFTagboardLayoutStyleScroll, // 如果当前展示内容超出最大宽度,则开启滚动
TFTagboardLayoutStyleMultLines // 如果当前展示内容超出最大宽度,则换行展示
};
typedef NS_ENUM(NSUInteger, TFTagboardTextAlignment) {
TFTagboardTextAlignmentLeft,
TFTagboardTextAlignmentRight,
TFTagboardTextAlignmentCenter
};
typedef NS_ENUM(NSUInteger, TFTagboardBorderStyle) {
TFTagboardBorderStyleBorder,
TFTagboardBorderStyleFill,
TFTagboardBorderStyleNone,
};
@interface TFTagboardView : UIView
@property (nonatomic ,strong) NSArray *tagboardArray;
// 字体
@property (nonatomic ,strong) UIFont *font;
// 圆角样式
@property (nonatomic ,assign) CGFloat cornerRadius;
// 控件间隔
@property (nonatomic ,assign) CGFloat spacing;
// 控件高度
@property (nonatomic ,assign) CGFloat viewHeight;
// 控件增加宽度
@property (nonatomic ,assign) CGFloat increaseWidth;
// 样式选择
@property (nonatomic ,assign) TFTagboardLayoutStyle layoutStyle;
@property (nonatomic ,assign) TFTagboardBorderStyle borderStyle;
@property (nonatomic ,assign) TFTagboardTextAlignment textAlignment;
@end
NS_ASSUME_NONNULL_END

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