iOS几个功能:1.摇一摇;2.震动;3.简单的摇动动画;4.生成二维码图片;5.发送短信;6.播放网络音频等
有一个开锁的功能,具体的需求就类似于微信的“摇一摇”功能:摇动手机,手机震动,手机上的锁的图片摇动一下,然后发送开锁指令。需求简单,但用到了许多方面的知识。
1.摇一摇
相对这是最简单的功能了。
在viewController的viewDidLoad中加这两句代码,或者在你想开始监听“摇一摇”这个功能的时候,添加这两句代码:
1 // 允许摇一摇功能 2 UIApplication.shared.applicationSupportsShakeToEdit = true 3 // 并让自己成为第一响应者 4 self.becomeFirstResponder()
其中的 self 自然指的是需要监听“摇一摇”的视图控制器了。
然后,重写下面三个方法,就可以了:
1 // MARK: 摇一摇相关方法 2 override func motionBegan(_ motion: UIEventSubtype, with event: UIEvent?) { 3 4 super.motionBegan(motion, with: event) 5 6 print("开始摇动") 7 } 8 9 override func motionCancelled(_ motion: UIEventSubtype, with event: UIEvent?) { 10 11 super.motionCancelled(motion, with: event) 12 13 print("取消摇动") 14 } 15 16 override func motionEnded(_ motion: UIEventSubtype, with event: UIEvent?) { 17 18 super.motionEnded(motion, with: event) 19 20 // 判断是否摇动结束 21 if event?.subtype == UIEventSubtype.motionShake { 22 23 print("摇动结束") 24 } 25 }
实测:
1.“开始摇动”方法最为灵敏,在“摇一摇”动作开始的一瞬间就会调用;
2.“摇动结束”方法不一定灵敏,时灵时不灵,但是如果“开始摇动”方法已调用,那必须调用“摇动结束”方法后才可以调用下一次“开始摇动”方法。也就是说,方法的调用顺序是下面这样的(每个方法之调用一次):
“开始摇动” -> “摇动结束” -> (第二次摇动)“开始摇动” -> ···
3.“取消摇动”是在这样的情况下会被调用:当系统调用了“开始摇动”过后,并没有监测到摇动已经结束,也就是没有调用“摇动结束”的方法;但不能无限在这儿卡下去,系统就设置了几秒钟为超时时间。若过了这几秒仍然没有监听到调用“摇动结束”的时机,那么就会调用“取消摇动”方法并开启“开始摇动”的响应链。
关闭自然就是关了监测就可以了:
1 UIApplication.shared.applicationSupportsShakeToEdit = false
2.手机震动
这个功能也比较简单,直接调用系统的震动函数就可以了。两步。
2.1 导入FrameWork
如下位置,导入系统框架AudioToolbox.framework:
这个框架的好多函数,负责手机震动与声音等等。
2.2 震动函数
就一句话,参数也不用变,自动提供一个大概半秒到一秒的震动(复杂的震动也可以通过这个框架自定义,这里只提供一次性的短的震动的函数):
1 // 震动 2 AudioServicesPlaySystemSound(kSystemSoundID_Vibrate)
3.摇动动画
这个算是这里面最难实现的功能了。涉及到CoreAnimation框架下的CABasicAnimation这个类型。
1 // 开始动画 2 let momAnimation = CABasicAnimation.init(keyPath: "transform.rotation.z") 3 // 从哪里开始 4 momAnimation.fromValue = NSNumber.init(value: -0.3) 5 momAnimation.toValue = NSNumber.init(value: 0.3) 6 // 动画持续时间(单次) 7 momAnimation.duration = 0.2 8 // 重复次数 9 momAnimation.repeatCount = 3 10 11 // 这是重复时间,也就是无视重复次数,按照动画单次持续时间无限循环,直到到了repeatDuration规定的时间。与上面都是动画重复,一个规定次数一个规定时间,并且repeatDuration优先级高 12 // momAnimation.repeatDuration = 0.6 13 14 // 动画结束时是否执行逆动画 15 momAnimation.autoreverses = true 16 momAnimation.delegate = self 17 self.lockImageView.layer.add(momAnimation, forKey: "animateLayer") 18 19 DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.6) { 20 21 self.lockImageView.layer.removeAllAnimations() 22 }
唯一可能需要解释的就是keyPath中的x、y、z三轴究竟是什么。它是手机的三个轴线。x表示水平,也就是左右方向;y表示竖直,也就是上下方向;z表示垂直手机屏幕的方向。具体最好自己试一试,非常直观。
补充一些其他方面的功能。
4.生成二维码
利用系统的CoreImage框架下的CIFilter类型,将字符串转换成二维码图片。需要一个参数,也就是想战士的信息 字符串:String
1 // 1.创建过滤器 2 let filter = CIFilter.init(name: "CIQRCodeGenerator") 3 // 2.恢复默认 4 filter?.setDefaults() 5 // 3.给过滤器添加数据 6 filter?.setValue(String.data(using: String.Encoding.utf8), forKeyPath: "inputMessage") 7 // 4.获取输出的二维码 8 UIImage.init(ciImage: filter?.outputImage)
其中,filter?.outputImage返回的是CIImage类型的图片,需要转化成UIImage类型才可赋值到UIImageView上。
但是这样其实存在问题,那就是生成的二维码过于模糊。于是需要这样的方式进行处理:
1 /** 2 * 根据CIImage生成指定大小的UIImage 3 * 4 * @param image CIImage 5 * @param size 图片宽度 6 */ 7 + (UIImage *)createNonInterpolatedUIImageFormCIImage:(CIImage *)image withSize:(CGFloat)size { 8 9 CGRect extent = CGRectIntegral(image.extent); 10 CGFloat scale = MIN(size/CGRectGetWidth(extent), size/CGRectGetHeight(extent)); 11 // 1.创建bitmap; 12 size_t width = CGRectGetWidth(extent) * scale; 13 size_t height = CGRectGetHeight(extent) * scale; 14 CGColorSpaceRef cs = CGColorSpaceCreateDeviceGray(); 15 CGContextRef bitmapRef = CGBitmapContextCreate(nil, width, height, 8, 0, cs, (CGBitmapInfo)kCGImageAlphaNone); 16 CIContext *context = [CIContext contextWithOptions:nil]; 17 CGImageRef bitmapImage = [context createCGImage:image fromRect:extent]; 18 CGContextSetInterpolationQuality(bitmapRef, kCGInterpolationNone); 19 CGContextScaleCTM(bitmapRef, scale, scale); 20 CGContextDrawImage(bitmapRef, extent, bitmapImage); 21 // 2.保存bitmap到图片 22 CGImageRef scaledImage = CGBitmapContextCreateImage(bitmapRef); 23 CGContextRelease(bitmapRef); 24 CGImageRelease(bitmapImage); 25 return [UIImage imageWithCGImage:scaledImage]; 26 }
参数与返回值类型都介绍的很详细。这样就能获得一张高清晰度的二维码UIImage了。
5.发送短信
有两种实现方式。
5.1 跳转到系统的发送短信的界面发送短信
1 UIApplication.shared.openURL("sms://10000")
类似于跳转拨打电话、打开网页等等,区别在于“冒号双斜线”之前的代号。
不过区别很明显:能提供给外界的信息很少,只有一个电话号码。不能提供短信群发、短信内容等功能;不能再发送结束后返回App。
5.2 使用系统内部框架
据说有审核被拒的危险。不过目前我没有遇到,我想苹果官方提供的框架应该也不至于太那啥。
5.2.1 添加MessageUI.framework
不多描述。添加苹果官方提供的发短信的框架。
5.2.2 导入头文件
导入头文件:
1 // 发送短信 2 #import <MessageUI/MessageUI.h>
注意不要导错。
5.2.3 发送短信
遵守协议MFMessageComposeViewControllerDelegate,之后在需要发送短信的地方实现代码:
1 func sharedButtonDidPress(sender: UIButton) { 2 3 // 判断能否发短信(模拟器不可以发短信) 4 if MFMessageComposeViewController.canSendText() { 5 6 // 创建发送短信控制器 7 let controller = MFMessageComposeViewController.init() 8 controller.messageComposeDelegate = self 9 controller.recipients = [] // 短信接收者 10 controller.body = self.dataArr[0][2] // 短信内容 11 self.present(controller, animated: true, completion: nil) 12 } 13 else { 14 15 print("模拟器不可以发短信") 16 } 17 }
实现协议内容:
1 // MRAK: <MFMessageComposeViewControllerDelegate> 2 /// 协议方法,在信息界面处理完信息结果是调用(比如点击发送、取消发送、发送失败) 3 /// 4 /// - Parameters: 5 /// - controller: 信息控制器 6 /// - result: 返回的信息发送成功与否 7 func messageComposeViewController(_ controller: MFMessageComposeViewController, didFinishWith result: MessageComposeResult) { 8 9 // 发送完消息就回原程序 10 self.dismiss(animated: true, completion: nil) 11 12 switch result { 13 case MessageComposeResult.sent: 14 HTTPRequest().showHUDMessage("发送成功") 15 break 16 case MessageComposeResult.failed: 17 HTTPRequest().showHUDMessage("发送失败") 18 break 19 case MessageComposeResult.cancelled: 20 HTTPRequest().showHUDMessage("取消发送") 21 break 22 } 23 }
6.播放网络音频
这个没啥好解释的,就是给你个URL你放歌呗。URL一般是这个格式的:
后面的 .mp3 标注了格式。就这样。
接下来先来介绍一下 AVAudioPlayer 这个播放器。
6.1 AVAudioPlayer
6.1.1 简单的使用
AVAudioPlayer 是 AVFoudation 框架中的一个类,主要功能就是播放音频。
首先,我们需要先导入 AVFoudation 框架:
1 #import <AVFoundation/AVFoundation.h>
然后初始化 AVAudioPlayer 实例。这里有两种方法初始化:
1 /** 0.NSError 2 这里可以传一个 NSError 类型的指针地址进去。如果解析音频过程中出现了错误,那么这个 NSError * 类型的实例指针就会指向一个 NSError 类型的对象,从而实现多返回值的效果。 3 这里利用的是指针技术。 4 */ 5 NSError *error = nil; 6 7 /** 1.通过 NSData 初始化 AVAudioPlayer */ 8 AVAudioPlayer *player1 = [[AVAudioPlayer alloc] initWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:@"http://wvoice.spriteapp.cn/voice/2017/0119/5880af3c1ea81.mp3"]] error:&error]; 9 10 /** 11 2.通过 NSURL 初始化 AVAudioPlayer 12 这个URL只能是本地的文件,例如 13 */ 14 AVAudioPlayer *player2 = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:@"123.mp3"] error:&error];
两种初始化方式是这样的。第二种通过本地文件生成的 NSURL 初始化 AVAudioPlayer 没什么好说的,主要讲一下第一种方式。我在代码中直接使用 NSData的 dataWithContentsOfURL: 方法请求网络数据并且赋值给 player1。这样做是有问题的。问题很明显,网络请求是耗时操作,应该放在子线程中进行。我们先不考虑线程问题,先看下能否实现我们的需求。
我通过第一种方式初始化了player,那么怎么让它播放暂停呢?
1 /** 1.播放 */ 2 [player1 play]; 3 4 /** 2.暂停 */ 5 [player1 pause]; 6 7 /** 3.停止 */ 8 [player1 stop];
就是字面意思。可以看下官方介绍,不过其实绕来绕去也还就是 播放 暂停 停止。
这样,最基本的功能我们就实现了。
但是,这样虽然可以播放了(先不考虑先成问题),但是控制台却在不停的打印这条消息:
1 [aqme] 255: AQDefaultDevice (173): skipping input stream 0 0 0x0
这其实是Xcode8之后出现的问题。原因不明,解决方法:
解决。
6.1.2 AVAudioPlayer的一些属性和代理方法
值得留意的属性有这些:
1 // 1.音量 2 player1.volume =0.8;//0.0-1.0之间 3 4 // 2.循环次数 5 player1.numberOfLoops =3;//默认只播放一次 6 7 // 3.播放位置 8 player1.currentTime =15.0;//可以指定从任意位置开始播放 9 10 // 4.声道数 11 NSUInteger channels = player1.numberOfChannels; // 只读属性 12 13 // 5.持续时间 14 NSTimeInterval duration = player1.duration; // 获取持续时间 15 16 // 6.仪表计数 17 player1.meteringEnabled =YES; // 开启仪表计数功能 18 [player1 updateMeters]; // 更新仪表计数
需要遵守的协议是这个:<AVAudioPlayerDelegate>
协议方法:
1 #pragma mark - <AVAudioPlayerDelegate> 2 /** 1.播放结束 */ 3 - (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag { 4 5 NSLog(@"播放音频结束了"); 6 } 7 8 /** 2.解析音频出错 */ 9 - (void)audioPlayerDecodeErrorDidOccur:(AVAudioPlayer *)player error:(NSError *)error { 10 11 NSLog(@"解析音频出错"); 12 } 13 14 /** 3.播放(开始)被中断 15 原因:例如接电话等导致播放被中断 16 */ 17 - (void)audioPlayerBeginInterruption:(AVAudioPlayer *)player { 18 19 NSLog(@"播放音频中断"); 20 } 21 22 /** 4.播放中断结束 23 */ 24 - (void)audioPlayerEndInterruption:(AVAudioPlayer *)player { 25 26 [self.player play]; 27 }
其中3和4被废弃了:
6.2 AVPlayer