二維碼掃面當下很流行,也帶來了很多的便利。我也不是在吹捧,你也許你也發現,幾乎凡是個APP,都有掃描二維碼的功能。現在網上的資料很多,我也是閑的蛋疼,在做一個美團的高仿項目,涉及到二維碼的掃描功能,iOS7之前,開發者進行掃碼編程時,一般會借助第三方庫。常用的是ZBarSDKa和ZXingObjC,iOS7之后,系統的AVMetadataObject類中,為我們提供了解析二維碼的接口。經過測試,使用原生API掃描和處理的效率非常高,遠遠高于第三方庫。我查了很多的資料,網上關于用系統原生類來實現二維碼掃描的資料還是很多的,但都不是很全。所以我把一套功能的實現分享出來,希望你們喜歡。(剛來簡書,文章的排版還不是很熟,蛋疼!,還有就是打開項目時,要是不是真機仿真,要先注釋掉beginScanning()方法,要不程序會崩潰)
這里是Swift版的,如果你想使用OC來實現,翻譯過去我相信也不是難事。
OC也可以參考:http://yimouleng.com/2016/01/13/ios-QRCode/
這里包含:
- 1、窗口的搭建
- 2、二維碼的掃描
- 3、從相冊中讀取二維碼
- 4、生成二維碼
- 5、長按識別二維碼
其中二維碼的掃描主要是用到AVFoundation
這個框架中的AVCaptureSession
,讀取二維碼和識別二維碼是同個原理,只是處理方式不同,主要用到的是CIDetector
類,而生成二維碼要用到CoreImage
框架中的CIFilter
濾鏡類,值得一提的是CoreImage
非常強大,這是iOS中關于圖片處理的幾乎所有內容集合,即圖片的處理幾乎都在CoreImage
中,CIFilter
也非常值得一究,mac上應該在CoreQuartz
中。CoreImage
和CIFilter
的內容很多,我可能會在另外的文章中去分享。(我并不是大神,不敢吹牛逼說給大家講解,只是分享和大家相互學習)
詳情看看我的源碼:
美團的高仿項目 更多模塊中的“掃一掃”
1、窗口的搭建
這是UI的東西,很簡單,想怎么弄就怎么弄,只要你happy就ok,這是我的代碼:
1.1、設置導航條:setupNavView(),editItemAction()中因為在iOS9+平臺,所以用UIAlertController來列出多個ActionSheet功能
func setupNavView() {
self.navigationController?.navigationBar.barTintColor = UIColor.clearColor()
self.title = "二維碼/條形碼"
//設置標題顏色
let navigationTitleAttribute = NSDictionary(object: UIColor.whiteColor(), forKey: NSForegroundColorAttributeName)
self.navigationController?.navigationBar.titleTextAttributes = navigationTitleAttribute as? [String : AnyObject]
//1.返回
let backBtn = UIButton(type: UIButtonType.Custom)
backBtn.frame = CGRectMake(20, 30, 25, 25);
backBtn.setBackgroundImage(UIImage(named: "qrcode_scan_titlebar_back_nor"), forState:UIControlState.Normal);
backBtn.contentMode = UIViewContentMode.ScaleAspectFit
backBtn.addTarget(self, action: #selector(QRCodeScanViewController.backBtnAction), forControlEvents: UIControlEvents.TouchUpInside)
let backItem = UIBarButtonItem(customView: backBtn)
self.navigationItem.leftBarButtonItem = backItem
let editItem = UIBarButtonItem(barButtonSystemItem: .Edit, target: self, action: #selector(QRCodeScanViewController.editItemAction))
self.navigationItem.rightBarButtonItem = editItem
// deprecated //
// //2.相冊
// let albumBtn = UIButton(type: UIButtonType.Custom)
// albumBtn.frame = CGRectMake(0, 0, 35, 49)
// albumBtn.center = CGPointMake(self.view.bounds.width / 2, 20 + 49 / 2.0)
// albumBtn.setBackgroundImage(UIImage(named: "qrcode_scan_btn_photo_down"), forState: UIControlState.Normal)
// albumBtn.contentMode=UIViewContentMode.ScaleAspectFit
// albumBtn.addTarget(self, action: #selector(QRCodeScanViewController.openAlbum), forControlEvents: UIControlEvents.TouchUpInside)// self.view.addSubview(albumBtn)
//
// //3.閃光燈
// let flashBtn = UIButton(type: UIButtonType.Custom)
// flashBtn.frame = CGRectMake(self.view.bounds.width - 55, 20, 35, 49)
// flashBtn.setBackgroundImage(UIImage(named: "qrcode_scan_btn_flash_down"), forState: UIControlState.Normal)
// flashBtn.contentMode=UIViewContentMode.ScaleAspectFit
// flashBtn.addTarget(self, action: #selector(QRCodeScanViewController.openFlash(_:)), forControlEvents: UIControlEvents.TouchUpInside)
// self.view.addSubview(flashBtn)
func editItemAction() {
let actionSheet = UIAlertController(title: nil, message: nil, preferredStyle: .ActionSheet)
let action1 = UIAlertAction(title: "從相冊選取二維碼", style: .Default) {
[unowned self]
(act) in
self.openAlbum()
}
let action2 = UIAlertAction(title: "打開閃光燈", style: .Default) {
[unowned self]
(act) in
self.openFlash()
}
let action3 = UIAlertAction(title: "生成二維碼", style: .Default) {
[unowned self]
(act) in
let inputVC = UIAlertController(title: "輸入信息", message: nil, preferredStyle: .Alert)
let cancelAction = UIAlertAction(title: "取消", style: .Cancel, handler: nil)
let okayAction = UIAlertAction(title: "確定", style: .Default){
[unowned self]
(act) in
///生成二維碼圖片
let QRCodeImage = self.createQRCodeImage(withImage: UIImage(named: "icon_mine_default_portrait")!, string: self.textInfo!)
///展示在界面上
let imageBtn = UIButton(frame: CGRectMake(0, 64, SCREENWIDTH, SCREENHEIGHT - 64))
imageBtn.setImage(QRCodeImage, forState: .Normal)
imageBtn.addTarget(self, action: #selector(self.imageBtnAction(_:)), forControlEvents: .TouchUpInside)
imageBtn.backgroundColor = THEMECOLOR
self.view.addSubview(imageBtn)
///保存到相冊
//UIImageWriteToSavedPhotosAlbum(QRCodeImage, self, #selector(self.image(_: didFinishSavingWithError: contextInfo: )), nil)
inputVC.dismissViewControllerAnimated(true, completion: {
print("生成二維碼")
})
}
inputVC.addAction(cancelAction)
inputVC.addAction(okayAction)
inputVC.addTextFieldWithConfigurationHandler({ (textField) in
textField.borderStyle = .None
textField.placeholder = "輸入需要保存的信息"
textField.delegate = self
textField.becomeFirstResponder()
})
self.presentViewController(inputVC, animated: true, completion: {
print("輸入信息可以生成二維碼")
})
}
let action4 = UIAlertAction(title: "取消", style: .Cancel, handler: nil)
actionSheet.addAction(action1)
actionSheet.addAction(action2)
actionSheet.addAction(action3)
actionSheet.addAction(action4)
self.presentViewController(actionSheet, animated: true, completion: nil)
}
1.3、遮罩(蒙板)的設置:setupMaskView()
let kMargin = CGFloat(50)
func setupMaskView() {
maskView = UIView()
maskView.layer.borderColor = UIColor(red: CGFloat(0), green: CGFloat(0), blue: CGFloat(0), alpha: CGFloat(0.7)).CGColor
maskView.layer.borderWidth = kMargin
let maskViewSize = CGSizeMake(self.view.extWidth(), self.view.extWidth())///正方形,下面會露出來,還要添加補充遮罩
maskView.frame = CGRectMake(0, 64, maskViewSize.width, maskViewSize.height)
self.view.addSubview(maskView)
///補充遮罩
let mask = UIView(frame: CGRectMake(0, maskView.extY() + maskView.extHeight(), SCREENWIDTH, SCREENHEIGHT - (maskView.extY() + maskView.extHeight())))
mask.backgroundColor = UIColor(red: CGFloat(0), green: CGFloat(0), blue: CGFloat(0), alpha: CGFloat(0.7))
self.view.addSubview(mask)
}
1.4、掃描區域的設置:setupScanWindowView()
func setupScanWindowView() {
let scanWindowH = maskView.extWidth() - kMargin * 2 ///kMargin為黑色邊框的寬度,
let scanWindowW = scanWindowH
scanWindow = UIView(frame: CGRectMake(kMargin, kMargin + 64, scanWindowW, scanWindowH))///隱形的框
scanWindow.clipsToBounds = true
self.view.addSubview(scanWindow)
scanNetImageView = UIImageView(image: UIImage(named: "scan_net"))
scanNetImageView.extSetY(-1 * scanNetImageView.extHeight())
scanWindow.addSubview(scanNetImageView)
let buttonWH = CGFloat(18)
let topLift = UIImageView(frame: CGRectMake(0, 0, buttonWH, buttonWH))
topLift.image = UIImage(named: "scan_1")
let topRight = UIImageView(frame: CGRectMake(scanWindowW - buttonWH, 0, buttonWH, buttonWH))
topRight.image = UIImage(named: "scan_2")
let bottomLeft = UIImageView(frame: CGRectMake(0, scanWindowH - buttonWH, buttonWH, buttonWH))
bottomLeft.image = UIImage(named: "scan_3")
let bottomRight = UIImageView(frame: CGRectMake(topRight.frame.origin.x, bottomLeft.frame.origin.y, buttonWH, buttonWH))
bottomRight.image = UIImage(named: "scan_4")
scanWindow.addSubview(topLift)
scanWindow.addSubview(topRight)
scanWindow.addSubview(bottomLeft)
scanWindow.addSubview(bottomRight)
self.view.addSubview(scanWindow)
}
2、二維碼的掃描
主要用的的是AVCaptureSession
類,然后設置input
和output
即可,值得一提的是掃碼支持的類型:output.metadataObjectTypes
。iOS已經提供了很多,但是并不全,不過包含了常用的二維碼AVMetadataObjectTypeQRCode
和條形碼AVMetadataObjectTypeCode128Code
等
func beginScanning() {///要真機
///模擬圖片動起來
///way 1、UIView Animation
UIView.animateWithDuration(1.5, delay: 0, options: UIViewAnimationOptions.Repeat, animations: {
[unowned self] in
self.scanNetImageView.transform = CGAffineTransformTranslate(self.scanNetImageView.transform, 0, self.scanWindow.extHeight())
}, completion: nil)
///way 2、coreAnimation
//初始化鏈接對象
session = AVCaptureSession()
//高質量采集率
session.sessionPreset = AVCaptureSessionPresetHigh
let preLayer = AVCaptureVideoPreviewLayer(session: session)///注意session存放的地方
preLayer.frame = self.view.bounds
self.view.layer.insertSublayer(preLayer, atIndex: 0)
/*
* AVCaptureDevice 獲取攝像設備
* AVCaptureDeviceInput 創建輸入流
* AVCaptureMetadataOutput 創建輸出了
*/
let device = AVCaptureDevice.defaultDeviceWithMediaType(AVMediaTypeVideo)
var input: AVCaptureDeviceInput?
do {
input = try AVCaptureDeviceInput(device: device)
///add input
}catch let error as NSError {
// 發生了錯誤
print(error.localizedDescription)
}
catch {
print("--input未知錯誤--")
}
///add input
session.addInput(input)
let output = AVCaptureMetadataOutput()
///add delegate
output.setMetadataObjectsDelegate(self, queue: dispatch_get_main_queue())//主隊列(主線程)
///設置“感興趣”區域(敏感區域)
let interestRect = preLayer.metadataOutputRectOfInterestForRect(scanWindow.frame)///掃描區 到 metadata輸出區
///值等于CGRectMake(scanWindow.extY() / SCREENHEIGHT, scanWindow.extX() / SCREENWIDTH, scanWindow.extHeight() / SCREENHEIGHT, scanWindow.extWidth() / SCREENWIDTH)
///把一個在 preview layer 坐標系中的rect 轉換成一個在 metadata output 坐標系中的rect
output.rectOfInterest = interestRect ///注意,這個并不是掃描區的坐標尺寸
session.addOutput(output)
//設置掃碼支持的類型
output.metadataObjectTypes = [AVMetadataObjectTypeDataMatrixCode,
AVMetadataObjectTypeAztecCode,
AVMetadataObjectTypeQRCode,
AVMetadataObjectTypePDF417Code,
AVMetadataObjectTypeEAN13Code,
AVMetadataObjectTypeEAN8Code,
AVMetadataObjectTypeCode128Code]
///常用的碼制有:PDF417二維條碼、Datamatrix二維條碼、QR Code、Code 49、Code 16K、Code one等,
///除了這些常見的二維條碼之外,還有Vericode條碼、Maxicode條碼、CP條碼、Codablock F條碼、 Ultracode條碼及Aztec條碼。
///start grab
session.startRunning()
}
3、從相冊中讀取二維碼
主要用到的是CIDetector
檢測器類,然后獲取feature
對象。
/***********照片讀取**************/
func openAlbum(){//相冊
if(UIImagePickerController.isSourceTypeAvailable(UIImagePickerControllerSourceType.PhotoLibrary)){
///1.初始化相冊拾取器
let pikController = UIImagePickerController()
///2.設置代理
pikController.delegate = self//兩個代理
//3.設置資源:
/**
UIImagePickerControllerSourceTypePhotoLibrary,相冊
UIImagePickerControllerSourceTypeCamera,相機
UIImagePickerControllerSourceTypeSavedPhotosAlbum,照片庫
*/
pikController.sourceType = UIImagePickerControllerSourceType.SavedPhotosAlbum
//4.隨便給他一個轉場動畫
pikController.modalTransitionStyle = UIModalTransitionStyle.FlipHorizontal
self.presentViewController(pikController, animated: true, completion: nil)
}else{
let alertVC = UIAlertController(title: "提示", message: "設備不支持訪問相冊,請在設置->隱私->照片中進行設置!", preferredStyle: UIAlertControllerStyle.Alert)
let action = UIAlertAction(title: "OK", style: .Cancel, handler: nil)
alertVC.addAction(action)
self.presentViewController(alertVC, animated: true, completion: nil)
}
}
///imagePickerControllerdelegate func
func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : AnyObject]) {
///1.獲取選擇的圖片
let image = info[UIImagePickerControllerOriginalImage]
///2.初始化一個監測器
let detector = CIDetector(ofType: CIDetectorTypeQRCode, context: nil, options: [ CIDetectorAccuracy : CIDetectorAccuracyHigh ])
picker.dismissViewControllerAnimated(true) { () -> Void in
///監測到的結果數組
let features = detector.featuresInImage(CIImage(CGImage: (image?.CGImage)!))
if features.count >= 1 {
/**結果對象 */
///CIQRCodeFeature
let feature = features[0] as! CIQRCodeFeature
let scannedResult = feature.messageString
let alertVC = UIAlertController(title: "提示", message: scannedResult, preferredStyle: UIAlertControllerStyle.Alert)
self.presentViewController(alertVC, animated: true, completion: nil)
}else {
let alertVC = UIAlertController(title: "提示", message: "該圖片沒有包含一個二維碼!", preferredStyle: UIAlertControllerStyle.Alert)
let action = UIAlertAction(title: "OK", style: .Cancel, handler: nil)
alertVC.addAction(action)
self.presentViewController(alertVC, animated: true, completion: nil)
}
}
}
4、生成二維碼
生成二維碼處理過程有點復雜,首先要導入CoreImage
,然后用CIFilter
生成圖片,name
為CIQRCodeGenerator
就是二維碼濾鏡,CICode128BarcodeGenerator
就是條形碼濾鏡,當然還有很多濾鏡,如下面會用到的顏色濾鏡name
為CIFalseColor
。
4.1、原生二維碼的生成
/***********生成二維碼圖片**************/ ///coreImage
func createQRCodeImage(withImage image: UIImage, string: String) -> UIImage {
/// 1. 實例化二維碼濾鏡
let filter = CIFilter(name: "CIQRCodeGenerator")///CICode128BarcodeGenerator ///條形碼
///注意
/// 2. 恢復濾鏡的默認屬性
filter?.setDefaults()
/// 3. 將字符串轉換成二進制數據,(生成二維碼所需的數據)
let data = string.dataUsingEncoding(NSUTF8StringEncoding)
/// 4. 通過KVO把二進制數據添加到濾鏡inputMessage中
filter?.setValue(data, forKey: "inputMessage")
filter?.setValue("H", forKey: "inputCorrectionLevel")
/// 5. 獲得濾鏡輸出的圖像
let outputImage = filter?.outputImage ///CIImage
/// 6. 將CIImage轉換成UIImage,并放大顯示
//let originQRCodeImage = UIImage(CIImage: outputImage!, scale: 0.07, orientation: UIImageOrientation.Up) ///原生二維碼圖片 ///這樣將圖片放大會變得模糊
//return originQRCodeImage
///進行重繪
let newQRCodeImage = createUIimageWithCGImage(ciImage: outputImage!, widthAndHeightValue: 300)
return newQRCodeImage
}
如果你直接用這行代碼返回圖片
let originQRCodeImage = UIImage(CIImage: outputImage!, scale: 0.07, orientation: UIImageOrientation.Up) ///原生二維碼圖片 ///這樣將圖片放大會變得模糊
return originQRCodeImage
得到的圖片將會很模糊,因為這是將圖片進行放大了,但圖片將0.07改為了1,看到的圖片將會很小。
所以還得處理一下:
func createUIimageWithCGImage(ciImage image: CIImage, widthAndHeightValue wh: CGFloat) -> UIImage {
let ciRect = CGRectIntegral(image.extent)///根據容器得到適合的尺寸
let scale = min(wh / ciRect.width, wh / ciRect.height)
///獲取bitmap
let width = size_t(ciRect.width * scale)
let height = size_t(ciRect.height * scale)
let cs = CGColorSpaceCreateDeviceGray()///灰度顏色通道 ///CGColorSpaceRef
let info_UInt32 = CGImageAlphaInfo.None.rawValue
let bitmapRef = CGBitmapContextCreate(nil, width, height, 8, 0, cs, info_UInt32)
let contex = CIContext(options: nil)
let bitmapImageRef = contex.createCGImage(image, fromRect: CGRectMake(ciRect.origin.x, ciRect.origin.y, ciRect.size.width, ciRect.size.height)) ///CGImageRef
CGContextSetInterpolationQuality(bitmapRef, CGInterpolationQuality.High)///寫入質量高,時間長
CGContextScaleCTM(bitmapRef, scale, scale) ///調整“畫布”的縮放
CGContextDrawImage(bitmapRef, ciRect, bitmapImageRef) ///繪制圖片
///保存
let scaledImage = CGBitmapContextCreateImage(bitmapRef)
///bitmapRef和bitmapImageRef不用主動釋放,Core Foundation自動管理
//let originImage = UIImage(CGImage: scaledImage!) ///原生灰度圖片(灰色)
let ciImage = CIImage(CGImage: scaledImage!)
///添加濾鏡
let colorFilter = CIFilter(name: "CIFalseColor")///顏色濾鏡
colorFilter!.setDefaults()
colorFilter!.setValue(ciImage, forKey:kCIInputImageKey)
colorFilter!.setValue(CIColor(red: 33.0 / 225.0, green: 192.0 / 225.0, blue: 174.0 / 225.0, alpha: 1.0), forKey:"inputColor0")///二維碼元素(像素)
colorFilter!.setValue(CIColor(red: 1.0, green: 1.0, blue: 1.0, alpha: 1), forKey:"inputColor1")///背景
let colorImgae = colorFilter!.outputImage
let newQRCodeImage = UIImage(CIImage: colorImgae!)
return newQRCodeImage
}
這樣將會得到彩色的二維碼,效果也還不錯,這里又添加colorFilter這樣濾鏡,達到著色的效果。iOS其實可以混合多個濾鏡來實現很多不能的效果,手機自帶的圖片處理就是基于濾鏡來完成,如果你有使用過PS,或了解過知識,這就很容易理解了。如果去掉這部分代碼得到的將會是灰色的圖片。
其實重新著色,還可以通過,context來重繪或者通過改變每個像素的的顏色值來達到目的,但是難度會遠比添加一個濾鏡要復雜的多,而且,Swift中通過指針來處理數據,寫法將會更加復雜,這是因為Swift是一門語法安全性語言。也沒有*P這樣的寫法。
關于CIFilter的更多知識,我就會在后續文章中分享,你們也可以通過資料去學習,總之,這部分知識還是很容易理解的,也比較有趣。
更新的CIFilter文章:點擊這里 會教你獲取更清晰的二維碼。
最后,想了解更多詳情:請查看我的demo,記得給個Star,??????
下載:點擊這里