iOS UIAlertView弹出视图动画效果实现

本文出自: [文章链接地址:http://www.tuicool.com/articles/E3iiQb]

在App设计中为了加强用户体验,我们会常常加入一些友好的动画效果。比如类似UIAlertView弹出的动画效果,由于系统中并没有直接提供类似的动画API, 如果我们想要做出一样的效果,那就得深入的研究一下系统中的UIAlertView了。

仔细观察UIAlertView的动画你就会发现:这个动画是由几部分组成,它带一个视图大小抖动的效果。先是由小变大,再由大变小,最后变成本来的大小。 但是这个大小的具体参数值和动画的速度恐怕是肉眼所不能看出来的。 本篇文章会使用一些objc runtime和CAAnimation的一些知识,通过本文你可以了解 到如何研究一些objc中内部调用机制和动画基础。

要想知道这些动画的组成,我们就要从比较低层次的API:CALayer的一些调用开始。iOS动画最终都是加到Layer中的,加入Layer就要调用Layer对象这个方法:

- (void)addAnimation:(CAAnimation *)anim forKey:(NSString *)key;

所以只要我们知道了anim参数,并把anim动画对象的属性揪出来,就可以知道到底是什么动画了,但是这个方法是系统Framework中的,通常我们是无法能知道anim到底是什么。 这时我们就需要用一些objc的一些底层API:Objc Runtime来解决了。

Objc Runtime

Objc Runtime是由一组处理Objctive-C动态语言运行时的API函数组成,这些函数都是一些比较底层的C函数。它有很多实用功能比如查看对象的成员,类/对象方法签名等等。这次我们要用的就是其中把对象方法调用替换的API。

void method_exchangeImplementations(Method m1, Method m2) 

这个函数的功能就是把类/对象的方法m1和m2进行调换。如果执行了这个函数,那么在App运行过程中所有调用方法m1的指令,最终都会执行成了方法m2。

方法调换

有了Objc Rumtime的API,就可以很方便的将调用系统库中方法的代码,执行成我们自己的代码了。所以我们想要知道Layer中加入了什么方法,只要把addAnimation:forKey:这个方法调换成我们自己的方法就行了。下面的这段代码就实现了这个功能。


@implementation CALayer(Hacked)   

+ (void)load{   
    method_exchangeImplementations(class_getInstanceMethod([CALayer class], @selector(addAnimation:forKey:)), 
class_getInstanceMethod([CALayer class], @selector(hackedAddAnimation:forKey:)));   
}   

- (void)hackedAddAnimation:(CABasicAnimation *)anim forKey:(NSString *)key{
    [self hackedAddAnimation:anim forKey:key];
    if ([anim isKindOfClass:[CABasicAnimation class]]) {
        if ([anim.keyPath isEqualToString:@"transform"]) {
            if (anim.fromValue) {
                CATransform3D fromValue = [anim.fromValue CATransform3DValue];
                NSLog(@"From:%@",NSStringFromCGAffineTransform(CATransform3DGetAffineTransform(fromValue)));
            }
            if (anim.toValue) {
                CATransform3D toValue = [anim.toValue CATransform3DValue];
                NSLog(@"To:%@",NSStringFromCGAffineTransform(CATransform3DGetAffineTransform(toValue)));
            }
            if (anim.byValue) {
                CATransform3D byValue = [anim.byValue CATransform3DValue];
                NSLog(@"By:%@",NSStringFromCGAffineTransform(CATransform3DGetAffineTransform(byValue)));
            }
            NSLog(@"Duration:%.2f",anim.duration);
            NSLog(@"TimingFunction:%@",anim.timingFunction);
        }
    }
}

@end

下面来说明一下上面的代码,这段代码是CALayer做了一个Catalog处理。其中initialize是一个类的方法,是进程开始时初始化类时调用,一般只有类有加载这个方法就会第一个调用了。hackedAddAnimation:forKey:是要被调换的代码。在类的初始化方法initialize中(代码中的第5行)实现了CALayer的addAnimation:forKey:和hackedAddAnimation:forKey:方法的调换。在hackedAddAnimation:forKey:中首先直接调用了[self hackedAddAnimation:forKey:],也许你会问:这不死循环递归了么?其实不是,应为method_exchangeImplementations实现的是调换而不是替换,所以代码中调用addAnimation:forKey:运行时就成了调用hackedAddAnimation:forKey:。而代码中调用hackedAddAnimation:forKey:运行时成了调用addAnimation:forKey:。所以这里虽然写的是hackedAddAnimation:forKey:,实际上会调用系统Framework中的addAnimation:forKey:。这样做的目的是保证虽然我们把系统的方法改变了,我们还是调用系统的一次,以保持系统功能运行是正常的。在hackedAddAnimation:forKey:剩下的代码就只是把anim动画对象的各个属性的值打印出来了。

好了,把上面的这段代码粘贴到你的代码文件中。然后简单的写个UIAlertView弹出动画代码。


UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Title" message:@"Message" delegate:nil cancelButtonTitle:@"Close" otherButtonTitles:nil];
[alert show];

编译并运行上面这段代码,你就会在控制台中看到下面这些日志:


2013-04-10 19:13:11.795 Test[10952:c07] From:[0.01, 0, 0, 0.01, 0, 0]
2013-04-10 19:13:11.796 Test[10952:c07] Duration:0.20
2013-04-10 19:13:11.796 Test[10952:c07] TimingFunction:easeInEaseOut
2013-04-10 19:13:11.999 Test[10952:c07] From:[1.1, 0, 0, 1.1, 0, 0]
2013-04-10 19:13:12.000 Test[10952:c07] Duration:0.10
2013-04-10 19:13:12.000 Test[10952:c07] TimingFunction:easeInEaseOut
2013-04-10 19:13:12.101 Test[10952:c07] From:[0.9, 0, 0, 0.9, 0, 0]
2013-04-10 19:13:12.101 Test[10952:c07] Duration:0.10
2013-04-10 19:13:12.101 Test[10952:c07] TimingFunction:easeInEaseOut

查看CGAffineTransformMakeScale函数的头文件你会看到:


/* Return a transform which scales by `(sx, sy)':
     t' = [ sx 0 0 sy 0 0 ] */

CG_EXTERN CGAffineTransform CGAffineTransformMakeScale(CGFloat sx, CGFloat sy)
  CG_AVAILABLE_STARTING(__MAC_10_0, __IPHONE_2_0);
  

所以根据日志我们会发现这其实是3个关键帧动画,首先scale(缩放比例)从0.01放大到1.1,历时0.2秒;然后从1.1到0.9,历时0.1秒;那么最后就是从0.9到1.0(正常缩放比例),历时0.1秒。哈哈,那我们就简单的写个关键帧动画对象就可以表示UIAlertView的弹出动画效果了。


CAKeyframeAnimation *popAnimation = [CAKeyframeAnimation animationWithKeyPath:@"transform"];
popAnimation.duration = 0.4;
popAnimation.values = @[[NSValue valueWithCATransform3D:CATransform3DMakeScale(0.01f, 0.01f, 1.0f)],
                        [NSValue valueWithCATransform3D:CATransform3DMakeScale(1.1f, 1.1f, 1.0f)],
                        [NSValue valueWithCATransform3D:CATransform3DMakeScale(0.9f, 0.9f, 1.0f)],
                        [NSValue valueWithCATransform3D:CATransform3DIdentity]];
popAnimation.keyTimes = @[@0.0f, @0.5f, @0.75f, @1.0f];
popAnimation.timingFunctions = @[[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut],
                                 [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut],
                                 [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]];
[anAlertAnimationView.layer addAnimation:popAnimation forKey:nil];

你可以把popAnimation加入到你想进行动画的任何View中的layer中这样就实现了UIAlertView一样的弹出动画效果。

结论

UIAlertView动画其实是由三部分动画组成:缩放比例变化0.01->1.1->0.9->1.0。每次变化的时间函数(控制加速度)都是EaseInEaseOut。 在研究系统中调用函数的参数是我们可以用method_exchangeImplementations来hack到系统调用中去,但不要忘记调用系统本身的方法。否则容易导致App异常。当然,如果你是研究测试不怕crash,那随便。

本文出自: [文章链接地址:http://www.tuicool.com/articles/E3iiQb]

iOS 蓝牙4.0 CoreBluetooth 框架的使用


文章一: 本文出自:coderyi 文章链接地址:http://www.coderyi.com/archives/137
***

内容简介:

iOS开发蓝牙4.0的框架是CoreBluetooth,本文主要介绍CoreBluetooth的使用,关于本文中的代码片段大多来自github上的一个demo,地址是myz1104/Bluetooth。
在CoreBluetooth中有两个主要的部分,Central和Peripheral,有一点类似ClientServer。CBPeripheralManager作为周边设备是服务器。CBCentralManager作为中心设备是客户端。所有可用的iOS设备可以作为周边(Peripheral)也可以作为中央(Central),但不可以同时既是周边也是中央。

一般手机是客户端, 设备(比如手环)是服务器,因为是手机去连接手环这个服务器。
周边(Peripheral)是生成或者保存了数据的设备,中央(Central)是使用这些数据的设备。你可以认为周边是一个广播数据的设备,他广播到外部世界说他这儿有数据,并且也说明了能提供的服务。
另一边,中央开始扫描附近有没有服务,如果中央发现了想要的服务,然后中央就会请求连接周边,一旦连接建立成功,两个设备之间就开始交换传输数据了。
除了中央和周边,我们还要考虑他俩交换的数据结构。这些数据在服务中被结构化,每个服务由不同的特征(Characteristics)组成,特征是包含一个单一逻辑值的属性类型。

详细文章链接地址:http://www.coderyi.com/archives/137


文章二: 本文出自:liuyanwei 文章链接地址:http://liuyanwei.jumppo.com/2015/07/17/ios-BLE-1.html
***

内容简介:

1.iOS蓝牙开发(一)蓝牙相关基础知识
蓝牙常见名称和缩写

1.MFI ======= make for ipad ,iphone, itouch 专们为苹果设备制作的设备
2.BLE ==== buletouch low energy,蓝牙4.0设备因为低耗电,所以也叫做BLE
3.peripheral,central == 外设和中心,发起连接的时central,被连接的设备为perilheral
4.service and characteristic === 服务和特征 >5.每个设备会提供服务和特征,类似于服务端的api,但是机构不同。每个外设会有很多服务,每个服务中包含很多字段,这些字段的权限一般分为 读read,写write,通知notiy几种,就是我们连接设备后具体需要操作的内容。
6.Description 每个characteristic可以对应一个或多个Description用户描述characteristic的信息或属性
7.MFI === 开发使用ExternalAccessory 框架
8.4.0 BLE === 开发使用CoreBluetooth 框架

2.ios蓝牙开发(二)ios连接外设的代码实现

  1. 建立中心角色
  2. 扫描外设(discover)
  3. 连接外设(connect)
  4. 扫描外设中的服务和特征(discover)
    • 4.1 获取外设的services

    • 4.2 获取外设的Characteristics,获取Characteristics的值,获取Characteristics的Descriptor和Descriptor的值

  5. 与外设做数据交互(explore and interact)
  6. 订阅Characteristic的通知
  7. 断开连接(disconnect)

3.ios蓝牙开发(三)app作为外设被连接的实现
peripheral模式的流程

  1. 打开peripheralManager,设置peripheralManager的委托
  2. 创建characteristics,characteristics的description 创建service,把characteristics添加到service中,再把service添加到peripheralManager中
  3. 开启广播advertising
  4. 对central的操作进行响应

    4.1 读characteristics请求 4.2 写characteristics请求 4.4 订阅和取消订阅characteristics

4.ios蓝牙开发(四)BabyBluetooth蓝牙库介绍

1.基于原生CoreBluetooth框架封装的轻量级的开源库,可以帮你更简单地使用CoreBluetooth API。
2.CoreBluetooth所有方法都是通过委托完成,代码冗余且顺序凌乱。BabyBluetooth使用block方法,可以重新按照功能和顺序组织代码,并提供许多方法减少蓝牙开发过程中的代码量。
3.链式方法体,代码更简洁、优雅。
4.通过channel切换区分委托调用,并方便切换

详细文章链接地址:http://liuyanwei.jumppo.com/2015/07/17/ios-BLE-1.html
Demo地址:https://github.com/coolnameismy/BabyBluetooth

iOS App 调试证书和发布证书的创建和使用流程

本文出自:伯乐在线 - 小良 文章链接地址:http://ios.jobbole.com/84643/

内容简介: 相关证书的创建,使用流程及注意事项。

1.Certificates: 证书,常用的证书类型有4种:真机调试证书、推送调试证书,发布证书、推送生产证书。

2.Identifiers: App ID,跟项目工程的 Bundle Identifier

3.需要支持推送、Game Center 等功能的 App ID 不能包含通配符* (下图就是在新建App ID时,选择App ID的后缀)

4.Devices: iOS设备在真机调试、AdHoc发布时都需要包含设备的UDID才可以安装。

5.Provisioning Profiles: 配置文件(描述文件),不同类型的开发者账号都包含 Development、AdHoc 这两种

6.Profile,不同的是个人、公司开发者账号有发布到 AppStore 的 Profile,而企业开发者账号则是 InHouse 企业内发布的 Profile。

详细文章链接地址:http://ios.jobbole.com/84643/

iOS 设置后台定时闹铃

一.什么是AVAudioSession ?
我的理解就是一个应用最基本的音频配置,来设置基本的应用与系统之间的音频处理方式。

1). Audio Session 各个参数类别的作用及定义:
https://developer.apple.com/library/prerelease/tvos/documentation/Audio/Conceptual/AudioSessionProgrammingGuide/ConfiguringanAudioSession/ConfiguringanAudioSession.html

2).配置音频后台播放及官方资料:
https://developer.apple.com/library/ios/qa/qa1668/_index.html

