AVFoundation AVCaptureSession媒體捕捉
正文
AVFoundation 是Apple iOS和OS X系統(tǒng)中用于處理基于時(shí)間的媒體數(shù)據(jù)的高級框架,通過開發(fā)所需的工具提供了強(qiáng)大的功能集,讓開發(fā)者能夠基于蘋果平臺(tái)創(chuàng)建當(dāng)下最先進(jìn)的媒體應(yīng)用程序,其針對64位處理器設(shè)計(jì),充分利用了多核硬件優(yōu)勢,會(huì)自動(dòng)提供硬件加速操作,確保大部分設(shè)備能以最佳性能運(yùn)行,是iOS開發(fā)接觸音視頻開發(fā)必學(xué)的框架之一。
參與掘金日新計(jì)劃,持續(xù)記錄AVFoundation學(xué)習(xí),Demo學(xué)習(xí)地址,里面封裝了一些工具類,可以直接使用,這篇文章主要講述AVFoundation中的AVCaptureSession等類實(shí)現(xiàn)媒體捕捉功能,其他類的相關(guān)用法可查看我的其他文章。
捕捉媒體
媒體捕捉是AVFoundation的核心功能之一,也是開發(fā)音視頻App必不可少的功能。捕捉用到的類如圖所示
- AVCaptureSession是AVFoundation捕捉棧的核心類,捕捉會(huì)話用于連接輸入和輸出資源,管理從物理設(shè)備得到的輸入流,例如從攝像頭得到的視頻從麥克風(fēng)得到的音頻,以不同的方式輸出給一個(gè)或多個(gè)輸出,可以動(dòng)態(tài)配置輸入和輸出線路,讓開發(fā)者能夠在會(huì)話中按需重新配置捕捉環(huán)境。
- AVCaptureDevice為攝像頭麥克風(fēng)等物理設(shè)備定義了一個(gè)接口,在iOS10以后,使用AVCaptureDeviceDiscoverySession獲取設(shè)備。
- 將AVCaptureDevice包裝成AVCaptureDeviceInput才能添加到捕捉回話中。
- AVFoundation定義了AVCaptureOutput的許多擴(kuò)展類,AVCaptureOutput是一個(gè)抽象基類,用于從捕捉回話中得到數(shù)據(jù),框架定義了這個(gè)抽象類的高級擴(kuò)展類,常用的有AVCaptureStillImageOutput靜態(tài)圖片輸出、AVCaptureMovieFileOutput視頻文件輸出、AVCaptureVideoDataOutput視頻流數(shù)據(jù)輸出、AVCaptureAudioDataOutput音頻流數(shù)據(jù)輸出、AVCaptureMetadataOutput元數(shù)據(jù)輸出。注意,不能同時(shí)配置AVCaptureVideoDataOutput和AVCaptureMovieFileOutput,二者無法同時(shí)啟用。
- AVCaptureVideoPreviewLayer是CoreAnimation框架中CALayer的一個(gè)子類,對捕捉視頻數(shù)據(jù)實(shí)時(shí)預(yù)覽。當(dāng)然也可以使用GLKView、UIImageView預(yù)覽實(shí)時(shí)視頻流的Buffer。
具體代碼可以看Demo中的CQCaptureManager類對捕捉工具的封裝
1.創(chuàng)建會(huì)話
創(chuàng)建會(huì)話并配置分辨率
- 配置分辨率注意要判斷下能否支持,例如老機(jī)型前置攝像頭配置4k是不支持的。
- 不同分辨率的縮放倍數(shù)也是不同的
self.captureSession = [[AVCaptureSession alloc] init]; - (void)configSessionPreset:(AVCaptureSessionPreset)sessionPreset { [self.captureSession beginConfiguration]; if ([self.captureSession canSetSessionPreset:sessionPreset]) { self.captureSession.sessionPreset = sessionPreset; } else { self.captureSession.sessionPreset = AVCaptureSessionPresetHigh; } [self.captureSession commitConfiguration]; self.isConfigSessionPreset = YES; }
2.配置視頻輸入
/// 配置視頻輸入 - (BOOL)configVideoInput:(NSError * _Nullable *)error { // 添加視頻捕捉設(shè)備 // 拿到默認(rèn)視頻捕捉設(shè)備 iOS默認(rèn)后置攝像頭 // AVCaptureDevice *videoDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo]; AVCaptureDevice *videoDevice = [self getCameraWithPosition:AVCaptureDevicePositionBack]; // 將捕捉設(shè)備轉(zhuǎn)化為AVCaptureDeviceInput // 注意:會(huì)話不能直接使用AVCaptureDevice,必須將AVCaptureDevice封裝成AVCaptureDeviceInput對象 AVCaptureDeviceInput *videoInput = [AVCaptureDeviceInput deviceInputWithDevice:videoDevice error:error]; // 將捕捉設(shè)備添加給會(huì)話 // 使用前判斷videoInput是否有效以及能否添加,因?yàn)閿z像頭是一個(gè)公共設(shè)備,不屬于任何App,有可能別的App在使用,添加前應(yīng)該先進(jìn)行判斷是否可以添加 if (videoInput && [self.captureSession canAddInput:videoInput]) { // 將videoInput 添加到 captureSession中 [self.captureSession beginConfiguration]; [self.captureSession addInput:videoInput]; [self.captureSession commitConfiguration]; self.videoDeviceInput = videoInput; return YES; }else { return NO; } } /// 移除視頻輸入設(shè)備 - (void)removeVideoDeviceInput { if (self.videoDeviceInput) [self.captureSession removeInput:self.videoDeviceInput]; self.videoDeviceInput = nil; }
- 獲取攝像頭,iOS10之后使用AVCaptureDeviceDiscoverySession獲取
- 長焦超廣或者雙攝三攝必須使用AVCaptureDeviceDiscoverySession獲取,[AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo]無法獲取
/// 根據(jù)position拿到攝像頭 - (AVCaptureDevice *)getCameraWithPosition:(AVCaptureDevicePosition)position { /** AVCaptureDeviceTypeBuiltInWideAngleCamera 廣角(默認(rèn)設(shè)備,28mm左右焦段) AVCaptureDeviceTypeBuiltInTelephotoCamera 長焦(默認(rèn)設(shè)備的2x或3x,只能使用AVCaptureDeviceDiscoverySession獲取) AVCaptureDeviceTypeBuiltInUltraWideCamera 超廣角(默認(rèn)設(shè)備的0.5x,只能使用AVCaptureDeviceDiscoverySession獲取) AVCaptureDeviceTypeBuiltInDualCamera (一個(gè)廣角一個(gè)長焦(iPhone7P,iPhoneX),可以自動(dòng)切換攝像頭,只能使用AVCaptureDeviceDiscoverySession獲取) AVCaptureDeviceTypeBuiltInDualWideCamera (一個(gè)超廣一個(gè)廣角(iPhone12 iPhone13),可以自動(dòng)切換攝像頭,只能使用AVCaptureDeviceDiscoverySession獲取) AVCaptureDeviceTypeBuiltInTripleCamera (超廣,廣角,長焦三攝像頭,iPhone11ProMax iPhone12ProMax iPhone13ProMax,可以自動(dòng)切換攝像頭,只能使用AVCaptureDeviceDiscoverySession獲取) AVCaptureDeviceTypeBuiltInTrueDepthCamera (紅外和攝像頭, iPhone12ProMax iPhone13ProMax ) */ NSArray *deviceTypes; if (position == AVCaptureDevicePositionBack) { deviceTypes = @[AVCaptureDeviceTypeBuiltInDualCamera, AVCaptureDeviceTypeBuiltInDualWideCamera, AVCaptureDeviceTypeBuiltInTripleCamera, ]; } else { deviceTypes = @[AVCaptureDeviceTypeBuiltInWideAngleCamera]; } AVCaptureDeviceDiscoverySession *deviceSession = [AVCaptureDeviceDiscoverySession discoverySessionWithDeviceTypes:deviceTypes mediaType:AVMediaTypeVideo position:position]; if (deviceSession.devices.count) return deviceSession.devices.firstObject; if (position == AVCaptureDevicePositionBack) { // 非多攝手機(jī) deviceTypes = @[AVCaptureDeviceTypeBuiltInWideAngleCamera]; AVCaptureDeviceDiscoverySession *deviceSession = [AVCaptureDeviceDiscoverySession discoverySessionWithDeviceTypes:deviceTypes mediaType:AVMediaTypeVideo position:position]; if (deviceSession.devices.count) return deviceSession.devices.firstObject; } return nil; }
3.配置音頻輸入
/// 配置音頻輸入 - (BOOL)configAudioInput:(NSError * _Nullable *)error { // 添加音頻捕捉設(shè)備 ,如果只是拍攝靜態(tài)圖片,可以不用設(shè)置 // 選擇默認(rèn)音頻捕捉設(shè)備 即返回一個(gè)內(nèi)置麥克風(fēng) AVCaptureDevice *audioDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio]; self.audioDeviceInput = [AVCaptureDeviceInput deviceInputWithDevice:audioDevice error:error]; if (self.audioDeviceInput && [self.captureSession canAddInput:self.audioDeviceInput]) { [self.captureSession beginConfiguration]; [self.captureSession addInput:self.audioDeviceInput]; [self.captureSession commitConfiguration]; return YES; }else { return NO; } } /// 移除音頻輸入設(shè)備 - (void)removeAudioDeviceInput { if (self.audioDeviceInput) [self.captureSession removeInput:self.audioDeviceInput]; }
5.配置輸出
#pragma mark - Func 靜態(tài)圖片輸出配置 /// 配置靜態(tài)圖片輸出 - (void)configStillImageOutput { // AVCaptureStillImageOutput 從攝像頭捕捉靜態(tài)圖片 self.stillImageOutput = [[AVCaptureStillImageOutput alloc] init]; // 配置字典:希望捕捉到JPEG格式的圖片 self.stillImageOutput.outputSettings = @{AVVideoCodecKey:AVVideoCodecJPEG}; // 輸出連接 判斷是否可用,可用則添加到輸出連接中去 [self.captureSession beginConfiguration]; if ([self.captureSession canAddOutput:self.stillImageOutput]) { [self.captureSession addOutput:self.stillImageOutput]; } [self.captureSession commitConfiguration]; } /// 移除靜態(tài)圖片輸出 - (void)removeStillImageOutput { if (self.stillImageOutput) [self.captureSession removeOutput:self.stillImageOutput]; } #pragma mark - Func 電影文件輸出配置 /// 配置電影文件輸出 - (void)configMovieFileOutput { // AVCaptureMovieFileOutput,將QuickTime視頻錄制到文件系統(tǒng) self.movieFileOutput = [[AVCaptureMovieFileOutput alloc] init]; [self.captureSession beginConfiguration]; if ([self.captureSession canAddOutput:self.movieFileOutput]) { [self.captureSession addOutput:self.movieFileOutput]; } [self.captureSession commitConfiguration]; } /// 移除電影文件輸出 - (void)removeMovieFileOutput { if (self.movieFileOutput) [self.captureSession removeOutput:self.movieFileOutput]; }
6.開始會(huì)話\結(jié)束會(huì)話
// 異步開始會(huì)話 - (void)startSessionAsync { // 檢查是否處于運(yùn)行狀態(tài) if (![self.captureSession isRunning]) { // 使用同步調(diào)用會(huì)損耗一定的時(shí)間,則用異步的方式處理 dispatch_async(self.captureVideoQueue, ^{ [self.captureSession startRunning]; }); } } // 異步停止會(huì)話 - (void)stopSessionAsync { // 檢查是否處于運(yùn)行狀態(tài) if ([self.captureSession isRunning]) { dispatch_async(self.captureVideoQueue, ^{ [self.captureSession stopRunning]; }); } }
7.捕捉靜態(tài)圖片
#pragma mark - 靜態(tài)圖片捕捉 #pragma mark Public Func 靜態(tài)圖片捕捉 // 捕捉靜態(tài)圖片 - (void)captureStillImage { if (!self.isConfigSessionPreset) [self configSessionPreset:AVCaptureSessionPresetMedium]; if (!self.videoDeviceInput) { NSError *configError; BOOL configResult = [self configVideoInput:&configError]; if (!configResult) return; } if (!self.stillImageOutput) [self configStillImageOutput]; [self startSessionSync]; // 獲取圖片輸出連接 AVCaptureConnection *connection = [self.stillImageOutput connectionWithMediaType:AVMediaTypeVideo]; // 即使程序只支持縱向,但是如果用戶橫向拍照時(shí),需要調(diào)整結(jié)果照片的方向 // 判斷是否支持設(shè)置視頻方向, 支持則根據(jù)設(shè)備方向設(shè)置輸出方向值 if (connection.isVideoOrientationSupported) { connection.videoOrientation = [self getCurrentVideoOrientation]; } [self.stillImageOutput captureStillImageAsynchronouslyFromConnection:connection completionHandler:^(CMSampleBufferRef _Nullable imageDataSampleBuffer, NSError * _Nullable error) { if (imageDataSampleBuffer != NULL) { dispatch_async(dispatch_get_main_queue(), ^{ if (self.delegate && [self.delegate respondsToSelector:@selector(mediaCaptureImageFileSuccess)]) { [self.delegate mediaCaptureImageFileSuccess]; } }); // CMSampleBufferRef轉(zhuǎn)UIImage 并寫入相冊 NSData *imageData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageDataSampleBuffer]; UIImage *image = [[UIImage alloc] initWithData:imageData]; [self writeImageToAssetsLibrary:image]; } else { dispatch_async(dispatch_get_main_queue(), ^{ if (self.delegate && [self.delegate respondsToSelector:@selector(mediaCaptureImageFailedWithError:)]) { [self.delegate mediaCaptureImageFailedWithError:error]; } }); NSLog(@"NULL sampleBuffer:%@",[error localizedDescription]); } }]; } #pragma mark Private Func 靜態(tài)圖片捕捉 /** Assets Library 框架 用來讓開發(fā)者通過代碼方式訪問iOS photo 注意:會(huì)訪問到相冊,需要修改plist 權(quán)限。否則會(huì)導(dǎo)致項(xiàng)目崩潰 */ /// 將UIImage寫入到用戶相冊 - (void)writeImageToAssetsLibrary:(UIImage *)image { ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init]; // 參數(shù)1 圖片, 參數(shù)2 方向, 參數(shù)3 回調(diào) [library writeImageToSavedPhotosAlbum:image.CGImage orientation:(NSUInteger)image.imageOrientation completionBlock:^(NSURL *assetURL, NSError *error) { if (!error) { dispatch_async(dispatch_get_main_queue(), ^{ if (self.delegate && [self.delegate respondsToSelector:@selector(assetLibraryWriteImageSuccessWithImage:)]) { [self.delegate assetLibraryWriteImageSuccessWithImage:image]; } }); } else { dispatch_async(dispatch_get_main_queue(), ^{ if (self.delegate && [self.delegate respondsToSelector:@selector(assetLibraryWriteImageFailedWithError:)]) { [self.delegate assetLibraryWriteImageFailedWithError:error]; } }); } }]; }
8.捕捉視頻文件
#pragma mark - 電影文件捕捉 #pragma mark Public Func 電影文件捕捉 // 開始錄制電影文件 - (void)startRecordingMovieFile { if (!self.isConfigSessionPreset) [self configSessionPreset:AVCaptureSessionPresetMedium]; if (!self.videoDeviceInput) { NSError *configError; BOOL configResult = [self configVideoInput:&configError]; if (!configResult) return; } if (!self.movieFileOutput) [self configMovieFileOutput]; [self startSessionSync]; if ([self isRecordingMovieFile]) return; AVCaptureConnection *videoConnection = [self.movieFileOutput connectionWithMediaType:AVMediaTypeVideo]; // 設(shè)置輸出方向 // 即使程序只支持縱向,但是如果用戶橫向拍照時(shí),需要調(diào)整結(jié)果照片的方向 // 判斷是否支持設(shè)置視頻方向, 支持則根據(jù)設(shè)備方向設(shè)置輸出方向值 if (videoConnection.isVideoOrientationSupported) { videoConnection.videoOrientation = [self getCurrentVideoOrientation]; } // 設(shè)置視頻幀穩(wěn)定 // 判斷是否支持視頻穩(wěn)定 可以顯著提高視頻的質(zhì)量。只會(huì)在錄制視頻文件涉及 // if (videoConnection.isVideoStabilizationSupported) { // videoConnection.enablesVideoStabilizationWhenAvailable = YES; // } videoConnection.preferredVideoStabilizationMode = AVCaptureVideoStabilizationModeAuto; // 設(shè)置對焦 AVCaptureDevice *device = [self getActiveCamera]; // 攝像頭可以進(jìn)行平滑對焦模式操作。即減慢攝像頭鏡頭對焦速度。當(dāng)用戶移動(dòng)拍攝時(shí)攝像頭會(huì)嘗試快速自動(dòng)對焦。 if (device.isSmoothAutoFocusEnabled) { NSError *error; if ([device lockForConfiguration:&error]) { device.smoothAutoFocusEnabled = YES; [device unlockForConfiguration]; } else { dispatch_async(dispatch_get_main_queue(), ^{ if (self.delegate && [self.delegate respondsToSelector:@selector(deviceConfigurationFailedWithError:)]) { [self.delegate deviceConfigurationFailedWithError:error]; } }); } } self.movieFileOutputURL = [self getVideoTempPathURL]; // 開始錄制 參數(shù)1:錄制保存路徑 參數(shù)2:代理 [self.movieFileOutput startRecordingToOutputFileURL:self.movieFileOutputURL recordingDelegate:self]; } // 停止錄制電影文件 - (void)stopRecordingMovieFile { if ([self isRecordingMovieFile]) { [self.movieFileOutput stopRecording]; } } // 是否在錄制電影文件 - (BOOL)isRecordingMovieFile { return self.movieFileOutput.isRecording; } // 錄制電影文件的時(shí)間 - (CMTime)movieFileRecordedDuration { return self.movieFileOutput.recordedDuration; } #pragma mark AVCaptureFileOutputRecordingDelegate /// 捕捉電影文件成功的回調(diào) - (void)captureOutput:(AVCaptureFileOutput *)output didFinishRecordingToOutputFileAtURL:(NSURL *)outputFileURL fromConnections:(NSArray<AVCaptureConnection *> *)connections error:(NSError *)error { if (error) { dispatch_async(dispatch_get_main_queue(), ^{ if (self.delegate && [self.delegate respondsToSelector:@selector(mediaCaptureMovieFileFailedWithError:)]) { [self.delegate mediaCaptureMovieFileFailedWithError:error]; } }); } else { dispatch_async(dispatch_get_main_queue(), ^{ if (self.delegate && [self.delegate respondsToSelector:@selector(mediaCaptureMovieFileSuccess)]) { [self.delegate mediaCaptureMovieFileSuccess]; } }); // copy一個(gè)副本再置為nil // 將文件寫入相冊 [self writeVideoToAssetsLibrary:self.movieFileOutputURL.copy]; self.movieFileOutputURL = nil; } } #pragma mark Private Func 電影文件捕捉 /// 創(chuàng)建視頻文件臨時(shí)路徑URL - (NSURL *)getVideoTempPathURL { NSFileManager *fileManager = [NSFileManager defaultManager]; NSString *tempPath = [fileManager temporaryDirectoryWithTemplateString:@"video.XXXXXX"]; if (tempPath) { NSString *filePath = [tempPath stringByAppendingPathComponent:@"temp_video.mov"]; return [NSURL fileURLWithPath:filePath]; } return nil; } /// 將視頻文件寫入到用戶相冊 - (void)writeVideoToAssetsLibrary:(NSURL *)videoURL { ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init]; // 和圖片不同,視頻的寫入更耗時(shí),所以寫入之前應(yīng)該判斷是否能寫入 if (![library videoAtPathIsCompatibleWithSavedPhotosAlbum:videoURL]) return; [library writeVideoAtPathToSavedPhotosAlbum:videoURL completionBlock:^(NSURL *assetURL, NSError *error) { if (error) { dispatch_async(dispatch_get_main_queue(), ^{ if (self.delegate && [self.delegate respondsToSelector:@selector(assetLibraryWriteMovieFileFailedWithError:)]) { [self.delegate assetLibraryWriteMovieFileFailedWithError:error]; } }); } else { // 寫入成功 回調(diào)封面圖 [self getVideoCoverImageWithVideoURL:videoURL callBlock:^(UIImage *coverImage) { dispatch_async(dispatch_get_main_queue(), ^{ if (self.delegate && [self.delegate respondsToSelector:@selector(assetLibraryWriteMovieFileSuccessWithCoverImage:)]) { [self.delegate assetLibraryWriteMovieFileSuccessWithCoverImage:coverImage]; } }); }]; } }]; } /// 獲取視頻文件封面圖 - (void)getVideoCoverImageWithVideoURL:(NSURL *)videoURL callBlock:(void(^)(UIImage *))callBlock { dispatch_async(self.captureVideoQueue, ^{ AVAsset *asset = [AVAsset assetWithURL:videoURL]; AVAssetImageGenerator *imageGenerator = [AVAssetImageGenerator assetImageGeneratorWithAsset:asset]; // 設(shè)置maximumSize 寬為100,高為0 根據(jù)視頻的寬高比來計(jì)算圖片的高度 imageGenerator.maximumSize = CGSizeMake(100.0f, 0.0f); // 捕捉視頻縮略圖會(huì)考慮視頻的變化(如視頻的方向變化),如果不設(shè)置,縮略圖的方向可能出錯(cuò) imageGenerator.appliesPreferredTrackTransform = YES; CGImageRef imageRef = [imageGenerator copyCGImageAtTime:kCMTimeZero actualTime:NULL error:nil]; UIImage *image = [UIImage imageWithCGImage:imageRef]; CGImageRelease(imageRef); dispatch_async(dispatch_get_main_queue(), ^{ !callBlock ?: callBlock(image); }); }); }
9.預(yù)覽視頻
previewView.session = captureManager.captureSession
以上就是AVFoundation AVCaptureSession媒體捕捉的詳細(xì)內(nèi)容,更多關(guān)于AVFoundation AVCaptureSession的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
iOS之?dāng)?shù)據(jù)解析之XML解析詳解
本篇文章主要介紹了iOS之?dāng)?shù)據(jù)解析之XML解析詳解,XML解析常見的兩種方式:DOM解析和SAX解析,有興趣的可以了解一下。2016-12-12iOS實(shí)現(xiàn)轉(zhuǎn)場動(dòng)畫的3種方法示例
這篇文章主要給大家介紹了關(guān)于iOS實(shí)現(xiàn)轉(zhuǎn)場動(dòng)畫的3種方法,文中通過示例代碼以及圖文介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧2019-03-03iOS自定義UITableView實(shí)現(xiàn)不同系統(tǒng)下的左滑刪除功能詳解
關(guān)于左滑刪除這塊,相信不少朋友都遇到過。下面這篇文章主要給大家介紹了關(guān)于iOS如何自定義UITableView實(shí)現(xiàn)不同系統(tǒng)下的左滑刪除功能的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考下2018-09-09IOS開發(fā)筆記之禁用手勢滑動(dòng)返回功能的示例
本篇文章主要介紹了IOS開發(fā)筆記之禁用手勢滑動(dòng)返回功能的示例,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-09-09iOS 本地存儲(chǔ)NSUserDefaults封裝代碼
下面小編就為大家分享一篇iOS 本地存儲(chǔ)NSUserDefaults封裝代碼,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-01-01iOS應(yīng)用開發(fā)中使用UIScrollView控件來實(shí)現(xiàn)圖片縮放
這篇文章主要介紹了iOS開發(fā)中使用UIScrollView控件來實(shí)現(xiàn)圖片縮放的方法,代碼基于傳統(tǒng)的Objective-C,需要的朋友可以參考下2015-12-12iOS利用AFNetworking3.0——實(shí)現(xiàn)文件斷點(diǎn)下載
這篇文章主要介紹了iOS利用AFNetworking3.0——實(shí)現(xiàn)文件斷點(diǎn)下載,具有一定的參考價(jià)值,有興趣的可以了解一下。2017-01-01iOS基于UITableView實(shí)現(xiàn)多層展開與收起
這篇文章主要為大家詳細(xì)介紹了iOS基于UITableView實(shí)現(xiàn)多層展開與收起的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-03-03