iOS使用CIFilter生成二维码

时间:2021-08-11 08:40:29

二维码(quick response code,简称qr code)是由水平和垂直两个方向上的线条设计而成的一种二维条形码(barcode)。可以编码网址、电话号码、文本等内容,能够存储大量的数据信息。自ios 7以来,二维码的生成和读取只需要使用core image框架和avfoundation框架就能轻松实现。在这里,我们主要介绍二维码的生成。关于二维码的读取,在 使用avfoundation读取二维码 文章中有详细介绍。

iOS使用CIFilter生成二维码

1 二维码的生成

生成一个二维码也就是根据提供的数据内容转换成一张二维码图像。从ios 7开始,我们只需要使用cifilter中的ciqrcodegenerator就可以轻易实现。只不过这样生成的二维码图像是一个ciimage对象,如果要在图像视图中显示,需要将其转换为uiimage对象。具体步骤如下:

①、使用名为 ciqrcodegenerator 的过滤器创建一个cifilter对象。

?
1
cifilter *filter = [cifilter filterwithname:@"ciqrcodegenerator"]

②、为cifilter对象设置 inputmessageinputcorrectionlevel 参数。

  1. inputmessage :是一个nsdata对象,用于表示被编码的数据。对于字符串或者url,需要使用nsisolatin1stringencoding字符串编码将其转换为nsdata对象。要注意的是,nsisolatin1stringencoding编码对于中文或表情无法生成,需要的话可以使用nsutf8stringencoding 替换。

  2. inputcorrectionlevel :是一个nsstring对象,通常使用单个字母来指定纠错率,默认值是 m 。该参数控制输出图像中编码的附加数据量以提供纠错。其纠错率越高,输出的图像越大,同时也允许代码的更大区域被破坏或模糊。通常有 lmqh 这四种可能的纠正模式,分别代表了7%、15%、25%、30%的错误恢复能力。

③、使用cifilter对象的 outputimage 属性获取生成的二维码图像

?
1
ciimage *outputimage = filter.outputimage;

④、对生成的二维码图像进行缩放。

由于生成的二维码图像尺寸一般都比较小,为了避免模糊,通常需要对它进行缩放以适应图像视图的大小。其缩放比例一般为图像视图宽度(或高度)与二维码图像宽度(或高度)的比值。

?
1
2
3
cgfloat scalex = imageview.bounds.size.width / qrcodeimage.extent.size.width;
cgfloat scaley = imageview.bounds.size.height / qrcodeimage.extent.size.height;
ciimage *transformedimage = [qrcodeimage imagebyapplyingtransform:cgaffinetransformmakescale(scalex, scaley)];

⑤、将二维码图像转换为uiimage对象。

?
1
imageview.image = [uiimage imagewithciimage:transformedimage];

2 应用示例

下面,我们就做一个如下图所示的二维码生成器:

iOS使用CIFilter生成二维码

其中主要实现的功能有:

  1. 生成和删除二维码。

  2. 通过滑动条对二维码图像进行缩放。

  3. 将二维码保存到相册。

2.1 创建项目

打开 xcode ,创建一个新的项目( file\new\project.. .),选择 ios 一栏下的 application 中的 single view application 模版,然后点击 next ,填写项目选项。在 product name 中填写 qrcodegeneratordemo ,选择 objective-c 语言,点击 nex t,选择文件位置,并单击 create 创建项目。

iOS使用CIFilter生成二维码

2.2 构建界面

打开 main.storyboard 文件,在当前控制器中嵌入导航控制器,并添加标题 qr code generator

iOS使用CIFilter生成二维码

在视图控制器中添加文本框、按钮、图像视图等,布局如下:

iOS使用CIFilter生成二维码

其中各元素及作用:

  1. text field:用于输入想要转换为二维码的数据内容,包括文本或url字符串。

  2. button:在这里具有双重作用,用于生成和清除二维码。

  3. image view:用于显示生成的二维码图像。

  4. slider:用来缩放生成的二维码图像。