二.AVAudioSession在开发中的应用

1)应用里的音频播放时是否要和其他应用的音频实现混音?或者让其他音频静音?
2)当iOS的闹钟响时应用内的音频是否要暂停?
3)当用户插拔耳机时应用应该如何反应?拔出耳机时是否要停止音乐?
4)关闭屏幕后音频是否暂停?

三.后台定时闹钟的实现

1.实现方式:

1).设置后台无限运行
方式:当程序退回后台时,系统会允许应用5分钟的存活时间。在这段时间里,启动一个定时器,每间隔一两分钟就执行播放一段非常短的无声音频,来继续获取系统重新的5分钟的存活时间,达到在后台无限运行的效果。

后台无线运行Demo例子: https://github.com/mddios/runInBackground

2).设置定时闹钟
方式:使用本地通知 UILocalNotification + 播放器AVAudioPlayer

3).闹钟响起时
方式:弹出本地通知及播放指定歌曲

2.遇到的问题:

问题1:
在设置定时闹钟后,退回后台;打开第三方播放器播(例如酷狗音乐)播放歌曲,当定时闹钟时间到后,弹出本地通知,但是设置的播放闹铃歌曲并没有播放出来。经调试发现,此时在后台中执行setActive失败:

    BOOL activated = [[AVAudioSession sharedInstance] setActive:YES error:&error];
    if(activated)
    {
      NSLog(@"OK");
    }else {
      NSLog(@"error: %@",error);
    }

