Yosemite DP3+iOS8 Beta3 Handoff打电话功能测试

8号一大早就把手机和Mac都升级到了最新的测试版,DP3+Beta3,出现了Handoff开关,但是电话和短信进来的时候Mac上死活就是没反应,在网页上选中电话号码或者在Mac的通讯录里查看电话,也不会出现打电话的图标。查看了很多次,iCloud都已经使用同一个账号登录了,而且在DP2的时候Mac上就提示过你的iPhone已经可以使用Continues功能了。

今天才偶然在威锋论坛上看到一个帖子,原来是需要用同一个账号登录Facetime,恍然大悟。打开Facetime一看,以前用的是另外一个AppleId登录的。重新登录之后打开Facetime的偏好设置,允许联系的方式里面确保手机号已经被选中,下面的iPhone(蜂窝通话)功能选中,就OK了。

再回到通讯录里,每个号码的旁边就会直接出现了拨号的电话图标,在网页上双击选中一个手机或固话,就会出现下拉箭头,里面就有呼叫的菜单了。尝试着通过Mac直接发起呼叫一个号码,不知道是不是因为公司WIFI的问题,反应有点迟钝,接近半分钟以后,手机上出现了通话的信息(就是通话中按Home键回到桌面的那个状态),然后对方接起来之后就可以在Mac上直接通话了,通过过程也有延迟,但是没有那么明显,基本可以接受。用带耳麦的耳机直接插在Mac里打电话也可以,不过对方听到的噪音会比较厉害,缺少了iPhone上的降噪功能。

iOS8地理位置编程的奇葩改进

一个很简单的iBeacon的测试程序,以前都好用的,手机升级到iOS8 b2,xcode6 b2编译运行,就不能正常工作了,总是提示位置授权状态是kCLAuthorizationStatusNotDetermined,进到设置里隐私-地理位置下面找到这个应用,设为总是允许,再重新编译运行,它又会变成这个未知状态。看了文档加上搜索了很多次,都是说当弹出授权对话框的时候如果用户没有选同意,就会出现这个状态。可是,我这应用根本就不会弹出让用户授权的对话框啊。

以为是xcode的Bug,今天等到了beta3,结果还是同样的情况。又重新开始搜索,这次终于找到了眉目。

首先,原来的代码只要走到locationManager startMonitoringForRegion的时候就会自动弹出询问授权的对话框,而现在你需要调用locationManager requestAlwaysAuthorization手动申请授权,并在didChangeAuthorizationStatus这个回调里面继续后面的开始监测的代码。

其次,最坑爹的是,当你调用这个request的时候,它弹出的对话框里面的那句询问语,需要你自己指定。所以需要在你的info.plist里面添加一个key: NSLocationAlwaysUsageDescription,value就是对话框上那句询问语。这时候程序才能正确的弹出授权对话框,用户选了同意以后,程序后面的代码才能够正确的运行。

Android与iPhone应用开发的差异性与相似性比较

对Android开发仍然没有经验,只是通过几本书熟悉了其中的开发理念与框架,根据自己的理解浅谈一下,错漏之处敬请指正。

1、程序的入口。

iPhone应用程序的入口是AppDelegate,在其中初始化UIWindow对象,然后将Windows显示出来。如果是游戏,就直接在这个Window上画东西了,一般的应用,你需要建一个UIViewController作为Root界面,将它添加到Window中,Window就负责将它显示出来,剩下的完成功能,和调用显示其它ViewController都是这个Root界面的任务了。调用时你需要初始化另一个ViewController,然后弹出它,或者push它到导航队列中。在大部分应用中,这个Root VC并不是一个普通的UIViewController子类,而是UINavigationController或者UITabBarController,它们负责统管界面调度,你还需要指定另外一个UIViewController的子类作为它们的Root界面。

Android应用程序的入口并不是在代码中指定的,而是在AndroidManifest.xml文件中指定的。Android应用中每个界面都是相互独立的,没有直接的相互调用关系,各个Activity之间是完全解耦的,相互不可见。Application对象也不负责启动某一个Activity。因此,你在xml文件中指定哪一个作为初始加载对象,系统就为你加载哪一个,随时可以改成另外一个界面。

2、界面间的关系。

如上所述,iPhone中每一个ViewController都了解它将要调用的子ViewController,并且以强类型来初始化,并加载。也可以直接调用子ViewController中的变量和方法,为其赋值。整个应用程序最后编译成一个完整的文件,密不可分。无论何时调用执行该程序,都先从AppDelegate入手,找到初始View,把所需要的参数传递过去。如果要在启动时直接显示另一个View,必须经由Root,由它来处理所有传递进来的参数,再去调用所需要的ViewController才行。

