轉自:http://www.jianshu.com/p/4772d3cb6a43
序言
前面我們已經調到過怎麼製作二維碼,在我們能夠生成二維碼之後,如何對二維碼進行掃描呢?
在iOS7之前,大部分應用中使用的二維碼掃描是第三方的掃描框架,例如ZXing或者ZBar。使用時集成麻煩,出錯也不方便調試。在iOS7之後,蘋果自身提供了二維碼的掃描功能,從效率上來說,原生的二維碼遠高於這些第三方框架。本文講解如何使用原生框架實現二維碼掃描功能,並且進行掃描後的項目跳轉。
ps:本期的源代碼會在文章結尾給出鏈接
掃描相關類
二維碼掃描需要獲取攝像頭並讀取照片信息,因此我們需要導入系統的AVFoundation框架,創建視頻會話。我們需要用到一下幾個類:
- AVCaptureSession 會話對象。此類作爲硬件設備輸入輸出信息的橋樑,承擔實時獲取設備數據的責任
- AVCaptureDeviceInput 設備輸入類。這個類用來表示輸入數據的硬件設備,配置抽象設備的port
- AVCaptureMetadataOutput 輸出類。這個支持二維碼、條形碼等圖像數據的識別
- AVCaptureVideoPreviewLayer 圖層類。用來快速呈現攝像頭獲取的原始數據
二維碼掃描功能的實現步驟是創建好會話對象,用來獲取從硬件設備輸入的數據,並實時顯示在界面上。在掃描到相應圖像數據的時候,通過AVCaptureVideoPreviewLayer類型進行返回
應用跳轉
在使用第三方登陸、分享sdk的時候,我們的項目會在本機安裝有目標平臺的應用的情況下進行應用跳轉,並且傳遞信息過去。這在沙盒機制下的iOS應用而言,理應是不符合規則的。但是,iOS SDK給我們提供了一個叫做url scheme的機制來實現這個功能。
url scheme讓我們可以像使用Safari打開網頁的方式跳轉到其他應用中,並使用類似網絡請求的GET請求的參數拼湊方式來在不同應用之間傳遞數據。
使用url scheme的第一步是在項目的info.plist文件中添加新row,命名爲URL types
展開新增的字典,我們修改其中的URL Identifier以及新增加一個字段
URL Schemes。
Identifier用來跳轉後,讓跳轉應用識別從哪裏跳轉過來的,我們可以設置爲bundleID反轉,來確保其特殊性。
URL Schemes是一個數組,我們將在這個數組裏面自定義自己的url schemes,這裏我們填寫應用名。最終效果如下:
接着,我們就可以在其他應用中通過openURL:方法打開我們的app。
二維碼掃描
二維碼掃描的步驟:
1、創建設備會話對象,用來設置設備數據輸入
2、獲取攝像頭,並且將攝像頭對象加入當前會話中
3、實時獲取攝像頭原始數據顯示在屏幕上
4、掃描到二維碼/條形碼數據,通過協議方法回調
-
會話對象
AVCaptureSession
的創建_session = [AVCaptureSession new]; [_session setSessionPreset: AVCaptureSessionPresetHigh]; //高質量採集 [self setupIODevice];
-
setupIODevice
方法中懶加載方式創建輸入對象和輸出對象,注意必須在輸出數據對象加入到當前會話後才能設置識別的數據格式。這裏設置爲掃描二維碼以及條形碼[_session addInput: self.input]; [_session addOutput: self.output]; _output.metadataObjectTypes = @[AVMetadataObjectTypeQRCode, AVMetadataObjectTypeEAN13Code, AVMetadataObjectTypeEAN8Code, AVMetadataObjectTypeCode128Code];
-
創建
AVCaptureMetadataOutput
設置好掃描成功回調代理以及回調線程_output = [AVCaptureMetadataOutput new]; [_output setMetadataObjectsDelegate: self queue: dispatch_get_main_queue()];
-
創建
AVCaptureDeviceInput
輸入設備爲手機攝像頭AVCaptureDevice * device = [AVCaptureDevice defaultDeviceWithMediaType: AVMediaTypeVideo]; _input = [AVCaptureDeviceInput deviceInputWithDevice: device error: nil];
-
創建
AVCaptureVideoPreviewLayer
對象來實時獲取攝像頭圖像,我們需要調用[self.view addSubview: self.scanView]
把攝像頭獲取的圖像實時展示在屏幕上_scanView = [AVCaptureVideoPreviewLayer layerWithSession: self.session]; _scanView.videoGravity = AVLayerVideoGravityResizeAspectFill; _scanView.frame = self.bounds;
-
實現
captureOutput: didOutputMetadataObjects: fromConnection:
來獲取掃描得到的數據。回調參數metadataObjects中存放了掃描結果,我們需要先判斷這個數組的數據個數不爲0再執行下面的代碼:[self stop]; AVMetadataMachineReadableCodeObject * metadataObject = metadataObjects[0]; if ([self.delegate respondsToSelector: @selector(scanView:codeInfo:)]) { [self.delegate scanView: self codeInfo: metadataObject.stringValue]; [self removeFromSuperview]; } else { [[NSNotificationCenter defaultCenter] postNotificationName: LXDSuccessScanQRCodeNotification object: self userInfo: @{ LXDScanQRCodeMessageKey: metadataObject.stringValue }];
讀取二維碼信息進行應用跳轉
首先要說明的是,二維碼並非一定要存儲應用的url scheme。例如公衆號的二維碼,雖然不知道是怎樣的數據存儲,但肯定不是應用跳轉。可以給自己的應用指定一個二維碼數據規則,例如支付寶付款掃描是讀取商品的ID、價格等信息,然後進行頁面跳轉付款。
這裏我們使用上面設置的url scheme,我們通過製作二維碼方法來定製一個存儲應用跳轉信息的二維碼,通過下面的代理創建一個存儲url scheme(使用url scheme的時候要注意在後面加上://後才能使用openURL進行跳轉)的二維碼,這一步應該放到模擬器上面生成
- (IBAction)createBarcode:(id)sender
{
UIImage image = [UIImage imageOfQRFromURL: @"LXDDrawLosts://" codeSize: 160.f red: 123 green: 189 blue: 229 insertImage: nil];
CGSize size = image.size;
UIImageView imageView = [[UIImageView alloc] initWithFrame: ((CGRect){(CGPointZero), (size)})];
imageView.center = self.view.center;
imageView.image = image;
[self.view addSubview: imageView];
}
創建二維碼掃描控制器,然後對我們生成的二維碼進行掃描(這一步要在真機上面完成,上面url scheme的應用應當通過xcode安裝在手機上,才能完成跳轉)
LXDScanCodeController * scanCodeController = [LXDScanCodeController scanCodeController];
scanCodeController.scanDelegate = self;
[self.navigationController pushViewController: scanCodeController animated: YES];
掃描成功後判斷是否可以打開跳轉,如果你的應用有一套二維碼數據存儲的規則,那麼在不能跳轉的時候應該按照這套規則解析數據。這裏我直接在無法跳轉的情況下顯示警告框告訴用戶無法解析二維碼:
NSURL * url = [NSURL URLWithString: codeInfo];
if ([[UIApplication sharedApplication] canOpenURL: url]) {
[[UIApplication sharedApplication] openURL: url];
} else {
UIAlertView * alertView = [[UIAlertView alloc] initWithTitle: @"警告" message: [NSString stringWithFormat: @"%@:%@", @"無法解析的二維碼", codeInfo] delegate: nil cancelButtonTitle: @"確定" otherButtonTitles: nil];
[alertView show];
}
按照上面的步驟進行的話,那麼在你掃完二維碼之後,你的手機就會跳轉到剛纔設置url scheme的應用中。
掃描優化
上面已經完成了二維碼的掃描功能實現,但是現在你會發現我們在使用上面代碼進行掃描的時候,整個屏幕都是掃描範圍,這樣會影響掃描的準確性以及我們調整掃描範圍的難度。
蘋果提供了一種方式讓我們規定掃描範圍:在AVCaptureMetadataOutput
中有一個叫做rectOfInterest
的CGRect類型屬性,這個屬性用來限制掃描範圍。
這個屬性的每一個值取值範圍在0~1之間,代表的是對應軸上的比例大小。最開始我以爲這個是以左上角爲原點,後來設置爲CGRectMake(0.3, 0.35, 0.4, 0.3)
發現和預期的不一樣,因爲這個屬性是以屏幕右上角爲座標原點,並且寬高的順序要對換過來
如圖所示,由於座標系的不同,原本CGRectMake(0.3, 0.35, 0.4, 0.3)
到了新座標系中就變成了CGRectMake(0.35, 0.3, 0.3, 0.4)
。那麼大家設置成新的掃描範圍之後,重新運行掃描程序,看看效果——然而,我們發現並不能掃描成功,這是因爲這個掃描區域不僅僅是座標系原點發生了改變。如下圖所示
按照上面CGRect的設置,我是想要把掃描範圍控制在屏幕x軸上面0.3-0.7,y軸上0.35-0.65之間的範圍。但是在這個屬性中,width和height分別表示的是在rectOfInterest座標中掃描矩形右下角的座標點位置。因此,這個掃描範圍應該是CGRectMake(0.35, 0.3, 0.65, 0.7)
。除了設置好掃描範圍之內,我們還可以仿照微信的掃描,給非掃描範圍加上一層半透明的黑色layer
應用傳值
前面說過,url scheme不僅僅支持應用跳轉,它還支持使用類似get請求的方式在應用間傳值。上面跳轉的url scheme是LXDDrawLosts://
,那麼類似get請求,我們在這個字符串後面加上一個?
表示區分開參數和應用id,使用&
分隔不同參數,然後後面按照字段名=屬性值
的方式拼湊鏈接。
比如,假設這是一個即時通訊app,那麼我可以制定這樣的一個跳轉參數規則:
method
表示操作類型userId
用戶idtitle
分享標題message
分享消息link_url
分享鏈接
那麼,如果傳入的是LXDDrawLosts://?method=addFriends&userId=10086
這可能代表的是掃描後添加id爲10086的新好友。
又比如LXDDrawLosts://?method=shareMessage&title=分享測試&message=這是林欣達的分享測試&link_url=http://www.jianshu.com/users/0cf7d455eb9e/latest_articles
這代表分享信息到你的app中。這些都是我們自己的應用可以制定的規則,如果有興趣,可以新浪微博開放平臺或者騰訊開放平臺,他們的文檔中應該有url scheme的傳值標準。
說完了通過url scheme傳入參數後,怎麼把這些參數取出來呢?AppDelegate中提供了application:openURL: sourceApplication: annotation:
方法讓我們可以取出傳入的值。
在我們通過url scheme跳轉到本應用的時候,這個方法就會被系統調用。其中,有兩個重要的參數需要我們知道
sourceApplication
這個字符串保存了跳轉方app的url Identifier
,就是上文中除了url scheme
以外的另一個字段url
這個鏈接中存儲了跳轉的url scheme
以及參數列表,我們通過[url scheme]
方法獲取前者;用[url query]
方法獲取?
之後的參數列表,然後使用字符串的分隔方法把這些數據讀取出來
單純的二維碼數據並沒有過於強大的功能,但結合了url scheme的跳轉機制後,二維碼能夠幫助我們的應用獲得更加強大的能力,使得我們的應用之間有了更多聯繫。