解决办法:
在定时闹钟时间到后,设置AVAudioSession参数为允许其他播放器混音选项AVAudioSessionCategoryOptionMixWithOthers;这样就能解决后台播放失败的问题。

  // 设置接受远程控制事件
  [[UIApplication sharedApplication] beginReceivingRemoteControlEvents]; 
  
  AVAudioSession *session = [AVAudioSession sharedInstance];

  //设置允许混音
  [session setCategory:AVAudioSessionCategoryPlayback withOptions:AVAudioSessionCategoryOptionMixWithOthers error:nil];
                
  [session setActive:YES error:nil];
                
  //播放歌曲
  _play = [[AVAudioPlayer alloc]initWithContentsOfURL:url error:nil];
  [_play play];

问题2:
设置了混音AVAudioSessionCategoryOptionMixWithOthers后,虽然能决解了后台定时闹钟响铃失败的问题,但是若此时打开自己APP的播放器播放歌曲,会发现自己的APP无法暂停其他APP播放器(如酷狗播放器)的音乐;别的APP播放器也暂停不了自己APP的音乐。
调试发现,之前设置的接收中断通知服务失效:


//监听电话进来 或 打开其他APP音乐播放歌曲时接收的中断信息
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onAudioSessionEvent:) name:AVAudioSessionInterruptionNotification object:nil];
    