打开辅助编辑器,将storyboard中的元素连接到代码中:

iOS使用CIFilter生成二维码

2.3 添加代码

2.3.1 生成二维码图像

由于使用cifilter对象生成的二维码图像将是一个ciimage对象,所以需要先在 viewcontroller.m 文件的接口部分声明一个ciimage对象的属性:

?
1
@property (strong, nonatomic) ciimage *qrcodeimage;

然后在实现部分添加 generateqrcodeimage 方法及代码:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- (void)generateqrcodeimage
{
  nsdata *data = [self.textfield.text datausingencoding:nsisolatin1stringencoding allowlossyconversion:no];
  
  // 创建并设置cifilter对象
  cifilter *filter = [cifilter filterwithname:@"ciqrcodegenerator"];
  [filter setvalue:data forkey:@"inputmessage"];
  [filter setvalue:@"q" forkey:@"inputcorrectionlevel"];
  
  // 获取生成的ciimage对象
  self.qrcodeimage = filter.outputimage;
  
  // 转换成uiimage对象,并显示在图像视图中
  self.imageview.image = [uiimage imagewithciimage:self.qrcodeimage];
}

clickbutton: 方法中调用该方法:

?
1
2
3
4
- (ibaction)clickbutton:(id)sender
{
  [self generateqrcodeimage];
}

此时,运行程序,在文本框中输入内容,点击按钮就可以看到生成的二维码:

iOS使用CIFilter生成二维码

仔细的话你会发现这里的二维码比较模糊,这是由于生成的二维码尺寸较小,在imageview中显示时被拉伸导致的。下面我们就通过调整二维码的缩放来解决图像模糊的问题。

修改 generateqrcodeimage 方法中的代码如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
- (void)generateqrcodeimage
{
  ... 
  // 获取生成的ciimage对象
  self.qrcodeimage = filter.outputimage;
  
  // 缩放ciimage对象
  cgfloat scalex = self.imageview.bounds.size.width / self.qrcodeimage.extent.size.width;
  cgfloat scaley = self.imageview.bounds.size.height / self.qrcodeimage.extent.size.height;
  ciimage *transformedimage = [self.qrcodeimage imagebyapplyingtransform:cgaffinetransformmakescale(scalex, scaley)];
  
  // 将调整后的ciimage对象转换成uiimage对象,并显示在图像视图中
  self.imageview.image = [uiimage imagewithciimage:transformedimage];
}

再次运行,你会看到一张清晰的二维码:

iOS使用CIFilter生成二维码

2.3.2 删除二维码图像

在这个demo中,按钮具有双重作用:即生成二维码和删除二维码。因此需要在 clickbutton: 方法中先判断二维码图像是否存在:

  1. 如果不存在,点击按钮生成一张二维码图像,按钮的标题变为" clear "。

  2. 如果存在,点击按钮删除二维码图像,按钮的标题变为" generate "。

修改 clickbutton: 方法中的代码如下:

?
1
2
3
4
5
6
7
8
9
10
11
- (ibaction)clickbutton:(id)sender
{
  if (self.qrcodeimage == nil) {
    [self generateqrcodeimage];
    [self.button settitle:@"clear" forstate:uicontrolstatenormal];
  }
  else {
    [self clearqrcodeimage];
    [self.button settitle:@"generate" forstate:uicontrolstatenormal];
  }
}

下面我们就添加 clearqrcodeimage 方法并实现它:

?
1
2
3
4
5
6
- (void)clearqrcodeimage
{
  self.imageview.image = nil;
  self.qrcodeimage = nil;
  self.textfield.text = nil;
}

至此,二维码的生成和删除已基本完成,但为了有良好的体验,我们还需要考虑下面的情况:

  1. 在文本框未输入的情况下点击按钮是否生成二维码。

  2. 切换按钮时文本框的响应状态以及键盘出现与消失。

  3. 滑动条的显示和隐藏。

