AVFoundation AVCaptureSession媒體捕捉
正文
AVFoundation 是Apple iOS和OS X系統(tǒng)中用于處理基于時(shí)間的媒體數(shù)據(jù)的高級(jí)框架,通過(guò)開(kāi)發(fā)所需的工具提供了強(qiáng)大的功能集,讓開(kāi)發(fā)者能夠基于蘋(píng)果平臺(tái)創(chuàng)建當(dāng)下最先進(jìn)的媒體應(yīng)用程序,其針對(duì)64位處理器設(shè)計(jì),充分利用了多核硬件優(yōu)勢(shì),會(huì)自動(dòng)提供硬件加速操作,確保大部分設(shè)備能以最佳性能運(yùn)行,是iOS開(kāi)發(fā)接觸音視頻開(kāi)發(fā)必學(xué)的框架之一。
參與掘金日新計(jì)劃,持續(xù)記錄AVFoundation學(xué)習(xí),Demo學(xué)習(xí)地址,里面封裝了一些工具類,可以直接使用,這篇文章主要講述AVFoundation中的AVCaptureSession等類實(shí)現(xiàn)媒體捕捉功能,其他類的相關(guān)用法可查看我的其他文章。
捕捉媒體
媒體捕捉是AVFoundation的核心功能之一,也是開(kāi)發(fā)音視頻App必不可少的功能。捕捉用到的類如圖所示