// 中断处理
-(void)onAudioSessionEvent:(NSNotification*)noty{
    NSLog(@"%@",noty.userInfo);

    NSDictionary *dict = noty.userInfo;
    if ([dict[AVAudioSessionInterruptionTypeKey]integerValue]==AVAudioSessionInterruptionTypeBegan) {
        //正当音乐被其他应用或者电话中断时
    }else if([dict[AVAudioSessionInterruptionTypeKey]integerValue]==AVAudioSessionInterruptionTypeEnded){
       //当中断结束时
    }
}

解决办法:
在应用进入前台时,设置AVAudioSession的参数为AVAudioSessionCategorySoloAmbient选项,即关闭其他APP应用播放器音乐;但是此选项不支持后台音乐播放,故需要再次设置AVAudioSession的参数为AVAudioSessionCategoryPlayback,即允许后台播放歌曲。

// 应用进入前台
- (void)applicationDidBecomeActive:(UIApplication *)application
{
    //允许应用程序接收远程控制
    [[UIApplication sharedApplication] beginReceivingRemoteControlEvents];

    AVAudioSession *session = [AVAudioSession sharedInstance];
    
    // 设置为暂停其他播放器音乐
    [session setCategory:AVAudioSessionCategorySoloAmbient error:nil];

    // 设置播放器为允许后台播放
    [session setCategory:AVAudioSessionCategoryPlayback withOptions:AVAudioSessionCategoryOptionDefaultToSpeaker error:nil];
    [session setActive:YES error:nil];
}