于是,在 clickbutton: 方法中添加下面的代码:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
- (ibaction)clickbutton:(id)sender
{
  if (self.qrcodeimage == nil) {
    if ([self.textfield.text isequaltostring:@""]) {
      return;
    }
    [self generateqrcodeimage];
    
    [self.textfield resignfirstresponder];
    [self.button settitle:@"clear" forstate:uicontrolstatenormal];
  }
  else {
    [self clearqrcodeimage];
    
    [self.button settitle:@"generate" forstate:uicontrolstatenormal];
  }
  
  self.textfield.enabled = !self.textfield.enabled;
  self.slider.hidden = !self.slider.hidden;
}

最后,考虑到程序启动时滑动条不应该显示(滑动条只在生成二维码时出现),还需要在 viewdidload 方法中设置其 hidden 属性:

?
1
2
3
4
5
6
- (void)viewdidload
{
  [super viewdidload];
  
  self.slider.hidden = yes;
}

可以再次运行试下。

2.3.3 缩放二维码图像

缩放显示的二维码主要是通过拖动滑动条缩放image view来完成的。在实现部分找到 changescale: 方法,并添加代码下面的代码即可:

?
1
2
3
4
- (ibaction)changescale:(id)sender
{
  self.imageview.transform = cgaffinetransformmakescale((cgfloat)self.slider.value, (cgfloat)self.slider.value);
}

需要注意的是, self.slider.valuefloat 类型,而 cgaffinetransformmakescale 方法的参数是 cgfloat 类型,因此在上面的代码中进行了类型转换。

2.3.4 保存二维码图像

将生成的二维码图片保存到相机胶卷相册主要是使用 uiimagewritetosavedphotosalbum 函数来实现的。其完整声明如下:

 

复制代码 代码如下:

void uiimagewritetosavedphotosalbum(uiimage *image, id completiontarget, sel completionselector, void *contextinfo);

 

其中各参数及含义:

image :表示要保存到相册的图像。

completiontarget :可选的参数,表示图片保存后,调用完成选择器(completionselector)的对象。

completionselector :可选的方法,表示图片保存后,completiontarget对象要调用的方法(即回调方法)。该方法应符合以下签名:

?
1
2
3
- (void)image:(uiimage *)image
  didfinishsavingwitherror:(nserror *)error
         contextinfo:(void *)contextinfo;

contextinfo :可选的参数,用于提供一个上下文信息以通过参数传递给completionselector。

在这里我们的大致思路是:首先在imageview中添加单击手势,当用户点击二维码图像时会弹出一个提示框询问是否保存,如果用户点击保存按钮,那么就将二维码保存到相册中。下面是具体实现:

打开 main.storyboard 文件,从对象库中找到点击手势(tap gesture recognizer),将其添加到视图控制器的image view上,完成之后会在控制器的顶部看到它:

iOS使用CIFilter生成二维码

打开辅助编辑器,将其连接到代码中:

iOS使用CIFilter生成二维码

为了使image view能响应手势操作,需要在 viewdidload 中设置image view的 userinteractionenabled 属性:

?
1
2
3
4
5
- (void)viewdidload
{
  ...
  self.imageview.userinteractionenabled = yes;
}

然后在实现部分找到 tap: 方法,并添加下面的代码:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- (ibaction)tap:(id)sender
{
  // 添加提示框
  uialertcontroller *alertcontroller = [uialertcontroller alertcontrollerwithtitle:@"save qrcode?" message:@"the qrcode will be saved in camera roll album." preferredstyle:uialertcontrollerstylealert];
  
  uialertaction *saveaction = [uialertaction actionwithtitle:@"save" style:uialertactionstyledefault handler:^(uialertaction * _nonnull action) {
    // 保存二维码图像
    [self saveqrcodeimage];
  }];
  uialertaction *cancelaction = [uialertaction actionwithtitle:@"cancel" style:uialertactionstylecancel handler:nil];
  
  [alertcontroller addaction:saveaction];
  [alertcontroller addaction:cancelaction];
  
  [self presentviewcontroller:alertcontroller animated:yes completion:nil];
}

