關于掃一掃,是時候放棄第三方了

原文地址:張飛的技術博客

掃一掃這個功能我們并不陌生,微信QQ加好友掃一掃,路邊搞推廣的掃一掃,吃飯付錢掃一掃。看來這個掃一掃應用的地方還真不少啊!打開手機看了看安裝的APP,社交類APP有掃一掃,支付類的APP有掃一掃,連瀏覽器都有掃一掃。看來這個掃一掃不可小視!說不定哪天在你的項目中你的boss就要你給他加個掃一掃的功能呢!對于iOS開發要想做掃一掃這個功能,并不是很難,我們需要做的就是處理二維碼(QRCode)而已。在iOS7之前需要借助第三方庫ZBar和ZXing。在iOS7以后用自帶API就能輕松搞定。不用在項目中導入臃腫的第三方庫和擔心編譯出問題了。

二維碼是個什么鬼?

二維條碼是指在一維條碼的基礎上擴展出另一維具有可讀性的條碼,使用黑白矩形圖案表示二進制數據,被設備掃描后可獲取其中所包含的信息。----維基百科

詳細的生成細節和原理可以看看二維碼的生成細節和原理這篇文章。

掃描二維碼

首先我們來想一想具體的步驟,大概流程應該是:1.打開設備的攝像頭-->2.進行二維碼圖像捕獲-->3.獲取捕獲的圖像進行解析-->4.取得解析結果進行后續處理。這些流程需要用到AVFoundation這個庫,注意導入。

//獲取一個AVCaptureDevice對象,可以理解為打開攝像頭這樣的動作
AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
//獲取一個AVCaptureDeviceInput對象,將上面的'攝像頭'作為輸入設備
AVCaptureDeviceInput *input = [AVCaptureDeviceInput deviceInputWithDevice:_device error:nil];
//拍完照片以后,需要一個AVCaptureMetadataOutput對象將獲取的'圖像'輸出,以便進行對其解析
AVCaptureMetadataOutput *output = [[AVCaptureMetadataOutput alloc]init];
//獲取輸出需要設置代理,在代理方法中獲取
[output setMetadataObjectsDelegate:self queue:dispatch_get_main_queue()];
//設置輸出類型,如AVMetadataObjectTypeQRCode是二維碼類型,下面還增加了條形碼。如果掃描的是條形碼也能識別
output.metadataObjectTypes = @[AVMetadataObjectTypeEAN13Code,
                                    AVMetadataObjectTypeEAN8Code,
                                    AVMetadataObjectTypeCode128Code,
                                    AVMetadataObjectTypeQRCode];

上面完成了捕獲的設置,但是并未正在開始'掃描',要完成一次掃描的過程,需要用到AVCaptureSession這個類,這個session類把一次掃描看做一次會話,會話開始后才是正在的'掃描'開始,具體代碼如下。

AVCaptureSession *session = [[AVCaptureSession alloc]init];
[session setSessionPreset:AVCaptureSessionPresetHigh];//掃描的質量
if ([session canAddInput:input]){
   [session addInput:input];//將輸入添加到會話中
}  
if ([session canAddOutput:output]){
   [session addOutput:output];//將輸出添加到會話中
}

接下來我們要做的不是立即開始會話(開始掃描),如果你現在調用會話的startRunning方法的話,你會發現屏幕是一片黑,這時由于我們還沒有設置相機的取景器的大小。設置取景器需要用到AVCaptureVideoPreviewLayer這個類。具體代碼如下:

AVCaptureVideoPreviewLayer *preview =[AVCaptureVideoPreviewLayer layerWithSession:session];
preview.videoGravity = AVLayerVideoGravityResize;
[preview setFrame:self.view.bounds];//設置取景器的frame
[self.view.layer insertSublayer:preview atIndex:0];

接下來我們就可以調用session的startRunning方法了,這時我們的掃描就真正開始了。想要獲得掃描的結果,需要實現session的代理方法- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects fromConnection:(AVCaptureConnection *)connection,代碼如下:

- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects fromConnection:(AVCaptureConnection *)connection
{
    [self.session stopRunning];//停止會話
    [self.preview removeFromSuperlayer];//移除取景器
    
    if (metadataObjects.count > 0) {
        AVMetadataMachineReadableCodeObject *obj = metadataObjects[0];
        NSString *result = obj.stringValue;//這就是掃描的結果啦
        //對結果進行處理...
    }
}

如果要做到為用戶考慮的話,還得加入照明的功能或者設置興趣區域。

  • 設設置興趣區域

使用過微信中的掃一掃的話,你應該發現在掃描界面中間有一個矩形限定框,這個框就是興趣區域了。這個興趣區域是AVCaptureMetadataOutputrectOfInterest屬性。rectOfInterest的值的范圍都是0-1,是按比例取值而不是實際尺寸,所以設置的時候要注意的一點。還要注意rectOfInterest都是按照橫屏來計算的,所以當豎屏的情況下x軸和y軸要交換一下。代碼如下:

CGSize size = self.view.bounds.size;
CGSize transparentAreaSize = CGSizeMake(200,200);
CGRect cropRect = CGRectMake((size.width - transparentAreaSize.width)/2, (size.height - transparentAreaSize.height)/2, transparentAreaSize.width, transparentAreaSize.height);
output.rectOfInterest = CGRectMake(cropRect.origin.y/size.width,
                                              cropRect.origin.x/size.height,
                                              cropRect.size.height/size.height,
                                              cropRect.size.width/size.width);
  • 加入照明功能

加入照明功能能讓用戶在光照條件不好的情況下順利的進行進行掃描操作,代碼如下:

AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
NSError *error;
if (device.hasTorch) {  // 判斷設備是否有閃光燈
    BOOL b = [device lockForConfiguration:&error];
    if (!b) {
       if (error) {
          NSLog(@"lock torch configuration error:%@", error.localizedDescription);
       }
       return;
    }
    device.torchMode = (device.torchMode == AVCaptureTorchModeOff ? AVCaptureTorchModeOn : AVCaptureTorchModeOff);
    [device unlockForConfiguration];
    }

從圖片中讀取二維碼

從圖片中直接讀取二維碼的功能在iOS7上面蘋果沒有實現,不過在iOS上已經填補了這一功能。主要用到的是讀取主要用到CoreImage。

廢話不多說,直接上代碼。

+ (NSString *)scQRReaderForImage:(UIImage *)qrimage{
    CIContext *context = [CIContext contextWithOptions:nil];
    CIDetector *detector = [CIDetector detectorOfType:CIDetectorTypeQRCode context:context options:@{CIDetectorAccuracy:CIDetectorAccuracyHigh}];
    CIImage *image = [CIImage imageWithCGImage:qrimage.CGImage];
    NSArray *features = [detector featuresInImage:image];
    CIQRCodeFeature *feature = [features firstObject];
    NSString *result = feature.messageString;
    return result;
}

既然講到要從相冊獲取照片,那么順便把從相冊獲取照片也講一下吧。

從相冊獲取照片主要用到的是UIImagePickerController,這是蘋果給我們分裝好的一個相冊選取的控制器。實現起來也是很簡單的。

- (void)readerImage{
    UIImagePickerController *photoPicker = [[UIImagePickerController alloc] init];
    photoPicker.delegate = self;
    photoPicker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
    photoPicker.view.backgroundColor = [UIColor whiteColor];
    [self presentViewController:photoPicker animated:YES completion:NULL];
}

當我們從照片庫選擇取了照片后要帶調用UIImagePickerController的代理方法獲取選擇的照片。

- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info{
    [self dismissViewControllerAnimated:YES completion:^{
      //code is here ...  
    }];
    
    UIImage *srcImage = [info objectForKey:UIImagePickerControllerOriginalImage];
    NSString *result = [QRCScanner scQRReaderForImage:srcImage];//調用上面講過的方法對圖片中的二維碼進行處理
    [self.navigationController popViewControllerAnimated:YES];
}

