//
|
// ViewController.m
|
// ESVideoPhoneSDKDemo
|
//
|
// Created by eTouchSky on 2019/9/27.
|
// Copyright © 2019 eTouchSky. All rights reserved.
|
//
|
|
/*
|
⚠️
|
1、编译设置,
|
BuildSettings设置 :
|
Enable Bitcode 设置为NO;
|
Other Linker Flags 设置为-Wl,-all_load。
|
2、framework
|
添加系统库 libiconv2.4.0.tbd,libz.tbd,libbz2.tbd.
|
3、demo仅做参考,具体请根据App的情况适时调用
|
*/
|
#import <AVFoundation/AVFoundation.h>
|
#import "ViewController.h"
|
#import <ESVideoPhoneSDk/ESVideoPhone.h>
|
#import <ESVideoPhoneSDk/ESError.h>
|
#import "AudioSessionHelper.h"
|
|
@interface ViewController ()<ESVideoPhoneDelegate,UITextFieldDelegate>
|
|
@property (nonatomic,strong) AudioSessionHelper *sessionHelper;
|
@property (nonatomic,strong) ESVideoPhone *es;
|
@property (nonatomic,assign) BOOL playing;
|
@property (nonatomic,assign) BOOL isInterrupt;
|
@property (nonatomic,assign) BOOL isSpeaking;
|
@property (nonatomic,strong) UIImage *snapImage; //截图
|
@property (weak, nonatomic) IBOutlet UIButton *speakerBtn;
|
@property (weak, nonatomic) IBOutlet UITextField *uIDTextField;
|
@property (weak, nonatomic) IBOutlet UIButton *mCallOrAccept;
|
@property (weak, nonatomic) IBOutlet UIButton *monitorBtn;
|
|
@end
|
|
@implementation ViewController
|
{
|
BOOL isAccessAudio;
|
BOOL isAccessVideo;
|
BOOL isBackGround;
|
BOOL iSVideoNotDetermined;
|
BOOL iSAudioNotDetermined;
|
|
|
}
|
- (void)viewDidLoad {
|
[super viewDidLoad];
|
|
//初始化中断,进入后台的tag
|
_playing = NO;
|
_isSpeaking = NO;
|
self.isInterrupt = NO;
|
_es.isInterrupt = NO;
|
isBackGround = NO;
|
[_speakerBtn setTitle:@"听筒" forState:UIControlStateNormal];
|
// _mCallOrAccept.enabled = NO;
|
// _monitorBtn.enabled = NO;
|
// _uIDTextField.text = @"JJY000016YWECG";//@"JJY000007FSEYX" 默认门口机的ID
|
_uIDTextField.text = @"JJY000003UYRBK";//@"JJY000007FSEYX" 默认门口机的ID
|
// _uIDTextField.text = @"JJY000016YWECG";//@"JJY000016YWECG" 默认门口机的ID
|
|
_uIDTextField.delegate = self; //⚠️这里必须要检查是否已经授权否则会失败,初始化视频音频采集
|
[self requestAccessForAVMediaType:AVMediaTypeAudio];
|
if (isAccessAudio) {
|
ImageCallback snapImageCallback = ^(UIImage *image){
|
//block是在分线程中调用的,这里要放到主线程
|
dispatch_async(dispatch_get_main_queue(), ^{
|
self->_snapImage = image;
|
[self saveImage:image];
|
});
|
};
|
//门口机会有视频的长宽高,是固定的(暂时还不确定)
|
_es = [[ESVideoPhone alloc]initESVideoPhoneWithFrame:CGRectMake(10, 100, self.view.frame.size.width-20, (self.view.frame.size.width-20)/4*3) delegate:self imagecallBack:snapImageCallback];
|
if (_es) {
|
//判断视频渲染是否初始化成功,如果失败会走ESVideoPhoneDelegate方法
|
if (_es.showView) {
|
_es.delegate = self;
|
[self.view addSubview:_es.showView];
|
}
|
}else{
|
NSLog(@"ESVideoPhone 初始化失败");
|
return;
|
}
|
// 初始化Audio采集Unit
|
if(![_es initAudioCaptureSession]){
|
return;
|
}
|
}else{
|
//音频没有权限建议不要发起通话
|
return;
|
}
|
//初始化视频采集Capture
|
[self requestAccessForAVMediaType:AVMediaTypeVideo];
|
if (isAccessVideo) {
|
if(![_es initVideoCaptureSession]){
|
NSLog(@"VideoCaptureSession 初始化失败");
|
}
|
}
|
//初始化AudioSession
|
_sessionHelper = [[AudioSessionHelper alloc]init];
|
[_sessionHelper setAudioSession];
|
//添加进入后台,中断等通知
|
[self addObservers];
|
}
|
|
-(void)viewDidAppear:(BOOL)animated{
|
//这个方法请根据App的具体情况调用
|
//在viewDidLoad中 调用requestAccessForAVMediaType: 是为了节约初始化的时间
|
//在viewDidAppear中调用requestAccessForAVMediaType: 是为了弹出提示打开权限的Alert
|
//测试的时候发现如下情况:如果只把授权方法放到ViewDidAppear方法中处理,如果没有授权在初始化采集器时会失败。同样AlertView会因为View没有didLoad而导致present不出来
|
if (!isAccessVideo || !isAccessAudio) {
|
[self requestAccessForAVMedia];
|
}
|
}
|
-(void)setIsInterrupt:(BOOL)isInterrupt{
|
if (_es) {
|
_es.isInterrupt = isInterrupt;
|
}
|
}
|
-(void)viewWillDisappear:(BOOL)animated{
|
[super viewWillDisappear:animated];
|
//防止用户不按挂断,或者不等收到对方的挂断,点击返回按钮。
|
[_es onStopCapture];
|
[_es stopTalk];
|
}
|
-(void)dealloc{
|
NSLog(@"==============dealloc1");
|
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
NSLog(@"==============dealloc2");
|
[_es freeSubClass];
|
NSLog(@"==============dealloc3");
|
}
|
|
#pragma mark ButtonAction
|
- (IBAction)back:(id)sender {
|
[self dismissViewControllerAnimated:YES completion:NULL];
|
|
}
|
//反呼:模拟门口机呼叫手机,需要门口机先点呼叫,等1-2S后,手机点反呼。
|
//params样本 address=192.168.1.3:8554,tag=mobile://123,
|
- (IBAction)onReverseCall:(id)sender {
|
NSString *title = [sender titleForState:UIControlStateNormal];
|
if ([title isEqualToString:@"反呼"]) {
|
NSString *normalStr = _uIDTextField.text;
|
NSString *param = [NSString stringWithFormat:@"address=%@,tag=mobile://123,",normalStr];
|
NSLog(@"============点反呼%@", param);
|
[_es onReverseCall:param];
|
}else if([title isEqualToString:@"接听"]){
|
[_es onAccept];
|
}
|
|
}
|
- (IBAction)onHangup:(id)sender {
|
[_es onHangup];
|
}
|
- (IBAction)onMonitor:(id)sender {
|
[_es onMonitor:_uIDTextField.text];
|
}
|
- (IBAction)speaker:(UIButton *)sender {
|
[_es stopTalk];
|
|
NSString *result = nil;
|
//听筒状态 插耳塞后拔掉后恢复到默认设置
|
if (sender == nil) {
|
result = [_sessionHelper speaker:NO];
|
}else{
|
if(!_isSpeaking){
|
result = [_sessionHelper speaker:YES];
|
_isSpeaking = YES;
|
}else{
|
result = [_sessionHelper speaker:NO];
|
_isSpeaking = NO;
|
}
|
}
|
if (result) {
|
[sender setTitle:result forState:UIControlStateNormal];
|
[_es startTalk];
|
}
|
|
}
|
|
- (IBAction)openDoor:(id)sender {
|
[_es openTheDoorWithRoomid: 1234];
|
}
|
- (IBAction)onSnap:(id)sender {
|
[_es onSnap];
|
}
|
//⚠️ 文本输入框
|
|
#pragma mark ESVideoPhoneDelegate
|
//视频通话的状态代理事件,phoneEvent为返回的消息里面包含event状态与与event相关的数据
|
-(void)getPhoneEvent_UI:(NSString *)phoneEvent{
|
NSLog(@"事件%@", phoneEvent);
|
NSArray *strArray = [phoneEvent componentsSeparatedByString:@"\r\n"];
|
NSArray *eventArray = [strArray.firstObject componentsSeparatedByString:@"="];
|
NSString *phoneEventStr = eventArray.lastObject;
|
|
if([phoneEventStr isEqual:@"EVT_Ringing"]){
|
[_mCallOrAccept setTitle:@"接听" forState:UIControlStateNormal];
|
}else if([phoneEventStr isEqual:@"EVT_StartStream"]){
|
|
} else if([phoneEventStr isEqual:@"EVT_StopStream"]){
|
[_mCallOrAccept setTitle:@"反呼" forState:UIControlStateNormal];
|
}else if([phoneEventStr isEqual:@"EVT_Connected"]){
|
[_mCallOrAccept setTitle:@"通话中..." forState:UIControlStateNormal];
|
}else if([phoneEventStr isEqual:@"EVT_HangUp"]){
|
[_mCallOrAccept setTitle:@"反呼" forState:UIControlStateNormal];
|
}else if([phoneEventStr isEqual:@"EVT_P2POnlineStatusChanged"]){
|
//EVT_P2PStarted(p2p初始化OK,可以连接),EVT_P2POnlineStatusChangedonline=1
|
//p2p初始化成功,手机端目前没有这个回调了
|
//_mCallOrAccept.enabled = YES;
|
//_monitorBtn.enabled = YES;
|
}else if([phoneEventStr isEqual:@"EVT_RECV_CUSTOM_DATA"]){
|
//开门的结果从这里返回
|
NSString *baseStr = [strArray[1] substringFromIndex:5];
|
NSData *data = [[NSData alloc]initWithBase64EncodedString:baseStr options:NSDataBase64DecodingIgnoreUnknownCharacters];
|
NSDictionary *dic = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:nil];
|
NSInteger status = [[dic valueForKey:@"status"]integerValue];
|
if(status && status == 1){
|
NSLog(@"开门成功");
|
}else{
|
NSLog(@"开门失败");
|
}
|
}
|
}
|
|
-(void)getAErrorForESVideoPhone:(NSString *)errorStr type:(NSUInteger)errortype{
|
NSLog(@"错误%@", errorStr);
|
//没有授权
|
if (errortype == LMPVideoCaptureErrorNotAuthorized) {
|
NSLog(@"错误%@", errorStr);
|
}
|
}
|
#pragma mark AudioSession与Notifications处理
|
|
- (void) addObservers
|
{
|
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillChangeFrame:) name: UIKeyboardWillChangeFrameNotification object: nil];
|
|
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(sessionRuntimeError:) name:AVCaptureSessionRuntimeErrorNotification object:nil];
|
|
//isAccessVideo,如果AVCaptureSession没有new出来不会收到通知
|
if (isAccessVideo) {
|
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(sessionWasInterrupted:) name:AVCaptureSessionWasInterruptedNotification object:nil];
|
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(sessionInterruptionEnded:) name:AVCaptureSessionInterruptionEndedNotification object:nil];
|
}else{
|
//object:为nil 可能不会触发通知
|
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleInterruption:)
|
name:AVAudioSessionInterruptionNotification object:[AVAudioSession
|
sharedInstance]];
|
}
|
|
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(audioRouteChangeListenerCallback:) name:AVAudioSessionRouteChangeNotification object:nil];
|
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(willEnterForeground:) name:UIApplicationDidBecomeActiveNotification object:nil];
|
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(willEnterBackground:) name:UIApplicationDidEnterBackgroundNotification object:nil];
|
}
|
|
- (void) sessionRuntimeError:(NSNotification*)notification
|
{
|
NSError* error = notification.userInfo[AVCaptureSessionErrorKey];
|
NSLog(@"Capture session runtime error: %@", error);
|
|
// If media services were reset, and the last start succeeded, restart the session.
|
if (error.code == AVErrorMediaServicesWereReset) {
|
[_es onStopCapture];
|
[_es startTalk];
|
}
|
}
|
|
- (void)handleInterruption:(NSNotification *)notification
|
{
|
NSUInteger interruptionType = [[[notification userInfo]
|
objectForKey:AVAudioSessionInterruptionTypeKey] unsignedIntegerValue];
|
|
if (AVAudioSessionInterruptionTypeBegan == interruptionType)
|
{
|
if (isBackGround) {
|
return;
|
}
|
[_es stopTalk];
|
}
|
else if (AVAudioSessionInterruptionTypeEnded == interruptionType)
|
{
|
if (self.isInterrupt == NO) {
|
return;
|
}else{
|
//直接在进入前台那个通知里面实现,实际上进入前台的方法会在这个方法前面调用,效果更好
|
[self InterruptionEndedAVAudioSessionSetActiveYES];
|
}
|
}
|
}
|
|
//AVAudioPlayer 类和 AVAudioRecorder 类,当发生中断时,系统会自动暂停播放或录制
|
- (void) sessionWasInterrupted:(NSNotification*)notification
|
{
|
if (_playing == YES) {
|
self.isInterrupt = YES;
|
//AVCaptureSessionInterruptionReason
|
if (@available(iOS 9.0, *)) {
|
NSInteger reason = [notification.userInfo[AVCaptureSessionInterruptionReasonKey] integerValue]; //电话中断是1
|
NSLog(@"Capture session was interrupted with reason %ld", (long)reason);
|
|
//音频硬件暂时不可用而造成的中断,例如,电话或警报。
|
if (reason == AVCaptureSessionInterruptionReasonAudioDeviceInUseByAnotherClient ||
|
reason == AVCaptureSessionInterruptionReasonVideoDeviceInUseByAnotherClient) {
|
NSLog(@"AVCaptureSessionInterruptionReasonVideoDeviceInUseByAnotherClient");
|
|
//VAudioPlayer 类和 AVAudioRecorder 类,当发生中断时,系统会自动暂停播放或录制
|
//Audio Queue Services, I/O audio unit
|
[_es onStopCapture];
|
[_es stopTalk];
|
/*
|
NSError *error = nil;
|
[[AVAudioSession sharedInstance] setActive:NO error:&error];
|
if (error) {
|
NSLog(@"sessionWasInterruptedSetActiveNO error:%@", error);
|
}
|
*/
|
}else if (reason == AVCaptureSessionInterruptionReasonVideoDeviceNotAvailableInBackground){
|
NSLog(@"AVCaptureSessionInterruptionReasonVideoDeviceNotAvailableInBackground");
|
//如果是电话中断,不会走进入后台的通知,进入后台再切换到前台这里是不用处理的
|
if (isBackGround) {
|
return;
|
}
|
[_es onStopCapture];
|
[_es stopTalk];
|
}
|
//多个应用程序资源争用质量下降。只有当应用程序占据全屏时,会话才能运行。
|
else if (reason == AVCaptureSessionInterruptionReasonVideoDeviceNotAvailableWithMultipleForegroundApps) {
|
NSLog(@"AVCaptureSessionInterruptionReasonVideoDeviceNotAvailableWithMultipleForegroundApps");
|
// Fade-in a label to inform the user that the camera is unavailable.
|
}else if (@available(iOS 11.1, *)) {
|
if (reason == AVCaptureSessionInterruptionReasonVideoDeviceNotAvailableDueToSystemPressure){
|
NSLog(@"AVCaptureSessionInterruptionReasonVideoDeviceNotAvailableDueToSystemPressure");
|
}
|
} else {
|
// Fallback on earlier versions
|
}
|
} else {
|
if (isBackGround) {
|
return;
|
}
|
[_es onStopCapture];
|
[_es stopTalk];
|
}
|
}
|
}
|
|
//这个通知可能会获取不到,
|
- (void) sessionInterruptionEnded:(NSNotification*)notification
|
{
|
// NSInteger reason = [notification.userInfo[AVCaptureSessionInterruptionReasonKey] integerValue];
|
NSLog(@"Capture session interruption ended");
|
if (self.isInterrupt == NO) {
|
return;
|
}else{
|
//直接在进入前台那个通知里面实现,实际上进入前台的方法会在这个方法前面调用,效果更好
|
[self InterruptionEndedAVAudioSessionSetActiveYES];
|
}
|
|
}
|
|
-(void)InterruptionEndedAVAudioSessionSetActiveYES{
|
if (isBackGround) {
|
return;
|
}
|
if (self.isInterrupt == YES) {
|
[_es onStartCapture];
|
[_es startTalk];
|
self.isInterrupt = NO;
|
}
|
}
|
|
|
|
- (void)audioRouteChangeListenerCallback:(NSNotification*)notification
|
{
|
|
NSDictionary *interuptionDict = notification.userInfo;
|
NSInteger routeChangeReason = [[interuptionDict valueForKey:AVAudioSessionRouteChangeReasonKey] integerValue];
|
switch (routeChangeReason) {
|
case AVAudioSessionRouteChangeReasonNewDeviceAvailable:
|
//NSLog(@"AVAudioSessionRouteChangeReasonNewDeviceAvailable");
|
//免提状态下耳机插入没有采集,同意切换到默认状态
|
NSLog(@"耳机插入");
|
[self speaker:nil];
|
break;
|
case AVAudioSessionRouteChangeReasonOldDeviceUnavailable:
|
//NSLog(@"AVAudioSessionRouteChangeReasonOldDeviceUnavailable");
|
NSLog(@"耳机拔出");
|
if([[_speakerBtn titleForState:UIControlStateNormal] isEqualToString:@"免提"]){
|
[self speaker:nil];
|
}else{
|
[self speaker:_speakerBtn];
|
}
|
|
break;
|
case AVAudioSessionRouteChangeReasonCategoryChange:
|
// called at start - also when other audio wants to play
|
//NSLog(@"AVAudioSessionRouteChangeReasonCategoryChange");
|
break;
|
}
|
}
|
|
/*
|
需要注意的是,有一个中断开始消息不一定会有一个中断结束消息,这就意味着中断结束的回调里的处理逻辑可能会没有被执行到。
|
所以需要关注当切到前台运行状态时,是不是需要重新激活你的 Audio Session。
|
*/
|
- (void)willEnterForeground:(NSNotification*)notification{
|
NSLog(@"willEnterForeground");
|
//初次启动会走这个通知(根页面),这时候是没有进入后台的
|
if (isBackGround) {
|
return;
|
}
|
|
[self InterruptionEndedAVAudioSessionSetActiveYES];
|
|
// 这里是考虑到用户没有授权,之后通过AlertAction跳转到设置页面授权后再回到APP时做的重新检测
|
//跳转到设置页面,授权后返回页面,继续初始化采集器
|
if (isAccessAudio && isAccessVideo) {
|
return;
|
}
|
if (isAccessVideo && !isAccessAudio) {
|
[self requestAccessForAVMediaType:AVMediaTypeAudio];
|
if (isAccessAudio) {
|
[_es initAudioCaptureSession];
|
}
|
}else if (!isAccessVideo && isAccessAudio){
|
[self requestAccessForAVMediaType:AVMediaTypeVideo];
|
if (isAccessVideo) {
|
[_es initVideoCaptureSession];
|
}
|
|
}else if (!isAccessVideo && !isAccessAudio){
|
[self requestAccessForAVMediaType:AVMediaTypeAudio];
|
[self requestAccessForAVMediaType:AVMediaTypeVideo];
|
if (isAccessAudio) {
|
[_es initAudioCaptureSession];
|
}
|
if (isAccessVideo) {
|
[_es initVideoCaptureSession];
|
}
|
}
|
}
|
- (void)willEnterBackground:(NSNotification *)notification {
|
isBackGround = YES;
|
}
|
|
//授权Alert
|
-(void)requestAccessForAVMedia{
|
if (!isAccessAudio) {
|
[self requestAccessForAVMediaType:AVMediaTypeAudio];
|
}
|
if (!isAccessVideo) {
|
[self requestAccessForAVMediaType:AVMediaTypeVideo];
|
}
|
if (!iSAudioNotDetermined && iSVideoNotDetermined){
|
[self creatAlertViewWith:@"授权请求" message:@"麦克风没有授权,请在设置中开启权限,否则将影响通讯功能。" cancel:@"确定"];
|
}else if(iSAudioNotDetermined && !iSVideoNotDetermined){
|
[self creatAlertViewWith:@"授权请求" message:@"相机没有授权,请在设置中开启权限,否则将影响通讯功能。" cancel:@"确定"];
|
}else if(!iSAudioNotDetermined && !iSVideoNotDetermined){
|
[self creatAlertViewWith:@"授权请求" message:@"麦克风与相机授权,请在设置中开启权限,否则将影响通讯功能。" cancel:@"确定"];
|
}
|
}
|
|
-(void)requestAccessForAVMediaType:(AVMediaType)type{
|
if (type == AVMediaTypeVideo) {
|
isAccessVideo = YES;
|
iSVideoNotDetermined = YES;
|
}else{
|
isAccessAudio = YES;
|
iSAudioNotDetermined = YES;
|
}
|
switch ([AVCaptureDevice authorizationStatusForMediaType:type])
|
{
|
case AVAuthorizationStatusAuthorized:
|
{
|
break;
|
}
|
case AVAuthorizationStatusNotDetermined:
|
{
|
dispatch_suspend(dispatch_get_main_queue());
|
[AVCaptureDevice requestAccessForMediaType:type completionHandler:^(BOOL granted) {
|
if (!granted) {
|
if (type == AVMediaTypeVideo) {
|
self->isAccessVideo = NO;
|
}else{
|
self->isAccessAudio = NO;
|
}
|
}
|
dispatch_resume(dispatch_get_main_queue());
|
}];
|
break;
|
}
|
default:
|
{
|
if (type == AVMediaTypeVideo) {
|
isAccessVideo = NO;
|
iSVideoNotDetermined = NO;
|
}else{
|
isAccessAudio = NO;
|
iSAudioNotDetermined = NO;
|
}
|
break;
|
}
|
}
|
}
|
|
-(void)creatAlertViewWith:(NSString *)title message:(NSString *) msg cancel:(NSString *)cancelMsg{
|
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:title message:msg preferredStyle:UIAlertControllerStyleAlert];
|
[alertController addAction:[UIAlertAction actionWithTitle:cancelMsg style:UIAlertActionStyleCancel handler:nil]];
|
[alertController addAction:[UIAlertAction actionWithTitle:@"设置" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
|
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString]];
|
}]];
|
[self presentViewController:alertController animated:YES completion:nil];
|
}
|
|
|
#pragma mark --privita 辅助
|
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
|
[self.view endEditing:YES];
|
}
|
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
|
if(textField.returnKeyType == UIReturnKeyDone){
|
[textField resignFirstResponder];//键盘收起
|
return NO;
|
}
|
return YES;
|
}
|
- (void)keyboardWillChangeFrame:(NSNotification *)notification {
|
NSValue *notiValue =notification.userInfo[@"UIKeyboardFrameEndUserInfoKey"];
|
if(notiValue){
|
CGRect rect = [notiValue CGRectValue];
|
[UIView animateWithDuration:0.5 animations:^{
|
|
if (self.view.frame.origin.y < 0) {
|
//已经上移动一次了,就不用移动了
|
if (self.view.frame.origin.y == 0){
|
CGFloat yFloat = self.view.frame.origin.y;
|
self.view.frame = CGRectMake(self.view.frame.origin.x, yFloat+50, self.view.frame.size.width, self.view.frame.size.height);
|
}
|
}
|
if (rect.origin.y == [UIScreen mainScreen].bounds.size.height) { //键盘将要收起
|
self.view.frame = CGRectMake(self.view.frame.origin.x, 0, self.view.frame.size.width, self.view.frame.size.height);
|
}
|
} completion:nil];
|
}
|
}
|
|
- (void)saveImage:(UIImage *)image {
|
NSArray *paths =NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES);
|
|
|
NSString *filePath = [[paths objectAtIndex:0]stringByAppendingPathComponent:
|
[NSString stringWithFormat:@"demo.png"]]; // 保存文件的名称
|
|
BOOL result =[UIImagePNGRepresentation(image)writeToFile:filePath atomically:YES]; // 保存成功会返回YES
|
if (result == YES) {
|
NSLog(@"保存成功");
|
}
|
|
}
|
|
@end
|