这时编译器会有红色警告提示 saveqrcodeimage 方法未声明。接下来我们就在实现部分添加该方法来实现二维码的保存:

?
1
2
3
4
5
6
7
8
9
10
11
- (void)saveqrcodeimage
{
  // 绘制图像
  uigraphicsbeginimagecontext(self.imageview.image.size);
  [self.imageview.image drawinrect:self.imageview.bounds];
  self.imageview.image = uigraphicsgetimagefromcurrentimagecontext();
  uigraphicsendimagecontext();
  
  // 保存图像
  uiimagewritetosavedphotosalbum(self.imageview.image, nil, nil, nil);
}

值得说明的是,这里的 self.imageview.image 是从ciimage对象转换来的,是保存不到相册的,需要先在图形上下文中绘制一下,生成一个新的图像,然后才能保存。

此时,运行程序,点击生成的二维码图像你会看到下面的效果:

iOS使用CIFilter生成二维码

但是点击 save 按钮时,程序会崩溃,在控制台会看到下面的消息:

this app has crashed because it attempted to access privacy-sensitive data without a usage description. the app's info.plist must contain an nsphotolibraryaddusagedescription key with a string value explaining to the user how the app uses this data.

这是因为ios要求应用程序开发者在允许访问相册之前要先获得用户的许可。为此,我们必须在 info.plist 文件中添加名为 nsphotolibraryaddusagedescription 的键,并为其添加相应的描述:

iOS使用CIFilter生成二维码

问题修复完成!再次运行,点击保存按钮,打开相册应用,你会看到刚才保存的二维码:

iOS使用CIFilter生成二维码

到目前为止,我们的工作已基本完成,但是为了更完美一些,最好是在点击保存按钮之后能够收到是否保存成功的反馈。所以需要在 saveqrcodeimage 方法中更改 uiimagewritetosavedphotosalbum 函数的参数:

?
1
2
3
4
5
6
7
- (void)saveqrcodeimage
{
  ...
  
  // 保存图像
  uiimagewritetosavedphotosalbum(self.imageview.image, self, @selector(image:didfinishsavingwitherror:contextinfo:), nil);
}

然后添加 image:didfinishsavingwitherror:contextinfo: 方法,并在该方法内创建alert view来显示二维码的保存状态:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
- (void)image:(uiimage *)image didfinishsavingwitherror:(nserror *)error contextinfo:(void *)contextinfo
{
  nsstring *title;
  nsstring *message;
  
  if (!error) {
    title = @"success!";
    message = @"the qrcode image saved successfully.";
  }
  else {
    title = @"failed!";
    message = @"the qrcode image saved unsuccessfully, please try again later.";
  }
  
  // 使用alert view显示二维码保存状态
  uialertcontroller *alert = [uialertcontroller alertcontrollerwithtitle:title message:message preferredstyle:uialertcontrollerstylealert];
  uialertaction *action = [uialertaction actionwithtitle:@"ok" style:uialertactionstyledefault handler:nil];
  [alert addaction:action];
  
  [self presentviewcontroller:alert animated:yes completion:nil];
}

运行程序,测试一下效果:

iOS使用CIFilter生成二维码

至此,我们的二维码生成器已经全部完成,如果需要完整代码,可以下载 qrcodegeneratordemo 查看。

3 参考资料

cifilter - core image | apple developer documentation

building a qr code generator with core image filters

ciqrcodegenerator

how to save/load image/videos from camera roll – xcode ios

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持服务器之家。

原文链接:https://www.jianshu.com/p/7644949c29d4