生成二維碼

生成二維碼和從圖片中讀取二維碼一樣要用到CoreImage,具體步驟如下:

- (UIImage *)makeQRCodeForString(NSString *)string{
    NSString *text = string;
    NSData *stringData = [text dataUsingEncoding: NSUTF8StringEncoding];
    //生成
    CIFilter *qrFilter = [CIFilter filterWithName:@"CIQRCodeGenerator"];
    [qrFilter setValue:stringData forKey:@"inputMessage"];
    [qrFilter setValue:@"M" forKey:@"inputCorrectionLevel"];
    //二維碼顏色
    UIColor *onColor = [UIColor redColor];
    UIColor *offColor = [UIColor blueColor];
    //上色,如果只要白底黑塊的QRCode可以跳過這一步
    CIFilter *colorFilter = [CIFilter filterWithName:@"CIFalseColor"
                                          keysAndValues:
                             @"inputImage",qrFilter.outputImage,
                             @"inputColor0",[CIColor colorWithCGColor:onColor.CGColor],
                             @"inputColor1",[CIColor colorWithCGColor:offColor.CGColor],
                             nil];
    //繪制
    CIImage *qrImage = colorFilter.outputImage;
    CGSize size = CGSizeMake(300, 300);
    CGImageRef cgImage = [[CIContext contextWithOptions:nil] createCGImage:qrImage  fromRect:qrImage.extent];
    UIGraphicsBeginImageContext(size);
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGContextSetInterpolationQuality(context, kCGInterpolationNone);
    CGContextScaleCTM(context, 1.0, -1.0);//生成的QRCode就是上下顛倒的,需要翻轉一下
    CGContextDrawImage(context, CGContextGetClipBoundingBox(context), cgImage);
    UIImage *codeImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    CGImageRelease(cgImage);
    
    return [UIImage imageWithCIImage:qrImage];
}

當然如果你不想寫這個代碼的話,你也可以使用一個輕量級的開源代碼libqrencode來幫你實現。它的使用非常簡單,導入UIImage+MDQRCode這個擴展后,使用UIImage的類方法就可以調用了。

+ (UIImage *)mdQRCodeForString:(NSString *)qrString size:(CGFloat)size;
+ (UIImage *)mdQRCodeForString:(NSString *)qrString size:(CGFloat)size fillColor:(UIColor *)fillColor;

QRCScanner

如果你連上面的代碼一點兒都不想寫,想一句話就集成掃描功能,你可以使用我封裝的QRCScanner,地址點這里

總結

用第三方ZBar,ZXing是導入有問題?或者是編譯有問題?使用自帶的就不用擔心這些問題,而且是秒掃哦!如果你覺得我的文章對你有幫助,請隨意打賞!


最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,797評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,179評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,628評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,642評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,444評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,948評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,040評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,185評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,717評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,602評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,794評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,316評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,045評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,418評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,671評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,414評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,750評論 2 370

推薦閱讀更多精彩內容

  • WebSocket-Swift Starscream的使用 WebSocket 是 HTML5 一種新的協議。它實...
    香橙柚子閱讀 23,988評論 8 183
  • 發現 關注 消息 iOS 第三方庫、插件、知名博客總結 作者大灰狼的小綿羊哥哥關注 2017.06.26 09:4...
    肇東周閱讀 12,149評論 4 61
  • 我的表弟在6月19日出生了!我期待著他的到來,最為好奇的就是他的那張臉。 媽媽抱著表弟,他被一個棉被裹起來...
    少女故事閱讀 228評論 0 0
  • 昨天我參加了一場婚禮,婚禮的主人公是媽媽朋友的兒子。正當我興致勃勃的看新郎和新娘喝交杯酒的時候,坐在我旁邊的王阿姨...
    韓雅潔閱讀 1,350評論 4 5
  • 1.新貴的目標是把“迷你退休”分配到整個一生,而不是愚蠢地把休息和享樂一再延遲統統放到退休以后。只在身體機能處于最...
    瑩瑩happygo閱讀 443評論 5 4