而Android在这个问题上解决的非常彻底,你不需要知道自己的程序中其它Activity的名字,只需要知道它们的功能和接口即可调用。系统也可以不经由你的Root页面直接调用你的某一个Activity,同样其它的程序也可以直接把你的程序中的某一个页面的功能当成自己程序中的页面一样的来调用,只需要知道它的接口和功能。被调用时这个Activity的行为也像在那个程序中一样,弹出,完成自己的任务,消失,回退到调用它的那个界面上去。

3、界面的开发。

就像当年微软用VB的拖放式开发统治了入门级程序开发界一样,苹果用一个强大的InterfaceBuilder迅速的将自己的objective-c语言普及开来,其实这个语言的学习难度远大于Java。在iPhone的界面设计中,布局方式很简单,你把控件放在哪儿,它就在哪。为了适应界面旋转,某些控制会默认提供自适应窗口大小的功能,当然你也可以对大部分控件指定自适应大小的功能,设定让它距离某个边界的宽度保持不变,然后是否允许拉伸高度或宽度,就可以了。而比较复杂的界面你必须在旋转的时候在代码里精细的控制每一个控件的位置才可以。

Android的基于Java的界面布局要灵活的多,本身提供了几个容器,为其中的子控制提供流式布局,表格布局,或相关位置布局等。而且eclipse也提供了可视化的拖放操作的界面开发工具。但是由于Android的界面是xml方式保存的,对整个界面的复杂度会有一定的限制,某些复杂的布局实现起来会比较麻烦,比如本身是表格式布局,而其中的某个按钮或图片需要跨越多个元素这一类的。另外,基于xml的实现方式,Android很好的实现了界面的重用和样式的重用。在一个单独的xml中定义一个样式名,包含背景颜色字体大小之类的样式,然后在别的xml中定义控件时就可以直接引用这个style。这一点比iPhone上方便十倍。

4、界面与逻辑的交互。

iPhone使用了MVC的方式实现界面与逻辑的分离,而代码中的控件变量和界面中的控件本身的关联,可以通过在InterfaceBuilder中按住Ctrl拖放的方式建立关联,然后你就可以直接在代码中操作这个控件了。假如你在界面中删除了一个控件,而代码不做任何改变,对该控件的赋值或取值操作不会引发任何异常。

Android中逻辑代码与界面实现是完全分离的,没有任何关联关系。xml的界面定义中为每一个控件定义了id,如果要在代码中获取该控件对象并操作它的属性,你需要在代码中用findbyid,然后再加一个显式类型转换来关联到它。如果经常需要用到,需要像iPhone里面一样把它定义成全局变量,然后在整个界面加载时完成这个转换。如果你在界面中删除了原本的这个控件,那findbyid就找不到它了。

5、生命周期控制。

单个界面的生命周期在两个系统中基本上是一样的,只是两边的名字略有不同,都是创建,显示,隐藏,销毁,而对整个Application的生命周期的控制上,Android貌似缺少两个回调函数,无法实现让你在程序被结束之前安全的保存一些数据,而是要把这个保存动作放到某个Activity中才更可靠一些。

6、总结。

iPhone的系统设计是封闭的,这种封闭体现在各个方面。程序都运行在自己的沙盒中,没有权限操作任何非本程序目录下的东西,非本应用中的程序,只有几个系统允许你访问的东西提供了专用的访问接口,比如获取系统的照片,联系人之类的,除此之外的,你完全访问不了,也不能允许别人来访问你的数据。要做到跨程序的调用和数据传递,需要开放一个很复杂的sdk,给别人提供完整的头文件和具体实现的二进制代码,比如camara+,或者支付宝。

Android的系统设计的开发性也同样体现在各个方面。虽然每个程序也运行在自己的沙盒中(用Linux的用户名体系实现的沙盒),但是SD卡里的目录是全局公开的,所有程序都可以访问这里的文件。你只要知道别人程序的数据的位置和格式,就可以去读取或修改。而且你也可以把自己的应用中的某些功能实现的跟系统的一样,但是可以提供更好的体验,然后被其它应用调用。比如你可以让自己的拍照应用提供完整的美化功能,让自己的邮件提供更丰富的联系人管理功能,而只要暴露给系统的调用接口一致,那别人开发程序的时候完全不用关心自己需要去哪个拍照应用里取照片,只要要求系统提供一个这样的调用给自己的程序,系统会把所有可拍照的应用程序列表显示出来让用户挑一个,拍照并美化完成后就可以把结果照片返回给调用程序。在iPhone上开发任何程序,只要出现了图片的内容,你都要提供一套自己的查看图片的功能,这种功能完全是重复开发,目前最简单的方式也不过是去github上下载别人的图片管理器的源码,整个的编译到你的程序里去。而在Android中,你就可以直接让系统提供一个图片查看器来显示你的某一张图片,当用户按下返回,仍然回到你的程序中。这种开放性是iPhone程序员们梦寐以求的,却求之不得的。