- AVCaptureSession是AVFoundation捕捉棧的核心類,捕捉會(huì)話用于連接輸入和輸出資源,管理從物理設(shè)備得到的輸入流,例如從攝像頭得到的視頻從麥克風(fēng)得到的音頻,以不同的方式輸出給一個(gè)或多個(gè)輸出,可以動(dòng)態(tài)配置輸入和輸出線路,讓開(kāi)發(fā)者能夠在會(huì)話中按需重新配置捕捉環(huán)境。
- AVCaptureDevice為攝像頭麥克風(fēng)等物理設(shè)備定義了一個(gè)接口,在iOS10以后,使用AVCaptureDeviceDiscoverySession獲取設(shè)備。
- 將AVCaptureDevice包裝成AVCaptureDeviceInput才能添加到捕捉回話中。
- AVFoundation定義了AVCaptureOutput的許多擴(kuò)展類,AVCaptureOutput是一個(gè)抽象基類,用于從捕捉回話中得到數(shù)據(jù),框架定義了這個(gè)抽象類的高級(jí)擴(kuò)展類,常用的有AVCaptureStillImageOutput靜態(tài)圖片輸出、AVCaptureMovieFileOutput視頻文件輸出、AVCaptureVideoDataOutput視頻流數(shù)據(jù)輸出、AVCaptureAudioDataOutput音頻流數(shù)據(jù)輸出、AVCaptureMetadataOutput元數(shù)據(jù)輸出。注意,不能同時(shí)配置AVCaptureVideoDataOutput和AVCaptureMovieFileOutput,二者無(wú)法同時(shí)啟用。
- AVCaptureVideoPreviewLayer是CoreAnimation框架中CALayer的一個(gè)子類,對(duì)捕捉視頻數(shù)據(jù)實(shí)時(shí)預(yù)覽。當(dāng)然也可以使用GLKView、UIImageView預(yù)覽實(shí)時(shí)視頻流的Buffer。
具體代碼可以看Demo中的CQCaptureManager類對(duì)捕捉工具的封裝
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對(duì)象
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獲取
- 長(zhǎng)焦超廣或者雙攝三攝必須使用AVCaptureDeviceDiscoverySession獲取,[AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo]無(wú)法獲取
/// 根據(jù)position拿到攝像頭
- (AVCaptureDevice *)getCameraWithPosition:(AVCaptureDevicePosition)position {
/**
AVCaptureDeviceTypeBuiltInWideAngleCamera 廣角(默認(rèn)設(shè)備,28mm左右焦段)
AVCaptureDeviceTypeBuiltInTelephotoCamera 長(zhǎng)焦(默認(rèn)設(shè)備的2x或3x,只能使用AVCaptureDeviceDiscoverySession獲取)
AVCaptureDeviceTypeBuiltInUltraWideCamera 超廣角(默認(rèn)設(shè)備的0.5x,只能使用AVCaptureDeviceDiscoverySession獲取)
AVCaptureDeviceTypeBuiltInDualCamera (一個(gè)廣角一個(gè)長(zhǎng)焦(iPhone7P,iPhoneX),可以自動(dòng)切換攝像頭,只能使用AVCaptureDeviceDiscoverySession獲取)
AVCaptureDeviceTypeBuiltInDualWideCamera (一個(gè)超廣一個(gè)廣角(iPhone12 iPhone13),可以自動(dòng)切換攝像頭,只能使用AVCaptureDeviceDiscoverySession獲取)
AVCaptureDeviceTypeBuiltInTripleCamera (超廣,廣角,長(zhǎng)焦三攝像頭,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.開(kāi)始會(huì)話\結(jié)束會(huì)話
// 異步開(kāi)始會(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 并寫(xiě)入相冊(cè)
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 框架
用來(lái)讓開(kāi)發(fā)者通過(guò)代碼方式訪問(wèn)iOS photo
注意:會(huì)訪問(wèn)到相冊(cè),需要修改plist 權(quán)限。否則會(huì)導(dǎo)致項(xiàng)目崩潰
*/
/// 將UIImage寫(xiě)入到用戶相冊(cè)
- (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 電影文件捕捉
// 開(kāi)始錄制電影文件
- (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è)置對(duì)焦
AVCaptureDevice *device = [self getActiveCamera];
// 攝像頭可以進(jìn)行平滑對(duì)焦模式操作。即減慢攝像頭鏡頭對(duì)焦速度。當(dāng)用戶移動(dòng)拍攝時(shí)攝像頭會(huì)嘗試快速自動(dòng)對(duì)焦。
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];
// 開(kāi)始錄制 參數(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
// 將文件寫(xiě)入相冊(cè)
[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;
}
/// 將視頻文件寫(xiě)入到用戶相冊(cè)
- (void)writeVideoToAssetsLibrary:(NSURL *)videoURL {
ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
// 和圖片不同,視頻的寫(xiě)入更耗時(shí),所以寫(xiě)入之前應(yīng)該判斷是否能寫(xiě)入
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 {
// 寫(xiě)入成功 回調(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ù)視頻的寬高比來(lái)計(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的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
iOS之?dāng)?shù)據(jù)解析之XML解析詳解
本篇文章主要介紹了iOS之?dāng)?shù)據(jù)解析之XML解析詳解,XML解析常見(jiàn)的兩種方式:DOM解析和SAX解析,有興趣的可以了解一下。2016-12-12
iOS實(shí)現(xiàn)轉(zhuǎn)場(chǎng)動(dòng)畫(huà)的3種方法示例
這篇文章主要給大家介紹了關(guān)于iOS實(shí)現(xiàn)轉(zhuǎn)場(chǎng)動(dòng)畫(huà)的3種方法,文中通過(guò)示例代碼以及圖文介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-03-03
iOS自定義UITableView實(shí)現(xiàn)不同系統(tǒng)下的左滑刪除功能詳解
關(guān)于左滑刪除這塊,相信不少朋友都遇到過(guò)。下面這篇文章主要給大家介紹了關(guān)于iOS如何自定義UITableView實(shí)現(xiàn)不同系統(tǒng)下的左滑刪除功能的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友可以參考下2018-09-09
IOS開(kāi)發(fā)筆記之禁用手勢(shì)滑動(dòng)返回功能的示例
本篇文章主要介紹了IOS開(kāi)發(fā)筆記之禁用手勢(shì)滑動(dòng)返回功能的示例,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-09-09
iOS 本地存儲(chǔ)NSUserDefaults封裝代碼
下面小編就為大家分享一篇iOS 本地存儲(chǔ)NSUserDefaults封裝代碼,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-01-01
iOS應(yīng)用開(kāi)發(fā)中使用UIScrollView控件來(lái)實(shí)現(xiàn)圖片縮放
這篇文章主要介紹了iOS開(kāi)發(fā)中使用UIScrollView控件來(lái)實(shí)現(xiàn)圖片縮放的方法,代碼基于傳統(tǒng)的Objective-C,需要的朋友可以參考下2015-12-12
舉例詳解iOS開(kāi)發(fā)過(guò)程中的沙盒機(jī)制與文件
這篇文章主要介紹了舉例詳解iOS開(kāi)發(fā)過(guò)程中的沙盒機(jī)制與文件,示例代碼為傳統(tǒng)的Obejective-C,需要的朋友可以參考下2015-09-09
iOS利用AFNetworking3.0——實(shí)現(xiàn)文件斷點(diǎn)下載
這篇文章主要介紹了iOS利用AFNetworking3.0——實(shí)現(xiàn)文件斷點(diǎn)下載,具有一定的參考價(jià)值,有興趣的可以了解一下。2017-01-01
iOS基于UITableView實(shí)現(xiàn)多層展開(kāi)與收起
這篇文章主要為大家詳細(xì)介紹了iOS基于UITableView實(shí)現(xiàn)多層展開(kāi)與收起的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-03-03