iOS 设置AVAudioSessionInterruptionNotification通知无效

设置AVAudioSessionInterruptionNotification通知无效
如下代码


[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onAudioSessionEvent:) name:AVAudioSessionInterruptionNotification object:nil];

-(void)onAudioSessionEvent:(NSNotification*)noty{
    NSLog(@"%@",noty.userInfo);
    //    AVAudioSessionInterruptionTypeBegan
    //     AVAudioSessionInterruptionTypeEnded
    //      AVAudioSessionInterruptionTypeKey = 1;
    //     AVAudioSessionInterruptionTypeKey = 0;
    NSDictionary *dict = noty.userInfo;
    if ([dict[AVAudioSessionInterruptionTypeKey]integerValue]==AVAudioSessionInterruptionTypeBegan) {
        // pause
        
    }else if([dict[AVAudioSessionInterruptionTypeKey]integerValue]==AVAudioSessionInterruptionTypeEnded){
        // play
    }
}

解决办法 将AVAudioSession中的配置选项设置为AVAudioSessionCategoryOptionDefaultToSpeaker即可。

例如

- (void)setUpAudioSession {
    // 新建AudioSession会话
    AVAudioSession *audioSession = [AVAudioSession sharedInstance];
    
    // 设置后台播放
    NSError *error = nil;
    [audioSession setCategory:AVAudioSessionCategoryPlayback withOptions:AVAudioSessionCategoryOptionDefaultToSpeaker error:&error];
    if (error) {
        NSLog(@"Error setCategory AVAudioSession: %@", error);
    }
    
    NSError *activeSetError = nil;
    // 启动AudioSession,如果一个前台app正在播放音频则可能启动失败
    [audioSession setActive:YES error:&activeSetError];
    if (activeSetError) {
        NSLog(@"Error activating AVAudioSession: %@", activeSetError);
    }
}

iOS [UIApplication sharedApplication].keyWindow 添加视图无效

[UIApplication sharedApplication].keyWindow 添加视图无效
解决办法:

iOS 7使用:


[[[[UIApplication sharedApplication] delegate] window] addSubview:view];

iOS 8以上使用:


[[UIApplication sharedApplication].keyWindow addSubview:view];

原因: 在iOS 7中,调试输出可以看到[UIApplication sharedApplication].keyWindow的值为nil ,这是因为 appdelegate 中的keyWindow没有创建成功 ,故添加无效。在iOS 8中,苹果解决了此问题。