两者的差异性还有很多,比如在Android中你可以让自己的程序实现系统级的事件监听和定时任务,当事件发生或定时到达时,系统会调用你指定的那个代码来执行一些操作,你几乎可以做任何事,包括拦截下这条消息不允许其它程序继续处理,因此可以实现电话、短信拦截。而iPhone完全封闭了类似的接口,你不能监听系统事件,只能通过你的服务器推送一条消息给系统,你不能做全局定时,只能将一个定时消息放入系统的本地通知中。当这两种通知在屏幕上显示时,用户完全可能忽略它,然后你的应用无法做出任何动作响应,因此连自动回复微博或邮件都做不到。

但是由于这种开放性带来的负面问题是,程序的权限过高,而该权限所导致的可能的后果,需要完全由用户自行负责。如果你开发了一个没有任何功能的拍照程序或图片查看程序,却定义了相同的接口。而用户在未经验证或思考的情况下安装了你的程序,并在系统第一次询问的时候选择了你的程序来处理这两种请求并选择了不再重复询问,那除非他卸载你的程序重新选择,否则所有其它调用该接口获取图片的应用都不正常了。更严重的例子是短信和网络的权限,一旦一个应用在安装的时候被允许了该权限(不允许就根本无法安装),那它以后做了什么用户可能根本就不知道,因为它自己调用短信接口发送短信,根本不会进入系统的短信列表。这种判断完全需要由用户自己来做出,对用户的认知能力和专业水平提出了相当高的要求,而实际上,Android用户绝大部分肯定是缺少这种能力的。

用Asp.net生成Passbook的方法

iOS6已经正式上线了,而且破天荒的覆盖到已经超过三年的机型3GS。对于3GS来说,升级到iOS6能够得到的唯一好处,就是苹果的新功能Passbook了。所以iOS6对3GS的支持,恐怕是苹果为了推广Passbook不得不做出的妥协,也因此,iPhone5没有添加NFC功能,所以从3GS到iPhone5的四代机型上面,Passbook的体验是一致的,无论对于用户、开发人员还是最终使用的商户来说,这种统一性带来的好处都是巨大的。剩下的工作,可能就是苹果推出一个官方的Android上的Passbook解决方案了。

而对于像31会议网这样的网站报名管理和现场签到管理平台来说,在本来就已经在使用二维码作为会场电子签到的情况下,Passbook无疑是一个极好的补充,完全不影响现有的架构,又极大的方便和用户,提升了体验。所以,没理由放着这么好的功能不用啊。

要把你的门票放进用户的Passbook程序里,有三种方式,1、提供一个链接,让用户用Safari打开。2、直接将Passbook文件以邮件附件的方式发到用户邮箱,让用户在手机上打开。3、开发一个App,在App里将这个门票直接写到系统的Passbook程序里去。对于在网站上匿名报名的用户来说,第三种方式无法实现,前两种都可以。最方便的当然是第一种。

解决方案很简单,使用这个开源库:https://github.com/tomasmcguinness/dotnet-passbook

这个库提供了生成passbook门票文件所需的一切方法,自动打包生成zip文件并完成签名,可以使用流的方式直接返回给浏览器,用户在手机上用safari打开一个链接,就可以完成passbook文件的下载,然后点击添加,就可以将这张门票添加进Passbook程序了。不过这个库的使用过程可不是一帆风顺,折腾了整整一天才搞定。一开始是safari直接提示文件无法下载,但是在windows下可以看到生成的zip什么问题也没有,里面的文件也是全的,跟demo也几乎是一样,你完全无法知道是哪里的错误,只能一个参数一个参数的试。后来safari终于能打开显示出门票信息了,但是点击添加的时候,不显示添加的动画,而是门票直接消失,然后进入Passbook程序还是空白,什么也没有。这个时候你就更不知道文件里到底有什么错误导致的了。唉,想想就郁闷。

