iOS開發-定制多樣式二維碼。二維碼/條形碼是按照某種特定的幾何圖形按一定規律在平台(一維/二維方向上)分布的黑白相間的圖形紀錄符號信息。使用若干個與二進制對應的幾何形體來表示文字數值信息。
最常見的二維碼功能包括信息獲取、網站跳轉、電商交易、手機支付等等,其擁有密度小、信息容量大、容錯能力強、成本低、制作難度低等優點。在移動開發中,二維碼的地位也越來越重要,掌握二維碼的基本操作是重要的本領之一。
在iOS7之後,蘋果自身集成了二維碼的生成和讀取功能。生成二維碼包括以下步驟<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxwPjGhorW8yOtDb3JlSW1hZ2UvQ29yZUltYWdlLmjNt87EvP48L3A+DQo8cD4yoaLKudPDQ0lGaWx0ZXLCy761wODJ+rPJtv7OrMLrPC9wPg0KPHA+M6GittTJ+rPJtcS2/s6swuu9+NDQvNO5pKOsyrnG5Lj8x+XO+jwvcD4NCjxwPrP9wcvJz8r2yP249rK91ujWrs3io6zO0sPHu7m/ydLUttS2/s6swuu9+NDQvfjSu7K9tcTN2NW5tKbA7TwvcD4NCjxwPjGhotfUtqjS5bb+zqzC6828sLjR1cmrPC9wPg0KPHA+MqGi1Nq2/s6swuvW0NDEsuXI69SyvcfQoc28xqw8L3A+DQo8cD4zoaLU2tSyvcfNvMasz8LD5rzTyc/Su7Lj1LK9x7DXyavNvMasPC9wPg0KPGgyIGlkPQ=="二維碼生成">二維碼生成 碼農們生產代碼的同時永遠不要忘記盡可能的復用,那麼為了實現這種目的,本文的代碼通過類別拓展UIImage的方法來完成。我們先聲明並實現一個類方法用來接收二維碼存儲數據以及二維碼尺寸的方法:
+ (UIImage *)imageOfQRFromURL: (NSString *)networkAddress codeSize: (CGFloat)codeSize {
if (!networkAddress|| (NSNull *)networkAddress == [NSNull null]) { return nil; }
codeSize = [self validateCodeSize: codeSize];
CIImage * originImage = [self createQRFromAddress: networkAddress];
UIImage * result = [UIImage imageWithCIImage: originImage];
return result;
}
在上面的代碼裡面,我們總共做了四件事情:驗證存儲信息的有效性;驗證二維碼尺寸的合理大小;使用存儲信息生成二維碼;將二維碼轉成UIImage返回。這些方法的實現分別如下:
/*! 驗證二維碼尺寸合法性*/
+ (CGFloat)validateCodeSize: (CGFloat)codeSize
{
codeSize = MAX(160, codeSize);
codeSize = MIN(CGRectGetWidth([UIScreen mainScreen].bounds) - 80, codeSize);
return codeSize;
}
/*! 利用系統濾鏡生成二維碼圖*/
+ (CIImage *)createQRFromAddress: (NSString *)networkAddress
{
NSData * stringData = [networkAddress dataUsingEncoding: NSUTF8StringEncoding];
CIFilter * qrFilter = [CIFilter filterWithName: @"CIQRCodeGenerator"];
[qrFilter setValue: stringData forKey: @"inputMessage"];
[qrFilter setValue: @"H" forKey: @"inputCorrectionLevel"];
return qrFilter.outputImage;
}
ps:
上面的代碼生成了一個粗略的二維碼圖,我們需要對圖片再進行一次處理,使其清晰化。因為,我們需要另外一個類別方法:對於CIFilter想要更進一步了解,可以在xcode中使用快捷鍵shift+command+0打開文檔,然後搜索core image filter reference獲取更多濾鏡的使用方法,這些濾鏡可以用來實現類似美圖秀秀的修圖功能。
/*! 對圖像進行清晰化處理*/
+ (UIImage *)excludeFuzzyImageFromCIImage: (CIImage *)image size: (CGFloat)size
{
CGRect extent = CGRectIntegral(image.extent);
CGFloat scale = MIN(size / CGRectGetWidth(extent), size / CGRectGetHeight(extent));
size_t width = CGRectGetWidth(extent) * scale;
size_t height = CGRectGetHeight(extent) * scale;
//創建灰度色調空間
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceGray();
CGContextRef bitmapRef = CGBitmapContextCreate(nil, width, height, 8, 0, colorSpace, (CGBitmapInfo)kCGImageAlphaNone);
CIContext * context = [CIContext contextWithOptions: nil];
CGImageRef bitmapImage = [context createCGImage: image fromRect: extent];
CGContextSetInterpolationQuality(bitmapRef, kCGInterpolationNone);
CGContextScaleCTM(bitmapRef, scale, scale);
CGContextDrawImage(bitmapRef, extent, bitmapImage);
CGImageRef scaledImage = CGBitmapContextCreateImage(bitmapRef);
CGContextRelease(bitmapRef);
CGImageRelease(bitmapImage);
CGColorSpaceRelease(colorSpace);
return [UIImage imageWithCGImage: scaledImage];
}
那麼這時候,我們把+(UIImage *)imageOfQRFromURL: codeSize: 的最後改成
UIImage * result =[self excludeFuzzyImageFromCIImage: originImage size: codeSize];
示例完成後生成的二維碼效果圖如下:
單一的黑白色二維碼並不一定總能滿足開發的需求或者說領導的需求。好比現在的應用很多功能界面上都在朝著微信學習,這就包括了更多色彩,更多樣式的二維碼。
本文將從 顏色、二維碼中心小圖案 這兩點入手講解如何制作類似微信生成我的二維碼的樣式。
自定義二維碼顏色的實現思路是,遍歷生成的二維碼的像素點,將其中為白色的像素點填充為透明色,非白色則填充為我們自定義的顏色。但是,這裡的白色並不單單指純白色,rgb值高於一定數值的灰色我們也可以視作白色處理。在這裡我對白色的定義為rgb值高於0xd0d0d0的顏色值為白色,這個值並不是確定的,大家可以自己設置。基於顏色的設置,我們將原有生成二維碼的方法接口改成這樣:
+ (UIImage *)imageOfQRFromURL: (NSString *)networkAddress codeSize: (CGFloat)codeSize red: (NSUInteger)red green: (NSUInteger)green blue: (NSUInteger)blue {
if (!networkAddress || (NSNull *)networkAddress == [NSNull null]) { return nil; }
/** 顏色不可以太接近白色*/
NSUInteger rgb = (red << 16) + (green << 8) + blue;
NSAssert((rgb & 0xffffff00) <= 0xd0d0d000, @"The color of QR code is two close to white color than it will diffculty to scan");
codeSize = [self validateCodeSize: codeSize];
CIImage * originImage = [self createQRFromAddress: networkAddress];
UIImage * progressImage = [self excludeFuzzyImageFromCIImage: originImage size: codeSize]; //到了這裡二維碼已經可以進行掃描了
UIImage * effectiveImage = [self imageFillBlackColorAndTransparent: progressImage red: red green: green blue: blue]; //進行顏色渲染後的二維碼
return effectiveImage;
}
相較於前面的代碼,多了兩個步驟:判斷rgb的有效值;對二維碼進行顏色渲染。顏色渲染的過程包括獲取圖像的位圖上下文、像素替換、二進制圖像轉換等操作,具體代碼如下:
/*! 對生成二維碼圖像進行顏色填充*/
+ (UIImage *)imageFillBlackColorAndTransparent: (UIImage *)image red: (NSUInteger)red green: (NSUInteger)green blue: (NSUInteger)blue {
const int imageWidth = image.size.width;
const int imageHeight = image.size.height;
size_t bytesPerRow = imageWidth * 4;
uint32_t * rgbImageBuf = (uint32_t *)malloc(bytesPerRow * imageHeight);
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef context = CGBitmapContextCreate(rgbImageBuf, imageWidth, imageHeight, 8, bytesPerRow, colorSpace, kCGBitmapByteOrder32Little | kCGImageAlphaNoneSkipLast);
CGContextDrawImage(context, (CGRect){(CGPointZero), (image.size)}, image.CGImage);
//遍歷像素
int pixelNumber = imageHeight * imageWidth;
[self fillWhiteToTransparentOnPixel: rgbImageBuf pixelNum: pixelNumber red: red green: green blue: blue];
CGDataProviderRef dataProvider = CGDataProviderCreateWithData(NULL, rgbImageBuf, bytesPerRow, ProviderReleaseData);
CGImageRef imageRef = CGImageCreate(imageWidth, imageHeight, 8, 32, bytesPerRow, colorSpace, kCGImageAlphaLast | kCGBitmapByteOrder32Little, dataProvider, NULL, true, kCGRenderingIntentDefault);
UIImage * resultImage = [UIImage imageWithCGImage: imageRef];
CGImageRelease(imageRef);
CGColorSpaceRelease(colorSpace);
CGContextRelease(context);
return resultImage;
}
/*! 遍歷所有像素點進行顏色替換*/
+ (void)fillWhiteToTransparentOnPixel: (uint32_t *)rgbImageBuf pixelNum: (int)pixelNum red: (NSUInteger)red green: (NSUInteger)green blue: (NSUInteger)blue {
uint32_t * pCurPtr = rgbImageBuf;
for (int i = 0; i < pixelNum; i++, pCurPtr++) {
if ((*pCurPtr & 0xffffff00) < 0xd0d0d000) {
uint8_t * ptr = (uint8_t *)pCurPtr;
ptr[3] = red;
ptr[2] = green;
ptr[1] = blue;
} else {
//將白色變成透明色
uint8_t * ptr = (uint8_t *)pCurPtr;
ptr[0] = 0;
}
}
}
void ProviderReleaseData(void * info, const void * data, size_t size) {
free((void *)data);
}
ps:
在修改代碼之前,應該想清楚是否需要刪除原有代碼。類似這種二維碼的擴展,舊的二維碼生成接口可以留下來,然後在其中調用多參數的全能構造器(Designated Initializer)。
這時候距離微信還差一小步,我們要在二維碼的中心位置插入我們的小頭像,最直接的方式是加載完我們的頭像後,直接drawInRect:。這種實現方法是正確的,但是在我們畫上去之前,我們還需要對圖像進行圓角處理。(省事的可能直接用imageView加載頭像,然後設置頭像的cornerRadius,這個也能實現效果)。
到了這個時候,我們需要一個更多參數的二維碼生成方法接口了,這次新增的參數應該包括插入圖片、圓角半徑這些參數,因此方法如下:
+ (UIImage *)imageOfQRFromURL: (NSString *)networkAddress codeSize: (CGFloat)codeSize red: (NSUInteger)red green: (NSUInteger)green blue: (NSUInteger)blue insertImage: (UIImage *)insertImage roundRadius: (CGFloat)roundRadius {
if (!networkAddress || (NSNull *)networkAddress == [NSNull null]) { return nil; }
/** 顏色不可以太接近白色*/
NSUInteger rgb = (red << 16) + (green << 8) + blue;
NSAssert((rgb & 0xffffff00) <= 0xd0d0d000, @"The color of QR code is two close to white color than it will diffculty to scan");
codeSize = [self validateCodeSize: codeSize];
CIImage * originImage = [self createQRFromAddress: networkAddress];
UIImage * progressImage = [self excludeFuzzyImageFromCIImage: originImage size: codeSize]; //到了這裡二維碼已經可以進行掃描了
UIImage * effectiveImage = [self imageFillBlackColorAndTransparent: progressImage red: red green: green blue: blue]; //進行顏色渲染後的二維碼
return [self imageInsertedImage: effectiveImage insertImage: insertImage radius: roundRadius];
}
這次的生成方法同樣也只需要進行一次額外的調用方法操作,在插入圖片的時候我們需要注意,類似微信的圖中圖二維碼中間的小頭像是有一個圓角的白色邊緣的,這個邊緣的加入讓頭像顯示的更加自然。那麼要完成這個效果,我額外在項目中加入了一張白色背景的小圖,同樣對這張圖片進行圓角化處理,然後加在頭像的下面。作為頭像下方的白色背景圖像尺寸應該大於頭像圖。制作畫中畫效果的具體實現如下:
/*! 在二維碼原圖中心位置插入圓角圖像*/
+ (UIImage *)imageInsertedImage: (UIImage *)originImage insertImage: (UIImage *)insertImage radius: (CGFloat)radius {
if (!insertImage) { return originImage; }
insertImage = [UIImage imageOfRoundRectWithImage: insertImage size: insertImage.size radius: radius];
UIImage * whiteBG = [UIImage imageNamed: @"whiteBG"];
whiteBG = [UIImage imageOfRoundRectWithImage: whiteBG size: whiteBG.size radius: radius];
//白色邊緣寬度
const CGFloat whiteSize = 2.f;
CGSize brinkSize = CGSizeMake(originImage.size.width / 4, originImage.size.height / 4);
CGFloat brinkX = (originImage.size.width - brinkSize.width) * 0.5;
CGFloat brinkY = (originImage.size.height - brinkSize.height) * 0.5;
CGSize imageSize = CGSizeMake(brinkSize.width - 2 * whiteSize, brinkSize.height - 2 * whiteSize);
CGFloat imageX = brinkX + whiteSize;
CGFloat imageY = brinkY + whiteSize;
UIGraphicsBeginImageContext(originImage.size);
[originImage drawInRect: (CGRect){ 0, 0, (originImage.size) }];
[whiteBG drawInRect: (CGRect){ brinkX, brinkY, (brinkSize) }];
[insertImage drawInRect: (CGRect){ imageX, imageY, (imageSize) }];
UIImage * resultImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return resultImage;
}
+ (UIImage *)imageOfRoundRectWithImage: (UIImage *)image size: (CGSize)size radius: (CGFloat)radius
{
if (!image) { return nil; }
const CGFloat width = size.width;
const CGFloat height = size.height;
radius = MAX(5.f, radius);
radius = MIN(10.f, radius);
UIImage * img = image;
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef context = CGBitmapContextCreate(NULL, width, height, 8, 4 * width, colorSpace, kCGImageAlphaPremultipliedFirst);
CGRect rect = CGRectMake(0, 0, width, height);
//繪制圓角
CGContextBeginPath(context);
addRoundRectToPath(context, rect, radius, img.CGImage);
CGImageRef imageMasked = CGBitmapContextCreateImage(context);
img = [UIImage imageWithCGImage: imageMasked];
CGContextRelease(context);
CGColorSpaceRelease(colorSpace);
CGImageRelease(imageMasked);
return img;
}
ps:
在代碼中,對中心位置的頭像限制尺寸為二維碼的四分之一,這個尺寸下的頭像不失清晰度,而且圖片尺寸也不至於遮蓋了二維碼的存儲數據。上面的方法都可以在頭文件中開發方法接口使用,這將實現這些代碼的復用。另外,所有本文中寫到的生成二維碼的接口都應該在頭文件中聲明,並且在其實現中調用全能方法(不應當僅僅是構造器需要遵循Designated Initializer的原則):圖像繪制圓角是通過在圖像上下文中畫出圓角矩形的路徑,然後進行裁剪,這樣就能實現圖片的圓角化。
+ (UIImage *)imageOfQRFromURL: (NSString *)networkAddress {
return [self imageOfQRFromURL: networkAddress codeSize: 100.0f red: 0 green: 0 blue: 0 insertImage: nil roundRadius: 0.f];
}