首先,这个项目是VS2012写的,没有提供低版本的项目文件。无奈,下载VS2012,安装,打开项目,编译。还算顺利,结果把生成的dll引用到自己的项目以后,随便写了一个引用测试,编译就出错了。因为这个项目默认编译是for .net framework 4.5的,而我的网站项目还是.net 3.5的,不向下兼容。然后修改这个项目的属性,编译目标改成.net 3.5 dll,再编译,报一堆错误。其中引用的两个System级别的类是.net 4.5新增的,一个是Thread里面的Tasks类,一个是压缩类。删了这两个类的引用和using以后,第一个貌似代码中没有实际使用到,没有影响,第二个是生成zip文件的关键类,不能用了。没办法,添加了ICSharpCode.SharpZipLib.dll引用以后,手动修改生成zip文件的地方,改成FastZip类来生成zip文件。然后重新添加了.net 3.5下面的Newtonsoft.Json.dll,重新编译,成功。

第二个问题是证书,你需要在你的IIS所在的windows server里安装苹果的证书才能对生成的passbook门票签名。上面的gitbug项目说明文件里有一个简单的说明,不过有误导作用。正确的作法是:

1、去http://www.apple.com/certificateauthority/下载WWDR证书,也就是这个页面里的倒数第四个证书(一开始我以为是第一个证书)。这是个cer文件。在windows里进入证书管理(开始-运行-mmc-添加管理单元-证书-本地计算机),左边进入“中级证书颁发机构”下面的证书,右键-导入,选中这个cer文件,就可以导入了。

2、获取Passbook证书。如果你有苹果系统:登录你的苹果开发者中心,证书和Provision管理的地方,左边多了一个Pass Type IDs的菜单,进去新建一个ID,跟程序的App ID是一样的。然后进入这个ID的configure设置证书,申请过程也跟以前的申请certificate一样的,完成后下载cer证书双击导入你的钥匙串管理。然后在证书中找到它,右键,导出成p12文件。然后进入前面的windows的证书管理,在左边进入“个人”下面的证书,右键,导入,选中这个p12文件,成功就对了。然后右击这个证书,高级里有个管理证书,添加一个IIS能访问它的权限。

如果你没有苹果系统(不太可能吧?),上面的github的说明里有篇作者的博客,说明了在windows系统下如何申请获取这个证书,效果是一样的,获取完它会自动出现在“个人”证书下。

然后按照上面的项目里面的Web示例项目里面的代码来生成自己的passbook就行了。这个代码里有几个值是需要注意的:

request.Identifier是你的pass type id的定义,就是pass.com.yourdomain.xxx的样式。

request.TeamIdentifier是你上面的pass type id生成后的前缀,类似于FE93CED9这样的,通常是系统生成给你的。

request.CertThumbprint是你的windows里面的pass证书的指纹。获取方式:在windows证书管理里“个人”的证书下面双击这个pass的证书,在属性页面,所有的属性里,拉到最下面,把指纹的值复制出来,删掉里面的空格,一堆数字加字母的串就是了。大小写不敏感。

request.SerialNumber全局不能重复,也就是同一个Identifier下不允许出现重复的SerialNumber。

request.FormatVersion = 1; 这个值的定义在Web项目的示例代码里居然没有,但是少了这个safari会提示无法下载该文件。

所有的图片都是必填的,因为这个生成代码里没有判断图片为空的情况,所以必须保证Icon, Logo, Background都有。

其它没什么关键的了,如果一切顺利,你用safari打开这个页面的链接,就应该能弹出这个门票了,点击添加,它也应该能进入你的Passbook应用程序了。

另外对这个项目的源码做的修改还有:修改时间字段的序列化方式,默认是2012-10-30T08:00Z,这样会认为时区为0,进入passbook以后会显示成下午4点。把源码里格式改成2012-10-30T08:00+08:00就好了。

如果你的网站是.net 4.5的,就用项目里的代码自己编译一下就行了,如果你的项目也是3.5的,那可以直接下载我这儿生成的dll文件

升级xcode 4.3以后Category私有方法报警告的问题解决方案

在iPhone项目里使用了几个UIImage的Category代码,为UIImage类直接增加了Alpha,Resize,RoundCorner的功能,升级了xcode 4.3正式版以后,再编译的时候这个代码会提示Category is implementing a method which will also be implemented by its primary class,网上查了一下,找到了一个不是很靠谱的答案,但是却可以解决问题。

在这几个类的.m文件里,顶上都有一个Interface UIImage(),里面定义了一个或几个私有方法的定义,然后在下面的Interface UIImage(Resize)里对这个私有方法做了实现,报警告的就是这个私有方法。

解决方案非常奇特,随便新建一个.h头文件,把Interface UIImage()和它里面的私有方法都移到这个头文件里就好了,把这几个类里面的相关的几个私有方法都合并进去,而且,你甚至不需要在其它文件里对这个头文件做引用,再编译的时候就不会报警告了,而且,原有的方法完全不受影响,在实现和使用这几个私有方法的地方也不会报找不到定义的错误。不知道xcode是如何使用这个没有被任何地方引用的.h文件的。