太赫兹贴(Terahertz paste, a small Far-infrared radiation waves)

Well, first of all, I’m not sure the translation is correct or not. You just need to know this is the practical application of technology, and it’s super great. Let’s call it Terahertz paste, a small Far-infrared radiation waves.

广告写在前面,因为太赫兹贴的神奇,使我创建了个人微店,后面整篇都是发生在我身上的,使用太赫兹贴成功“贴疗”的故事和常识,当然还有一些别人的案例,来吧,来我的微店逛逛,围观一下太赫兹贴,猛击这个链接:http://weidian.com/?userid=899442576

在接触太赫兹光波技术应用实例之前,我认为只有中国的针灸和按摩才会有这么神奇的效果。太赫兹贴确实让我感觉非常奇妙、神奇,奥秘。去年(2015年)在公司整理新办公室,搬某块大的席梦思床的时候,不小心刮到了左手中指甲,把指甲刮翻了,撕裂了甲下皮(hyponychium),不知道有没有撕到甲床(nail bed),痛,不敢拉扯起来看伤口有多深,指甲边缘都是鲜血;搬整结束后,处理了干净指甲边缘的血迹,我用了一张太赫兹贴贴在了指甲甲板到甲下皮外缘(顺便固定,如下图):

Demo-1-1

Demo-1-2

晚上洗澡的时候,害怕水会溜进去,用塑料把手指保护好。第二天一觉醒来,感觉手指没跟平时没有什么两样,试着用捏了它一下,隐约还有点儿痛的感觉,不敢确定它是否已经好了,不敢拆贴。第三天感觉很好了,就大胆地拆贴,结果很意外,指甲及甲下皮之间完好如初,根本看不到伤过的痕迹。

太赫兹贴就是拥有这样一种超酷能力,让你的细胞迅速恢复(不知道有没有加快分裂生长)到最初的状态;通俗的讲,就是细胞(或细胞们组成的结构)“歪”了,太赫兹会把它摆“正”;细胞被“挤压”了,太赫兹让它“恢复弹性”;细胞“受损”了,太赫兹让它“修复”……

下面的图片很有意思,我是个做计算机软件家伙,我太清楚什么叫准确了,有时候文字远远不能描述太赫兹贴的神奇功效……

THz-01

太赫兹贴这玩意,特别适合我们这群整天在电脑前面,不分白天黑夜地埋头苦干,敲打代码的IT民工、码农们,肩膀,脖子,双手累吧?甚至痛吧?使用太赫兹贴就会悄悄地帮你处理这些老毛病,而不需要花专门的时间针灸,按摩,一切尽在你享受生活的过程中,太赫兹贴就这样默默地为您服务。

THz-02

今年(2016年)清明回老家,一路奔波,回到家里很晚了,去我哥家吃晚饭,不知道哪个菜中招了,饭后不到一个钟,感觉浑身不舒服,发冷,赶紧澡也不洗就溜上床去睡觉,结果一夜没睡好,第二天身体还是不舒服,当时没有意识是肚子吃坏了。早餐后,去了一趟厕所,才知道拉肚子了,后来在网上咨询一位健康养生李大师,问问有什么土方法(村里面没有诊所,不想跑到10公里开外)可以治疗拉肚子,李老师笑问我,有没有带太赫兹贴回老家?是啊,我怎么没想到这货呢,李老师叫我用两个太赫兹贴子,分别贴住天枢穴位,喝一两碗盐水,再揉揉肚皮……肚子慢慢就不闹了。

THz-520-03

太赫兹贴,好东东,真是任性!
修复损伤缓解疼痛,哪里不舒服贴哪里!
如果有机会,我会向朋友们推荐尝试太赫兹贴

太赫兹能量贴修复运动损伤,修复劳损,缓解疼痛,恢复疲劳,日本进口,无任何药物成分,纯棉材料,安全有效,无臭、无味、无痕、无痛、小巧、便捷、易学、易用、安全、持久⋯⋯

太赫兹(THz)波,它拥有比已经过时的红外光波更强大的效率及效果,能深入皮肤10cm以下,能有效与细胞分子共振,虽然100年以来,人类在太赫兹光波上获得无数的成果,但是,在医学、养生、农业,畜牧业等新领域上,这还算是老技术新应用。太赫兹贴就是太赫兹波新应用的产品。

太赫兹贴目前最受到国内消费者欢迎的太赫兹波主流产品(一小片可以用10天,一盒可以使用100天)。

太赫兹贴的作用:
1. 太赫兹贴通过持续、平衡放射对人体极为有益的健康太赫兹波,可以和人体细胞发生共振,使患者局部不适的病症回归到人体原本的正常状态。本产品可加速人体血液循环,促进细胞新陈代谢,活血化於,舒缓神经紧张,加速气血通畅,迅速缓解各类肌肉疼痛症状,例如:肌肉及皮肤疼痛、神经痛、因身体各种炎症带来的肌体不适、关节炎、腱鞘炎、甲沟炎等,可改善生理痛,缓解并消除人体因各种不适带来的肌肉酸困痛胀等不良症状。以及各类跌打损伤引起的疼痛。
2. 对脂肪瘤、局部静脉曲张、蟹足肿、腰间盘脱出、腰肌劳损等慢性损伤引起的腰腿疼痛有辅助治疗效果。
3. 带动患部组织细胞分子谐振,修复并改善细胞环境,消炎镇痛。治疗鼻炎选择大椎穴,以及左右迎香穴,改善流鼻涕或打喷嚏症状。
4. 消除疤痕体,尤其是女性妊娠纹。使用时间需要一个月至三个月。
5. 做运动时,贴附在负荷大的肌肉群上,显著消除乳酸堆积,提高乳酸阀值。
6. 对皮肤过敏及某些皮肤炎症有显效。
7. 骨刺、风湿骨关节疾病引起的局部疼痛效果显著。
8. 对消除静脉斑块有显效。
9. 手指开裂,指甲缝开裂,沾水疼痛,用贴后很快好转。
10.运动和旅行后的身体困乏和四肢酸困的迅速恢复。
11. 后背冰冷,用太赫兹贴贴于最不舒适的地方,很快缓解。

太赫兹贴的使用方法:
1. 将患部清洗干净,待干燥后将能量贴取出贴于患部,每次三贴或五贴效果好,患部面积增大时继续增加贴的数量,效果更佳;
2. 针对各种疼痛,将能量贴贴于疼痛点(阿是穴);
3. 生理疼痛贴于中极穴(脐下四指),关元穴(脐下三指)效果更好;
4. 软组织损伤、肌肉拉伤等无破损外伤使用本品贴敷以包裹或覆盖伤处为佳;
5. 本品为长效太赫兹光波释放载体,如无过敏现象可持续使用7-20天,最后待其自然脱落即可。
6. 有得网球肘朋友,半年来一直不能打球,在肘部疼痛部位对象贴太赫兹贴,几分之后更加疼痛,坚持贴了二天天之后,网球肘奇迹般恢复。
7. 某患者背部常常有冰冷感,走访多家医院都没有结果,也尝试使用了各种发热贴,结果都不理想。在朋友推荐下,在背部不舒服的地方贴了几片太赫兹贴,第二天起背部冰冷感消失。
8. 腰肩盘疼痛患者,在疼点贴以及二侧神经贴太赫兹贴,有明显的缓解作用。

 

(转载)iOS开发-SiriKit应用

此篇文章转载自 Sindri的小巢 的:http://www.jianshu.com/p/0881bb0ff538 。文章进一步把苹果官司的文档又提炼到更加适合入门的文章了,有图有真机,更加方便理解,对害怕阅读英文文档的朋友们是个好福利。

 

iOS开发-SiriKit应用

关于SiriKit

在6月14日凌晨的WWDC2016大会上,苹果提出iOS10是一次里程碑并且推出了十个新特性,大部分的特性是基于iPhone自身的原生应用的更新,具体的特性笔者不在这里再次叙述,请看客们移步WWDC2016下载自行观赏。要说里程碑在笔者看来有些夸大其实了,不过新增的通知中心联动3D Touch确实为人机交互带来新的发展,另外一个最大的亮点在于Siri的接口开放。在iOS10中提供了SiriKit框架在用户使用Siri的时候会生成INExtension对象来告知我们的应用,通过实现方法来让Siri获取应用想要展示给用户的内容

Siri服务

iOS10之后,苹果希望Siri能够给用户带来更多的功能体验,基于这个出发点,新增了SiriKit框架。Siri通过语言处理系统对用户发出的对话请求进行解析之后生成一个用来描述对话内容的Intents事件,然后通过SiriKit框架分发给集成框架的应用程序以此来获取应用的内容,比如完成类似通过文字匹配查找应用聊天记录、聊天对象的功能,此外它还支持为用户使用苹果地图时提供应用内置服务等功能。通过官方文档我们可以看到SiriKit框架支持的六类服务分别是:

  • 语音和视频通话
  • 发送消息
  • 收款或者付款
  • 图片搜索
  • 管理锻炼
  • 行程预约

SiriMaps通过Intents extension的扩展方式和我们的应用进行交互,其中,类型为INExtension的对象扮演着Intents extension扩展中直接协同Siri对象共同响应用户请求的关键角色。当我们实现了Intents extension扩展并产生了一个Siri请求事件时,一个典型的Intent事件的处理过程中总共有这三个步骤ResolveConfirmHandle

  • Resolve阶段。在Siri获取到用户的语音输入之后,生成一个INIntent对象,将语音中的关键信息提取出来并且填充对应的属性。这个对象在稍后会传递给我们设置好的INExtension子类对象进行处理,根据子类遵循的不同服务protocol来选择不同的解决方案
  • Confirm阶段。在上一个阶段通过handler(for intent:)返回了处理intent的对象,此阶段会依次调用confirm打头的实例方法来判断Siri填充的信息是否完成。匹配的判断结果包括Exactly one matchTwo or more matches以及No match三种情况。这个过程中可以让Siri向用户征求更具体的参数信息
  • confirm方法执行完成之后,Siri进行最后的处理阶段,生成答复对象,并且向此intent对象确认处理结果然后执显示结果给用户看

具体的执行过程请参考文档讲解视频

创建Intents Extension

SiriKit通过添加App Extension的方式来完成集成,这是一种独立于应用本身运行的代码结构,作为应用的扩展功能,只有在需要的时候系统会唤醒这些Extension代码来执行任务,然后在执行完毕之后将其杀死。另一方面,这些Extension在运行过程中的可占用内存是较少的,并且由于调用时机的限制,我们也无法在运行期间做一些坏事


现阶段集成SiriKit的条件是需要将开发工具升级到Xcode8,需要使用开发者账号到官方网站去下载Xcode8_beta版,并且需要将一台测试设备升级到iOS10系统。选中我们的应用,进入项目总览界面,新增一个TARGET


如上图所示,我创建的Intents Extension被我命名为LXDSiriExtension。记住在创建好一个Extension的时候,会询问你是否激活这个扩展,勾选是。另外还会提示你是否连同Intents UI Extension一并创建了,我们同样选是。这样我们在项目下面总共创建了LXDSiriExtensionLXDSiriExtensionUI两个TARGET,这两个文件目录下面分别存在着一个新的info.plist文件,这个文件用来设置intent事件发生时我们设置的处理类。这里借用WWDC在讲解时的一张ppt来了解:

按图中的层次展开,IntentsSupportedIntentsRestrictedWhileLocked分别是两个字符串数组,每一个字符串表示的是应用扩展处理的intent事件的类名。前者表示支持的事件类型,后者表示在非锁屏状态下执行的事件类型。文件默认是workout类型的事件,在这里笔者改成了发送消息INSendMessageIntent。除此之外,NSExtensionPrincipalClass对应的是INExtension子类类名,这个类用来获取处理intent事件的类。

plist设置

另外,官方讲解中提到了Embedded frameworks,在session中苹果开发人员通过一个消息聊天应用来示例集成SiriKit。由于应用扩展自身的运行机制和应用本身的运行机制不同,彼此之间创建的类是不能访问使用的。因此把我们需要的类开发成frameworks的方式导入我们的应用之后就能够在两种之中都使用到这些类。本文未使用frameworks导入功能,而是模拟了一个类用来管理事件处理过程中的部分逻辑,但是Embedded frameworks这个使用的准则需要记住。这个模拟类的具体代码如下:

import Intents

class LXDMatch {
    var handle: String?
    var displayName: String?
    var contactIdentifier: String?

    convenience init(handle: String, _ displayName: String, _ contactIdentifier: String) {
        self.init()
        self.handle = handle
        self.displayName = displayName
        self.contactIdentifier = contactIdentifier
    }

    func inPerson() -> INPerson {
        return INPerson(handle: handle!, displayName: displayName, contactIdentifier: contactIdentifier)
    }
}

class LXDAccount {
    private static let instance = LXDAccount()

    private init() {
        print("only call share() to get an instance of LXDAccount")
    }

    class func share() -> LXDAccount {
        return LXDAccount.instance
    }

    func contact(matchingName: String) -> [LXDMatch] {
        return [LXDMatch(handle: NSStringFromClass(LXDSendMessageIntentHandler.classForCoder()), matchingName, matchingName)]
    }

    func send(message: String, to recipients: [INPerson]) -> INSendMessageIntentResponseCode {
        print("Send a message: \"\(message)\" to \(recipients)")
        return .success
    }
}

在完成这些需要的工作之后,我们还需要对应用本身的Info.plist配置文件进行设置,新增一个关键字为NSSiriUsageDescription的字符串对象,对应填写的字符串将在我们征询用户Siri权限的时候显示给用户看。比如Siri想要访问您的应用信息之类的提示语。然后通过INPreferences类方法向用户请求Siri访问权限

import Intents

INPreferences.requestSiriAuthorization {
    switch $0 {
    case .authorized:
        print("用户已授权")
        break

    case .notDetermined:
        print("未决定")
        break

    case .restricted:
        print("权限受限制")
        break

    case .denied:
        print("拒绝授权")
        break
    }
}

代码实现

首先我们需要一个INExtension的子类,你也可以在默认创建的子类中实现代码。在方法中,我们通过判断intent的类型来创建对应的处理者实例,然后返回。在本文的示例中,假设我们对Siri说出这么一句话 Siri,在微信上告诉我的家人们今天我不回去吃饭了

class LXDIntentHandler: INExtension {
    override func handler(for intent: INIntent) -> AnyObject? {

        if intent is INSendMessageIntent {
            return LXDSendMessageIntentHandler()
        }
        //  这里可以判断更多类型来返回
        return nil
    }
}

通过判断intent事件是发送消息的聊天事件后,笔者创建了一个用来处理事件的LXDSendMessageIntentHandler类对象,并且返回。在对象创建完成之后需要完成ResolveConfirmHandle三个步骤,具体操作需要子类遵循实现INSendMessageIntentHandling协议来完成:

  • Resolve阶段
    这个阶段需要我们找到消息的具体接收者。在这个过程中,可能会出现三种情况:Exactly one matchTwo or more matches以及No matches,对于这三种情况的处理分别如下:

    func resolveRecipients(forSendMessage intent: INSendMessageIntent, with completion: ([INPersonResolutionResult]) -> Void) {
          if let recipients = intent.recipients {
              var resolutionResults = [INPersonResolutionResult]()
              for  recipient in recipients {
                  let matches = LXDAccount.share().contact(matchingName: recipient.displayName)
                  switch matches.count {
                  case 2...Int.max:    //两个或更多匹配结果
                      let disambiguations = matches.map { $0.inPerson() }
                      resolutionResults.append(INPersonResolutionResult.disambiguation(with: disambiguations))
                  break
    
                  case 1:  //一个匹配结果
                      let recipient = matches[0].inPerson()
                  resolutionResults.append(INPersonResolutionResult.success(with: recipient))
                      break
    
                  case 0:  //无匹配结果
                      resolutionResults.append(INPersonResolutionResult.unsupported(with: .none))
                      break
    
                  default:
                      break
                  }
              }
              completion(resolutionResults)
          } else {
              //未从用户语音中提取到信息,需要向用户征询更多关键信息
              completion([INPersonResolutionResult.needsValue()])
          }
      }

    上面的代码用来确认出消息中的我的家人们指代的是哪些人,其中每个联系人最终用一个INPerson的对象来表示。接着我们需要匹配消息的内容:

    func resolveContent(forSendMessage intent: INSendMessageIntent, with completion: (INStringResolutionResult) -> Void) {
        if let text = intent.content where !text.isEmpty {
            completion(INStringResolutionResult.success(with: text))
        } else {
            //向用户征询发送的消息内容
            completion(INStringResolutionResult.needsValue())
        }
    }

    在匹配完消息接收者跟消息内容之后,对于intent事件的处理就会进入第二阶段Confirm确认值是否正确

  • Confirm阶段
    在这个阶段intent对象本身的信息预计是已经完成填充的,我们通过获取这些填充值来判断是否符合我们的要求。同时在这个阶段,Siri会尝试唤醒应用来准备完成最后的处理操作。前面说了为了保证在应用和应用拓展之间能够进行通信,最好使用frameworks的方式来标记应用是否被启动,再进行相应操作。

    func confirm(sendMessage intent: INSendMessageIntent, completion: (INSendMessageIntentResponse) -> Void) {
          /// let content = intent.content
          /// let recipients = intent.recipients
          /// do or judge in content & recipients
          completion(INSendMessageIntentResponse(code: .success, userActivity: nil))
          /// Launch your app to do something like store message record
          /// Use a singleton in frameworks to remark if the app has launched
          /// if not launched, use the code following
          /// completion(INSendMessageIntentResponse(code: .failureRequiringAppLaunch, userActivity: nil))
      }

    Confirm阶段是我们最后可以尝试修改intent事件中传递的数值的时候。要记住一点,完全精确的内容固然是最好的答案,但是过多的让Siri询问用户参数的详细信息也会导致用户的抵触

  • Handle阶段
    Handle阶段不需要做太多额外的工作,判断一下消息接收者或消息内容是否存在,如果存在,执行类似保存/发送的工作,然后完成。否则告诉Siri本次的intent事件处理处理失败,我们还可以通过配置NSUserActivity对象来告诉Siri失败的原因

    func handle(sendMessage intent: INSendMessageIntent, completion: (INSendMessageIntentResponse) -> Void) {
        if intent.recipients != nil && intent.content != nil {
            /// do some thing success send message
            let success = LXDAccount.share().send(message: intent.content!, to: intent.recipients!)
            completion(INSendMessageIntentResponse(code: success, userActivity: nil))
        } else {
            let userActivity = NSUserActivity(activityType: String(INSendMessageIntent))
            userActivity.userInfo = [NSString(string: "error") : String("AppDidNotLaunch")]
            completion(INSendMessageIntentResponse(code: .failure, userActivity: userActivity))
        }
    }

事件UI

可以看到上面的代码主要集中在事件处理的逻辑上,那么在和Siri交互的过程中,我们同样可以让Siri展示响应的自定义界面:

在我们创建Intents Extension的时候,同时Xcode也询问我们是否创建Intents UI Extension。在后者的文件目录下也有一个Info.plist,有着跟前面类似的键值对,差别在于后者只有一个状态的设置。

在这个文件目录下存在一个故事板MainInterface,这个故事板就是Siri和应用交互时展示给用户看的界面。通过修改这个故事板的界面元素,就可以实现上图中的效果了。此外,在这个界面将要展示之前,我们可以修改类文件中的代码完成界面信息填充的操作:

func configure(with interaction: INInteraction!, context: INUIHostedViewContext, completion: ((CGSize) -> Void)!) {
    //这里执行界面设置的代码,完成之后执行completion代码就会让界面展示出来
    if let completion = completion {
        completion(self.desiredSize)
    }
}

var desiredSize: CGSize {
    return self.extensionContext!.hostedViewMaximumAllowedSize
}

尾言

在观看WWDC2016的新特性的时候,最开始给Siri和应用的交互惊艳到了。但是后来阅读文档发现这种交互仍然存在着过多的限制,整体而言并没有对Siri的使用带来更明显的提升。但是毫无疑问,这种交互如果苹果能继续对其进行补充发展,可以给我们的应用带来更多的新活力。

 

一起来玩玩SiriKit编码吧,很嗨

iOS 10 beta 出来一周罗,SiriKit Programming Guide 也出来几天了,初步研究了一下SiriKit,在Workouts (健身) 类别上测试了一下,还挺好玩。不过,开放的范围也太窄了。。。

记得要调用Siri授权允许:

[INPreferences requestSiriAuthorization:^(INSiriAuthorizationStatus status) {

if(status == INSiriAuthorizationStatusNotDetermined) {

NSLog(@”Siri not authorization yet”);

}

else if(status == INSiriAuthorizationStatusRestricted) {

NSLog(@”Siri authorization restricted”);

}

else if(status == INSiriAuthorizationStatusDenied) {

NSLog(@”Siri authorization denied”);

}

else if(status == INSiriAuthorizationStatusAuthorized) {

NSLog(@”Siri authorization authorized”);

NSString* language = [INPreferences siriLanguageCode];

if ([language isEqualToString:@”en-US”])

NSLog(@”The Siri language is U.S. English.”);

}

}];

还有就是,搞掂英文发音对接了,但是中文本地化Siri支持。。。研究了半天,妙是很复杂的样子,一下子还没弄得上。

先发发几张图片吧,希望正在研究的小伙伴们一起来研究研究。

450-IMG_5305 450-IMG_5306 450-IMG_5329 450-IMG_5330 450-IMG_5331 450-IMG_5332 450-IMG_5333

SiriKit Programming Guide

Hello every one,

Apple just release the SiriKit Programming Guide, here it is: https://developer.apple.com/library/prerelease/content/documentation/Intents/Conceptual/SiriIntegrationGuide/index.html .

Finally we got a chance to talk to our own apps via Siri now. But please upgrade your iPhone to iOS 10 beta first.

嘿嘿,刚刚谷歌搜索了一下,发现国内的天狐(Jakey Shao)已经翻译好了篇:SiriKit编程指南,链接 在这儿:http://www.skyfox.org/sirikit-programming-guide.html 。感谢 Jakey。

The content from the pages above:

—————————————————————-

Introduction to SiriKit

SiriKit is a way for you to make your content available through Siri. It also lets you add support for your services to the Maps app. To support SiriKit, you use the Intents framework and Intents UI framework to implement one or more extensions that you then include inside your iOS app. When the user requests specific types of services through Siri or Maps, the system uses your extensions to provide those services.

Add SiriKit support only if your app implements one of the following types of services:

  • Audio or video calling

  • Messaging

  • Payments

  • Searching photos

  • Workouts

  • Ride booking

Each type of service represents a domain of functionality that you can support, and each domain defines one or more intents for you to support. An intent is an object that represents the user’s intention. Siri and Maps create intent objects in response to user requests and fill them with any user-specified information. For example, an intent for sending a message might include the recipient and contents of the message. You use intent objects to provide an appropriate response and to perform the associated tasks. For more information about the supported domains and their intents, see Intents Domains.

The Intents and Intents UI Extensions

Siri and Maps interact with your app’s services through two different types of extensions:

  • An Intents extension communicates your app’s content to Siri and Maps and performs the tasks associated with any supported intents.

  • An Intents UI extension provides a custom interface for your content within the Siri or Maps interface. This extension is optional.

You must provide an Intents extension to support SiriKit. Your Intents extension is responsible for handling intents and for providing information that Siri and Maps can use to communicate with the user. Providing an Intents UI extension is optional, but is a way of making interactions with your app through Siri seem familiar to your users. For example, an Intents UI extension can display branding and other app-specific information to reinforce the idea that your app is providing the response.

For information about the classes used to implement your Intents extension, see Intents Framework Reference. For information about the protocols used to implement your Intents UI extension, see Intents UI Framework Reference.

The Role of Siri and the Maps App

Siri handles the language processing and semantic analysis needed to turn the user’s spoken requests into actionable intents that your extensions can handle. Siri and the Maps app manage all user interactions and display the results to the user using a standard system interface. Your role is primarily to provide the data to display. If your app includes an Intents UI extension, you can also provide a custom interface to supplement the default system interface.

Ride booking and restaurant reservations are handled primarily by the Maps app, although users can also book rides using Siri. Your Intents extension handles interactions that originate from the Maps app in the same way it handles requests coming from Siri. If you customize the user interface, your Intents UI extension can configure itself differently depending on whether the request came from Siri or Maps.

Creating the Intents Extension

Siri and Maps interact with your app through your Intents extension. The entry point of your Intents extension is the INExtension object, whose sole job is to direct Siri to the objects capable of responding to user requests. When implementing an Intents extensions, there are three types of objects that you use regularly:

  • An intent object defines the user’s intent and contains the data that Siri gathered from the user.

  • handler object is a custom object that you define and use to resolve, confirm, and handle an intent.

  • response object is a data object containing your response to an intent.

When there is an intent for your extension to handle, Siri asks your INExtension object for an object capable of handling that intent. A handler object can be any type that you want, but it must implement the specific methods needed to handle the given intent. Each intent has an associated protocol that its handler must adopt. The methods of this protocol are divided into three groups: resolution methods, confirmation methods, and handling methods. You implement the methods you need and provide Siri with information about how you plan to handle the intent.

Figure 2-1 illustrates the high-level flow between Siri and a handler object in your extension. In this example, when the user asks to book a ride with a ride booking service, Siri creates an intent object with the ride parameters and sends it to the handler. The handler resolves the data in the intent object to values that it can use to fulfill the ride. When all ride parameters have been resolved, Siri asks the handler to confirm how it plans to handle the ride request and to perform any final validation. During the confirmation phase, the handler provides a response object with the details of the ride, which Siri might display to the user. If the user accepts the ride, Siri asks the ride request handler to handle the intent. The handler responds by booking the ride and returning a response with the details of the booked ride.

Figure 2-1Handling a ride request intentimage: ../Art/resolve_confirm_handle_cycle_2x.png

For general information on how to create extensions, see App Extension Programming Guide.

Configuring Your Xcode Project

To support SiriKit, add an Intents extension to your iOS app.

To add an Intents extension to your app
  1. Open your existing iOS app project in Xcode.

  2. Select File > New > Target.

  3. Select Intents extension from the Application Extension group for the appropriate platform.

  4. Click Next.

  5. Specify the name of your extension and configure the language and other options.

    Enable the Include UI Extension option if you plan to customize portions of the Siri interface.

  6. Click Finish.

The Intents extension template provided by Xcode includes an INExtension subclass for you to customize. You use that class to create the handler objects needed to handle intents. Siri uses your extension’s Info.plist file to discover which intents that it supports. The Xcode-provided Info.plist file comes mostly configured, but you must modify portions of it to specify which intents that your extension handles.

To specify the intents that your app supports
  1. In Xcode, select the Info.plist file of your Intents extension.

  2. Expand the NSExtension and NSExtensionAttributes keys to view the IntentsSupported and IntentsRestrictedWhileLocked keys.

  3. In the IntentsSupported key, add String items for each intent you plan to support. Set the value of each item to the class name of the intent.

    This key is required. You can support all or some of the intents in a given domain, and your extension can support as many domains as you want. The value must contain all of the intent class that your extension supports.

  4. In the IntentsRestrictedWhileLocked key, add String items for any intents that you support only while the user’s device is unlocked. Set the value of each item to the class name of the intent.

    This key is optional. The value contains the subset of intent classes for which you require an unlocked device. You might include an intent in this list if handling the intent involves accessing protected files on disk or manipulating sensitive user data.

Implementing the behavior of your extension involves the following tasks:

  • Define handler classes for each intent that you support. You can define one handler class for each intent or use a single handler class for multiple intents. The use of protocols makes it easy to turn any object into a handler object for intents. For information on how to implement your handlers, see Resolving and Handling Intents.

  • Add the custom source files that you need to perform the tasks for any supported intents. Your extension must be capable of performing the tasks associated with any intents. For example, the handlers for a ride booking service must be able to book rides and get information about rides using that service. Add whatever source files to your extension project that you need to make that happen; see Structuring Your App’s Services.

  • Update the default INExtension class that Xcode provided. Modify the handlerForIntent: method of the Xcode-provided INExtension subclass to create and return your custom handler objects. For information on how to implement this method, see INIntentHandlerProviding Protocol Reference.

  • Define any custom vocabulary that your app uses. Apps that use words or phrases in specific ways can define a custom vocabulary file to help Siri understand that custom usage. For information on how to provide Siri with your app’s custom vocabulary, see Specifying Custom Vocabulary.

For a list of available intents and the associated classes and protocols you implement for each one, see Intents Domains.

Requesting Siri Authorization in Your iOS App

Before your Intents extension can be used and before any user-specific vocabulary can be registered, your iOS app must request authorization to use Siri. To request authorization, do the following:

  • Include the NSSiriUsageDescription key in your app’s Info.plist file. The value for this key is a string describing what data your app sends to Siri when processing intents. For example, a workout app might set the value to the string “Workout information will be sent to Siri.”

  • Call the requestSiriAuthorization: class method of INPreferences at some point during your app’s execution.

The first time that your iOS app calls the requestSiriAuthorization: method, the system displays an alert that prompts the user to authorize your app. The alert includes the usage description string you provided in the NSSiriUsageDescription key of your app’s Info.plist file. The user can approve or deny your app’s request for authorization and can change your app’s authorization status later in the Settings app. The system remembers your app’s authorization status so that subsequent calls to the requestSiriAuthorization:method do not prompt the user again.

Structuring Your App’s Services

Your Intents extension is an agent that acts on behalf of your app and should be able to perform the same services that your app does. Because your iOS app and Intents extension perform many of the same tasks, consider the following:

  • Use a private shared framework to store the code for your core services. Link the framework into both your iOS app and your Intents extension. A shared framework minimizes the size of both executables and ensures that both are using the same code to interact with your services.

  • Use a shared container to store common resources. If your services use images or data files that live outside the app bundle, put those resources into a shared container. Enable shared container support in the Capabilities tab of each target.

Testing Your Extension

To test your Intents extension, you must run it on a device. Xcode provides support for launching your Intents extension directly from your Xcode project and for debugging it while it runs on the device.

To run and debug your Intents extension on a device
  1. Set the active scheme to the one for your Intents extension.

    When you add an Intents extension to your project, Xcode automatically creates a scheme for running that extension.

  2. Configure the scheme to run on a connected device.

    You cannot debug your Intents extension in the simulator.

  3. Select Product > Run to launch your extension on the device.

  4. When prompted by Xcode, select Siri as the app to run.

    Xcode builds your app and extension, installs them on the device, and launches Siri.

When installing your extension for the first time, Siri may not immediately recognize your app extension. You may need to wait several minutes before you can issue any relevant commands. Similarly, when updating your Info.plist file, you may need to wait several minutes before Siri recognizes the changes.

Using the Intents Framework From Your iOS App

Your iOS app uses the Intents framework to perform specific tasks:

  • Use the INVocabulary class to register user-specific vocabulary terms. Register user-specific vocabulary only for terms that are custom to your app and the specific user and that might be misconstrued by Siri otherwise; do not register common or easily understandable terms. For information about registering vocabulary, see Specifying Custom Vocabulary.

  • Use the INPreferences class to get the Siri language for localized content. Use this information to format any content that your app shares with the extension. For more information, see INPreferences Class Reference.

  • Create INInteraction objects when you want to donate interactions to the system. Donating interactions lets you provide context about what the user was doing. The system and other apps can use that information to improve search results and provide a better user experience. For more information, see INInteraction Class Reference.

Internationalization and Siri

The user can configure a different language for Siri than for the rest of the device. In an iOS app, the system retrieves localized content using the device-specific language setting. In your Intents extension, the system retrieves localized content using the Siri language. If your app shares localized content with your extension, you must share that content using the Siri language, which your app can retrieve using siriLanguageCodemethod of the INPreferences class.

For more information about getting the Siri language, see INPreferences Class Reference.

Resolving and Handling Intents

In your Intents extension, handler objects do the crucial work of resolving, confirming, and handling intent objects sent by Siri. Each intent object has an associated protocol that was designed specifically for handling that intent. For example, the handler for an INRequestRideIntent object adopts the INRequestRideIntentHandling protocol.

The methods for each protocol are divided into three groups:

  • Zero or more methods for resolving intent parameters

  • A confirm method that provides Siri with your proposed response

  • A handle method that performs the task

When implementing a handler, you must always implement the method for handling the intent. All other methods are optional, but recommended. The resolve and confirm methods are your opportunity to validate the contents of an intent before you try to handle it. You also use those methods to identify or create the internal data objects that you need to handle the intent.

For a list of intents and the corresponding intent handling protocols, see Intents Domains.

Resolving the Parameters of an Intent

During the resolution phase of an intent, Siri asks your handler to resolve key parameters and to confirm the values you intend to use. Because the data coming from the user is spoken, there may be missing or ambiguous information. The resolution phase is your opportunity to validate the data that was provided and to let Siri know if you need clarifications or more information. It is recommended that you implement all of the resolution methods for a given handler protocol. Although you might not use all of the parameters, each method tells Siri whether you identified an appropriate value in your own data structures.

You communicate your resolution results back to SiriKit using resolution result objects, which are instances of the INIntentResolutionResult class. There are distinct subclasses for each parameter type that you might need to resolve. For example, when resolving a contact, in the form of an INPerson object, you return an instance of the INPersonResolutionResult class. When instantiating your subclass, use the creation method that corresponds to the outcome of your resolution efforts. Table 3-1 lists the possible outcomes and explains when you might use each one.

Table 3-1Possible outcomes when resolving intent parameters

Resolution

Examples

You successfully matched the value.

Specify this resolution when you successfully validated the parameter and have a corresponding value you can use to handle the intent. This result is the most preferable because it requires no further user interactions, and you should take steps to resolve parameters successfully whenever you can. For example, you might take advantage of user patterns or favorites to identify likely values for a parameter.

A value was not required.

Specify this resolution when you do not need the value of the parameter to handle the intent. Typically, you return this result only when a parameter is used some of the time and is not currently relevant. For example, you might return this result when the intent contains a goal for an open workout, which normally has no goal.

Values need disambiguation.

Specify this result when you identify two or more possible results and cannot choose one definitively. This result usually leads to additional user interactions to disambiguate the parameter. For example, if a message recipient is identified only as “Brandon” but the user has two contacts with that name, you could use this result to ask for help in choosing the correct one.

The value needs user confirmation.

Specify this result when you want Siri to prompt the user for confirmation of the specified value. Siri always asks for confirmation for intents involving financial transactions, but you can use this in cases where you want the user to confirm the value that you supplied. For example, you might request confirmation of the recipients of an audio call.

The value needs more details.

Specify this result when the information for a given parameter is incomplete. Some parameters are represented by objects that might have multiple pieces of information. For example, use this result if an INPerson object does not contain enough details to identify a specific contact and you cannot make a reasonable guess from the available information. Use this resolution to ask the user to provide the missing information.

A value is required.

Specify this result when the value for a required parameter is missing. Siri asks your extension to resolve every parameter, regardless of whether the user specified a value for that parameter. Use this resolution when you require a value to proceed. For example, you might return this result if the user requests a payment from another person but does not specify the amount.

The value is unsupported.

Specify this result when your app does not support a specific value or when that value conflicts with the values of other parameters. You must specify the reason why a parameter is not valid. You may also have the option of specifying alternative values that are valid. For example, you might return this result when the user wants to send a payment in Swiss francs but your app requires transactions to be in Euros or US Dollars. When providing a list of alternative values, provide only the most likely alternatives rather than every possible value. Offering too many choices may make it difficult for the user to choose the right one.

When resolving parameters, try to reach a successful resolution as quickly as possible. Asking for more information leads to additional user interactions and additional calls to your handler, which incurs delays and might frustrate the user. Instead, try to choose reasonable values based on the user’s patterns and habits, and ask for disambiguation or confirmation only as needed.

Listing 3-1 shows an example from a ride booking app that validates the drop-off location for the ride. If a a drop-off location is present, the method returns a successful result; otherwise, the method returns a result that indicates the value is needed.

Listing 3-1Resolving a required parameter of an intent

OBJECTIVE-C

  1. - (void)resolveDropOffLocationForRequestRide:(INRequestRideIntent *)requestRideIntent
  2. withCompletion:(void (^)(INPlacemarkResolutionResult *resolutionResult))completion {
  3. CLPlacemark* location = requestRideIntent.dropOffLocation;
  4. INPlacemarkResolutionResult* result = nil;
  5. if (location) {
  6. // Use the specified drop-off location.
  7. result = [INPlacemarkResolutionResult successWithResolvedPlacemark:location];
  8. }
  9. else {
  10. // Ask for the drop-off location.
  11. result = [INPlacemarkResolutionResult needsValue];
  12. }
  13. // Execute the completion block with the result.
  14. completion(result);
  15. }

SWIFT

  1. func resolveDropOffLocation(forRequestRide intent: INRequestRideIntent,
  2. with completion: (INPlacemarkResolutionResult) -> Void) {
  3. let location = intent.dropOffLocation
  4. var result : INPlacemarkResolutionResult
  5. if location != nil {
  6. // Use the specified drop-off location.
  7. result = INPlacemarkResolutionResult.success(with: location!)
  8. }
  9. else {
  10. // Ask for the drop-off location.
  11. result = INPlacemarkResolutionResult.needsValue()
  12. }
  13. // Execute the completion block with the result.
  14. completion(result);
  15. }

Confirming a Request

After all the parameters of an intent have been resolved satisfactorily, the handler is asked to confirm the details of the intent and propose a response. The satisfactory resolution of all parameters happens when all parameters have either been resolved successfully or are not required to proceed. During confirmation, you can perform additional validation of all intent parameters, ensuring that you can use that information to perform the requested service.

Implementing the confirmation method in your handler is not required but is strongly recommended. Siri always prompts the user to confirm important requests, especially those that are irrevocable or involve financial transactions. Siri may or may not prompt the user in other situations. Either way, implementing the confirmation method is a good way to ensure that you are able to provide a response.

Listing 3-2 shows a simple confirmation method for starting a workout. The main job of the confirmation method is to execute the provided completion block with a response object. This example creates a response object that indicates a workout can be started successfully. If you wanted to perform any additional validation, you could use this method to do it.

Listing 3-2Confirming the start of a workout

OBJECTIVE-C

  1. - (void)confirmStartWorkout:(INStartWorkoutIntent *)startWorkoutIntent
  2. completion:(void (^)(INStartWorkoutIntentResponse * _Nonnull))completion {
  3. NSUserActivity *activity = [[NSUserActivity alloc]
  4. initWithActivityType:@"startWorkoutActivityType"];
  5. INStartWorkoutIntentResponse* response = [[INStartWorkoutIntentResponse alloc]
  6. initWithCode:INStartWorkoutIntentResponseCodeSuccess userActivity:activity];
  7. // Perform any final validation.
  8. completion(response);
  9. }

SWIFT

  1. func confirmStartWorkout(startWorkout intent: INStartWorkoutIntent,
  2. completion: (INStartWorkoutIntentResponse) -> Void) {
  3. let activity = NSUserActivity(activityType: "startWorkoutActivityType")
  4. let response = INStartWorkoutIntentResponse(code: .success, userActivity: activity)
  5. // Perform any final validation.
  6. completion(response)
  7. }

For information about the response objects you create for each intent, see Intents Domains.

Handling a Request

The final stage of handling an intent is to perform the action associated with that intent. The handle method of your handler object has two responsibilities:

  • Perform the task associated with the intent.

  • Return a response object with information about what your app did.

When handling a task, connect to your service and perform the associated task. The implementation details of performing the task are entirely your responsibility. For example, a ride booking service would connect to the company’s web server to place the ride request and get back the results.

In addition to performing the task, create a response object containing the details of what you did. The information you put into a response object differs for each intent, and some response objects may need more information than others. You should also provide an NSUserActivity object with any additional details that your app might need to continue the task. The user might choose to launch your app immediately to get more details. If that happens, the user activity object should contain any information you need to configure your app’s interface with the details of the task.

Listing 3-3 shows a handle method that reports the status of a newly started workout. In this example, the handler calls a custom method that notifies the app to start the specified workout before returning a response to Siri.

Listing 3-3Handling the start of a workout

OBJECTIVE-C

  1. - (void)handleStartWorkout:(INStartWorkoutIntent *)startWorkoutIntent
  2. completion:(void (^) (INStartWorkoutIntentResponse * _Nonnull))completion {
  3. NSUserActivity *activity = [[NSUserActivity alloc]
  4. initWithActivityType:@"startWorkoutActivityType"];
  5. INStartWorkoutIntentResponse* response = [[INStartWorkoutIntentResponse alloc]
  6. initWithCode:INStartWorkoutIntentResponseCodeSuccess userActivity:activity];
  7. // Do the work…
  8. [self startWorkoutWithName:startWorkoutIntent.workoutName];
  9. completion(response);
  10. }

SWIFT

  1. func handleStartWorkout(startWorkout intent: INStartWorkoutIntent,
  2. completion: (INStartWorkoutIntentResponse) -> Void) {
  3. let activity = NSUserActivity(activityType: "startWorkoutActivityType")
  4. let response = INStartWorkoutIntentResponse(code: .success, userActivity: activity)
  5. // Do the work…
  6. self.startWorkout(startWorkoutIntent.workoutName!)
  7. completion(response)
  8. }

Changes you make in your Intents extension should also be reflected in your iOS app’s interface. As part of handling a task, your extension should make any needed data available to your iOS app. Even though you can provide an INInteraction object with the details of the intent and your response, user activity objects are not delivered to your app in all cases. For example, the user might launch the app without using Handoff. To ensure that your app is up to date, your extension can place any updated files in a shared group container that is accessible by your app. Your app can then use background app refresh opportunities to fetch any changes and update itself.

For information about the response objects you create for each intent, see Intents Domains.

Specifying Custom Vocabulary

Apps that have custom vocabulary can inform Siri about the proper usage of that vocabulary. Custom vocabulary refers to any terms that Siri might not understand naturally. For example, a ride booking app that refers to a specific vehicle type as a “Vroom” could define that term for Siri and provide examples of how users might use it. Defining your app’s vocabulary gives Siri hints for understanding user commands related to your app and helps improve the overall user experience.

There are two ways to define your app’s custom vocabulary:

  • To register terms that are specific to a single user, use an INVocabulary object.

  • To register terms common to all users of your app, add an AppIntentVocabulary.plist file to your iOS app.

Registering User-Specific Vocabulary Terms

Use the shared INVocabulary object to register vocabulary that is specific to a single user. User-specific terms must belong to one of the following categories:

  • Contact names (only if they are not managed by the Contacts framework)

  • Photo tags

  • Photo album names

  • Workout names

When selecting the vocabulary to register, choose terms that could be misunderstand by someone not familiar with your app. For example, a messaging app might register the screen names from the user’s favorites list. Do not register terms that are easily understood, such as “My Photo Album” or “My Workout”. Instead, focus on terms whose literal meaning differs from your app’s usage of those terms.

You register sets of terms for each category using the setVocabularyStrings:ofType: method. Registering a new set of terms replaces the previous terms for that category, so include all of the terms you need each time you call the method. Do not include every possible term that is available. Instead, focus on terms associated with the user’s favorites, terms that are used frequently, or terms that were used recently. The most important terms should always be first in the NSOrderedSet object that you create.

Listing 4-1 shows an example that registers a set of custom workouts. The app sorts the workout names based on the last time they were used, placing the most recently used workouts first in the ordered set. Typically, you register user-specific terms from your app, instead of from your Intents extension.

Listing 4-1Registering user-specific vocabulary

OBJECTIVE-C

  1. NSOrderedSet* workoutNames = [self sortedWorkoutNames];
  2. INVocabulary* vocabulary = [INVocabulary sharedVocabulary];
  3. [vocabulary setVocabularyStrings:workoutNames
  4. ofType:INVocabularyStringTypeWorkoutActivityName];

SWIFT

  1. let workoutNames = self.sortedWorkoutNames()
  2. let vocabulary = INVocabulary.shared()
  3. vocabulary.setVocabularyStrings(workoutNames, of: .workoutActivityName)

Registering a set of terms is not a guarantee that they will be recognized by Siri later. Siri treats any terms you provide as hints and uses them if it can. For this reason, it is important to focus on terms whose usage is unique to your app and to the specific user and that are actually used. Do not include generic terms or terms that the user would never use when talking to Siri.

For additional information about registering user-specific vocabulary terms, see INVocabulary Class Reference.

Creating the Global Vocabulary File

Use a global vocabulary file to register vocabulary that is common to all users of your app. Global terms must belong to one of the following categories:

  • Ride options

  • Workout names

A global vocabulary file is a property list file with the name AppIntentVocabulary.plist. Place this file in the .lproj directory corresponding to your app’s base development language. Include localized versions in your app’s language-specific project (.lproj) directories. In Xcode, you can create localized versions automatically from the File inspector.

To create the global vocabulary file
  1. Select New > File.

  2. In iOS > Resource, select the Property List file type.

  3. Click Next.

  4. Set the name of the file to AppIntentVocabulary.plist.

  5. Click Create.

  6. Select the AppIntentVocabulary.plist file in your project.

  7. Add two keys under the Root element.

    • Set the name of the first key to ParameterVocabularies.

    • Set the name of the second key to IntentPhrases.

The global vocabulary file contains two keys at the root level:

  • The ParameterVocabularies key defines your app’s custom terms and the intent parameters to which they apply.

  • The IntentPhrases key contains example phrases that include your custom terms.

For each custom term, specify the term and the intent property to which that term applies. A single term may be associated with multiple intents. For example, a custom workout name could be applied equally to all of the other workout-related intent classes. In addition to the term and intent information, you specify an identifier string for SiriKit to place in the intent object. During resolution, you would resolve that identifier string to the appropriate workout.

For detailed information about the structure and keys of the global vocabulary file, see App Vocabulary File Format.

Providing a Custom Interface

Siri and the Maps app display the information provided by your Intents extension, but you can customize portions of the displayed content using an Intents UI extension. The purpose of an Intents UI extension is to customize the Siri or Maps interface so that users know that your app is providing the response. For example, you might add visual elements related to your brand or provide additional information that is related to the request but not part of the standard interface.

An Intents UI extension contains a view controller that you fill with information related to your app’s response. You can display custom information for any or all of your app’s supported intents. When a response for a supported intent is displayed, Siri and Maps integrate the view controller from your Intents UI extension into the interfaces they display. Use the response data passed to your view controller to configure the content of any views and to determine what content to display. You may display any information that is relevant to your brand or that is useful to the user. You may not display advertising.

You can provide an Intents UI extension to support intents related to ride booking, messages, payments, and fitness. Some examples of how you might use your Intents UI extension include the following:

  • Ride booking. Display additional ride details and branding.

  • Messages. Display message content using your brand colors and style.

  • Payments. Display branding or additional payment-related information.

  • Fitness. Display custom workout information and branding.

An Intents UI extension may support multiple intents, but all of those intents share the same view controller. Your view controller receives enough information to configure itself differently based on the current intent and the display environment. Most intents are displayed by Siri, but ride booking intents are more commonly displayed by the Maps app.

Configuring Your Xcode Project

To customize the Siri interface, add an Intents UI extension to your iOS app. You can add this extension when creating your Intents extension or you can add it to your project later.

To add a new Intents UI extension to your app
  1. Open your existing iOS app project in Xcode.

  2. Select File > New > Target.

  3. Select Intents UI extension from the iOS Application Extension group.

  4. Click Next.

  5. Specify the name of your extension and configure the language and other options.

  6. Click Finish.

The Intents UI extension template provided by Xcode contains a storyboard with a single view controller. Siri and Maps always load and display the initial view controller in your storyboard file, so configure that view controller with the content that you want displayed. If you want to display different sets of views for each intent, create child view controllers and embed them into your initial view controller at runtime.

The Info.plist file of your Intents UI extension tells Siri which intents your extension supports. Table 5-1 lists the keys that must be included in the dictionary of the NSExtension key in the Info.plist file of your extension. You specify the supported intents in the dictionary of the NSExtensionAttributes key.

Table 5-1Keys for the Info.plist file of an Intents UI extension

Key

Description

NSExtensionAttributes

The dictionary associated with this key must contain an IntentsSupported key whose value is an array of strings. The value of each string is the name of an intent class that the extension supports.

NSExtensionMainStoryboard

The value of this key is the name of the storyboard file containing your extension’s view controller. You may substitute the NSExtensionPrincipalClass key for this one if you prefer to have the system create your view controller programmatically.

NSExtensionPointIdentifier

The value of this key must be the string com.apple.intents-ui-service.

To specify the intents that your custom interface supports
  1. In Xcode, select the Info.plist file of your Intents extension.

  2. Expand the NSExtension and NSExtensionAttributes keys to view the IntentsSupported key.

  3. In the IntentsSupported key, add items for each intent you support. The type of each item must be String, and the value must be the class name of the intent.

Implementing Your View Controller

The initial view controller of your Intents UI extension’s storyboard is responsible for displaying your content. Before displaying a response, Siri or the Maps app load that view controller and call its configureWithInteraction:context:completion: method. Use the interaction object from that method to configure the contents of your view controller and prepare it to appear onscreen. Use the context parameter to adjust the presentation of your content as needed for the Siri or Maps interfaces.

While onscreen, your view controller remains part of the foreground interface until the user dismisses the Siri or Maps interface. You may update your view controller’s interface as needed using timers or other programmatic means, and your view controller participates in the normal view controller processes when it is loaded, shown, and hidden. However, your view controller does not receive touch events and other responder-chain events while it is onscreen, and you cannot add gesture recognizers or try to intercept events in other ways. Therefore, you should never include controls or views that require user interactions.

Figure 5-1 shows the life cycle of your Intents UI extension and its view controller. The system creates your view controller and calls its configureWithInteraction:context:completion: method, passing it the interaction object you need to configure your interface. Once configured, your view controller is presented onscreen with the rest of the Siri or Maps content. While onscreen, your view controller can run animations and update itself using timers and other programmatic means, but it does not receive touch events or responder-chain events.

Figure 5-1Life cycle of an Intents UI extensionimage: ../Art/intentsui_lifecycle_2x.png

When the user dismisses the Siri or Maps interface, the system releases its reference to your view controller and your Intents UI extension. View controllers should be used only to display information. Do not try to save data or communicate with your app when your view controller moves offscreen.

Here are some tips for implementing the view controller for your Intents UI extension:

  • Incorporate your brand into your interface. Using your app’s color, imagery, and other design elements is a great way to add familiarity and convey the presence of your app.

  • Use child view controllers to switch between different types of content. Your Intents UI extension has only one main view controller. If you display different content for different intents, use child view controllers to manage the views that are relevant for that intent. In your configureWithInteraction:context:completion: method, install the child view controller based on the provided intent object.

  • Configure any animated content to run only when your view controller is visible. Wait until your view controller’s viewDidAppear: method is called to start animations. Stop animations in your view controller’s viewWillDisappear: method.

  • Configure your view controller’s view as quickly as possible so that Siri can display it. Your view controller may not be onscreen for very long, so use local resources and the provided INInteractionobject for the bulk of your configuration. If you need to fetch more information from a server, always do so asynchronously and update your interface later.

  • Do not include advertising in your interface. You may include branding and information that is relevant to the user, but advertising is prohibited.

For more information about configuring your view controller, see INUIHostedViewControlling Protocol Reference.

Replacing the Default Interface

For ride booking and message intents, you can hide the default content provided by the system if it conflicts with the content you provide. For message intents, Siri displays the message content and recipients. For ride booking intents, Siri and the Maps app display a map showing the user’s location. If you provide your own interfaces with the same information, use the properties of the INUIHostedViewSiriProviding protocol to suppress the display of the system interfaces.

For more information about suppressing the default map and message information, see INUIHostedViewSiriProviding Protocol Reference.

腾讯视频QQLive.exe开发者,我差点儿就笑出来

刚才协助朋友做培训幻灯片,因为他用来培训内容来源于他们圈子内的微信公众号文章,在制作PPT时,想在PPT中播放某段QQLive平台上的视频,最后因为没有该视频的网页地址,只好选择在腾讯视频PC客户端中搜索,并下载了该视频。

印象中上一次下载过一个视频,缓存文件夹只有一个,直接把 *.tdl 文件用 copy 合并到 mp4 文件,就获得最后可正常播放的视频文件了。时隔没多久,这次下载了一段半个多小时的视频,进缓存文件夹一看,有点儿傻了,发现居然是一堆文件夹。

随便进文件夹看了一下,发现各个文件夹中的文件大小总量很小,10M左右,我差点儿就笑出来,是的,我差点儿就笑出来,你们懂的。

于是,分别在各个文件夹内使用 copy /b *.tdl x.mp4(x 从 1 到 N),很快就得到了多个时长为5分钟左右的 mp4 视频。

接下来,一段一段播放这些视频,以确定它们在原视频中的顺序,并重新给它们命名为 y.mp4(y 从 1 到 N),到这里,一切资料准备就绪了。

当然这时候,不能够单纯地使用 copy /b *.mp4 final.mp4来合并视频,因为如果这样合并出来的视频在结构上只会标识时长为5分钟(应该是每一段视频对应时长)。发现网上有不少网友反映,视频只能播放5分钟,原因就在这里了,播放器获取视频时长时得到5分钟数据,当然会只播放5分钟了。

其实这时候很简单了,随便上网下载一个视频合并软件就能够搞掂了,视频合并软件如果在不需要重新编码的情况下(视频尺寸,码率等相同)合并速度是非常快的,它只需要做2件事,1是合并,2是重新计算并记录视频的总时长即完成。

顺便推荐一下这个软件,我就是用它来合并的,名称为 视频剪切合并器,网站为:http://www.yyzsoft.com/ 。

最后,我想搞笑一下那位腾讯视频 QQLive.exe 的开发者,你是太懒了呢,还是最近你们部门的鼓励师被抽走了,你就这样草草 “加密” 了事?何必呢,从上一个版本到这个版本,就是简单的把视频多分几个段,完全没有任何技术含量,根本不需要动用破解力量,只是需要简单的逻辑判断就找到恢复方案了。要加密,你就搞个牛点儿的加密,要么就不加密算了,直接使用 mp4 ,方便大家引用或是下载视频。在这种跨界的年代里面,大家要相互支持,相互方便。

我创建了自己的微店,时刻欢迎你

过去的一年,在健康及养生,以及身心灵方面注入了我的新心血。绕了大半个地球,逛了大半中华,见识了无数的美好,体验了众多的神奇……请原谅我无法一一为大家介绍,于是精心挑选了三,几样超级便携,即有着非凡效果的精品,摆在我的小店里;当然还有我的大大的爱,奉献给大家。祝福大家健康幸福🐠🌻☀️

欢迎大家光临我的 “已发货” 微店:http://weidian.com/?userid=899442576 。店内摆有用于铁打损伤的太赫兹贴,还有女神们天天在用的超级干净的低频共振的生命能量水,当然少不了我在身心灵方面学到的知识,玛雅智慧等,包括灵魂潜意识唤醒文。放马过来吧,刷你滴卡。

如何联系我 How to contact me

这里公布一下本人的联系方式,看留言,妙是有一些朋友想联系上我,好久没有来更新网站,真是不好意思。如果你是华人,那么很方便使用QQ、微信找到我,统一号码:1272000,自然有QQ,就会有QQ邮箱:1272000 at qq.com。当然还可以直接打电话给我,请说中文、奥语、英语,手机号是 1313 2914 138。

Here are my contact information, in case some one wants to catch me up. If you do have QQ number or we-chat number, pls add 1272000, this is my number for both QQ & we-chat. Also you can send me an email to: airflypan at live.com. Of course, you can just give me a call on this number +86 1313 2914 138. That’s all, thank you!

iOS Location Service 定位服务

Core Location是iOS SDK中一个提供设备位置的框架。可以使用三种技术来获取位置:GPS、蜂窝或WiFi。在这些技术中,GPS最为精准,如果有GPS硬件,Core Location将优先使用它。如果设备没有GPS硬件(如WiFi iPad)或使用GPS获取当前位置时失败,Core Location将退而求其次,选择使用蜂窝或WiFi。

Core Location的大多数功能是由位置管理器(CLLocationManager)提供的,可以使用位置管理器来指定位置更新的频率和精度,以及开始和停止接收这些更新。

要使用位置管理器,必须首先将框架Core Location加入到项目中,再导入其接口文件:

#import <CoreLocation/CoreLocation.h>

接下来,需要分配并初始化一个位置管理器实例、指定将接收位置更新的委托并启动更新:

CLLocationManager *locManager = [[CLLocationManager alloc] init];
locManager.delegate = self;
[locManager startUpdatingLocation];
//[locManager stopUpdatingLocation];

位置管理器委托(CLLocationManagerDelegate)有两个与位置相关的方法:

- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
{
    
}

- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error
{
    
}

第一个方法处理定位成功,manager参数表示位置管理器实例;locations为一个数组,是位置变化的集合,它按照时间变化的顺序存放。如果想获得设备的当前位置,只需要访问数组的最后一个元素即可。集合中每个对象类型是CLLocation,它包含以下属性:

coordinate — 坐标。一个封装了经度和纬度的结构体。

altitude — 海拔高度。正数表示在海平面之上,而负数表示在海平面之下。

horizontalAccuracy — 位置的精度(半径)。位置精度通过一个圆表示,实际位置可能位于这个圆内的任何地方。这个圆是由coordinate(坐标)和horizontalAccuracy(半径)共同决定的,horizontalAccuracy的值越大,那么定义的圆就越大,因此位置精度就越低。如果horizontalAccuracy的值为负,则表明coordinate的值无效。

verticalAccuracy — 海拔高度的精度。为正值表示海拔高度的误差为对应的米数;为负表示altitude(海拔高度)的值无效。

speed — 速度。该属性是通过比较当前位置和前一个位置,并比较它们之间的时间差异和距离计算得到的。鉴于Core Location更新的频率,speed属性的值不是非常精确,除非移动速度变化很小

- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
{
    CLLocation *curLocation = [locations lastObject];
    
    if(curLocation.horizontalAccuracy > 0)
    {
        NSLog(@"当前位置:%.0f,%.0f +/- %.0f meters",curLocation.coordinate.longitude,
              curLocation.coordinate.latitude,
              curLocation.horizontalAccuracy);
    }
    
    if(curLocation.verticalAccuracy > 0)
    {
        NSLog(@"当前海拔高度:%.0f +/- %.0f meters",curLocation.altitude,curLocation.verticalAccuracy);
    }
}

应用程序开始跟踪用户的位置时,将在屏幕上显示一个是否允许定位的提示框。如果用户禁用定位服务,iOS不会禁止应用程序运行,但位置管理器将生成错误。

第二个方法处理这种定位失败,该方法的参数指出了失败的原因。如果用户禁止应用程序定位,error参数将为kCLErrorDenied;如果Core Location经过努力后无法确认位置,error参数将为kCLErrorLocationUnknown;如果没有可供获取位置的源,error参数将为kCLErrorNetwork。

通常,Core Location将在发生错误后继续尝试确定位置,但如果是用户禁止定位,它就不会这样做;在这种情况下,应使用方法stopUpdatingLocation停止位置管理器。

- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error
{
    if(error.code == kCLErrorLocationUnknown)
    {
        NSLog(@"Currently unable to retrieve location.");
    }
    else if(error.code == kCLErrorNetwork)
    {
        NSLog(@"Network used to retrieve location is unavailable.");
    }
    else if(error.code == kCLErrorDenied)
    {
        NSLog(@"Permission to retrieve location is denied.");
        [manager stopUpdatingLocation];
    }
}

可根据实际情况来指定位置精度。例如,对于只需确定用户在哪个国家的应用程序,没有必要要求Core Location的精度为10米。要指定精度,可在启动位置更新前设置位置管理器的desiredAccuracy。有6个表示不同精度的枚举值

extern const CLLocationAccuracy kCLLocationAccuracyBestForNavigation;
extern const CLLocationAccuracy kCLLocationAccuracyBest;
extern const CLLocationAccuracy kCLLocationAccuracyNearestTenMeters;
extern const CLLocationAccuracy kCLLocationAccuracyHundredMeters;
extern const CLLocationAccuracy kCLLocationAccuracyKilometer;
extern const CLLocationAccuracy kCLLocationAccuracyThreeKilometers;

对位置管理器启动更新后,更新将不断传递给位置管理器委托,直到停止更新。您无法直接控制这些更新的频率,但可使用位置管理器的属性distanceFilter进行间接控制。在启动更新前设置属性distanceFilter,它指定设备(水平或垂直)移动多少米后才将另一个更新发送给委托。下面的代码使用适合跟踪长途跋涉者的设置启动位置管理器:

CLLocationManager *locManager = [[CLLocationManager alloc] init];
locManager.delegate = self;
locManager.desiredAccuracy = kCLLocationAccuracyHundredMeters;
locManager.distanceFilter = 200;
[locManager startUpdatingLocation];

P.s. 定位要求的精度越高、属性distanceFilter的值越小,应用程序的耗电量就越大。

位置管理器有一个headingAvailable属性,它指出设备是否装备了磁性指南针。如果该属性为YES,就可以使用Core Location来获取航向(heading)信息。接收航向更新与接收位置更新极其相似,要开始接收航向更新,可指定位置管理器委托,设置属性headingFilter以指定要以什么样的频率(以航向变化的度数度量)接收更新,并对位置管理器调用方法startUpdatingHeading:

位置管理器委托协议定义了用于接收航向更新的方法。该协议有两个与航向相关的方法:

- (BOOL)locationManagerShouldDisplayHeadingCalibration:(CLLocationManager *)manager
{
    return YES;
}

- (void)locationManager:(CLLocationManager *)manager didUpdateHeading:(CLHeading *)newHeading
{
    
}

第一个方法指定位置管理器是否向用户显示校准提示。该提示将自动旋转设备360°。由于指南针总是自我校准,因此这种提示仅在指南针读数剧烈波动时才有帮助。当设置为YES后,提示可能会分散用户的注意力,或影响用户的当前操作。

第二个方法的参数newHeading是一个CLHeading对象。CLHeading通过一组属性来提供航向读数:magneticHeading和trueHeading。这些值的单位为度,类型为CLLocationDirection,即双精度浮点数。这意味着:

如果航向为0.0,则前进方向为北;

如果航向为90.0,则前进方向为东;

如果航向为180.0,则前进方向为南;

如果航向为270.0,则前进方向为西。

CLHeading对象还包含属性headingAccuracy(精度)、timestamp(读数的测量时间)和description(这种描述更适合写入日志而不是显示给用户)。下面演示了利用这个方法处理航向更新:

- (void)locationManager:(CLLocationManager *)manager didUpdateHeading:(CLHeading *)newHeading
{
    if(newHeading.headingAccuracy >=0)
    {
        NSString *headingDesc = [NSString stringWithFormat:@"%.0f degrees (true), %.0f degrees (magnetic)",newHeading.trueHeading,newHeading.magneticHeading];
        
        NSLog(@"%@",headingDesc);
    }
}

trueHeading和magneticHeading分别表示真实航向和磁性航向。如果位置服务被关闭了,GPS和wifi就只能获取magneticHeading(磁场航向)。只有打开位置服务,才能获取trueHeading(真实航向)。

下面的代码演示了,当存在一个确定了经纬度的地点,当前位置离这个地点的距离及正确航向:

#import "ViewController.h"

#define kDestLongitude 113.12 //精度
#define kDestLatitude 22.23 //纬度
#define kRad2Deg 57.2957795 // 180/π
#define kDeg2Rad 0.0174532925 // π/180

@interface ViewController ()
@property (strong, nonatomic) IBOutlet UILabel *lblMessage;
@property (strong, nonatomic) IBOutlet UIImageView *imgView;
@property (strong, nonatomic) CLLocationManager *locationManager;
@property (strong, nonatomic) CLLocation *recentLocation;

-(double)headingToLocation:(CLLocationCoordinate2D)desired current:(CLLocationCoordinate2D)current;
@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];

    self.locationManager = [[CLLocationManager alloc] init];
    self.locationManager.delegate = self;
    self.locationManager.desiredAccuracy = kCLLocationAccuracyThreeKilometers;
    self.locationManager.distanceFilter = 1609; //1英里≈1609米
    [self.locationManager startUpdatingLocation];
    
    if([CLLocationManager headingAvailable])
    {
        self.locationManager.headingFilter = 10; //10°
        [self.locationManager startUpdatingHeading];
    }
}

/*
 * According to Movable Type Scripts
 * http://mathforum.org/library/drmath/view/55417.html
 *
 *  Javascript:
 *
 * var y = Math.sin(dLon) * Math.cos(lat2);
 * var x = Math.cos(lat1)*Math.sin(lat2) -
 * Math.sin(lat1)*Math.cos(lat2)*Math.cos(dLon);
 * var brng = Math.atan2(y, x).toDeg();
 */
-(double)headingToLocation:(CLLocationCoordinate2D)desired current:(CLLocationCoordinate2D)current
{
    double lat1 = current.latitude*kDeg2Rad;
    double lat2 = desired.latitude*kDeg2Rad;
    double lon1 = current.longitude;
    double lon2 = desired.longitude;
    double dlon = (lon2-lon1)*kDeg2Rad;
    
    double y = sin(dlon)*cos(lat2);
    double x = cos(lat1)*sin(lat2) - sin(lat1)*cos(lat2)*cos(dlon);
    
    double heading=atan2(y,x);
    heading=heading*kRad2Deg;
    heading=heading+360.0;
    heading=fmod(heading,360.0);
    return heading;
}

//处理航向 
- (void)locationManager:(CLLocationManager *)manager didUpdateHeading:(CLHeading *)newHeading
{
    if(self.recentLocation!=nil && newHeading.headingAccuracy>=0)
    {
        CLLocation *destLocation = [[CLLocation alloc] initWithLatitude:kDestLatitude longitude:kDestLongitude];
        
        double course = [self headingToLocation:destLocation.coordinate current:self.recentLocation.coordinate];
        
        double delta = newHeading.trueHeading - course;
        
        if (abs(delta) <= 10)
        {
            self.imgView.image = [UIImage imageNamed:@"up_arrow.png"];
        }
        else
        {
            if (delta > 180)
            {
                self.imgView.image = [UIImage imageNamed:@"right_arrow.png"];
            }
            else if (delta > 0)
            {
                self.imgView.image = [UIImage imageNamed:@"left_arrow.png"];
            }
            else if (delta > -180)
            {
                self.imgView.image = [UIImage imageNamed:@"right_arrow.png"];
            }
            else
            {
                self.imgView.image = [UIImage imageNamed:@"left_arrow.png"];
            }
        }
        self.imgView.hidden = NO;
    }
    else
    {
        self.imgView.hidden = YES;
    }
}

//处理定位成功
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
{
    CLLocation *curLocation = [locations lastObject];
    
    if(curLocation.horizontalAccuracy >= 0)
    {
        self.recentLocation = curLocation;
        
        CLLocation *destLocation = [[CLLocation alloc] initWithLatitude:kDestLatitude longitude:kDestLongitude];
        
        CLLocationDistance distance = [destLocation distanceFromLocation:curLocation];
        
        if(distance<500)
        {
            [self.locationManager stopUpdatingLocation];
            [self.locationManager stopUpdatingHeading];
            self.lblMessage.text = @"您已经到达目的地!";
        }
        else
        {
            self.lblMessage.text = [NSString stringWithFormat:@"距离目的地还有%f米",distance];
        }
    }
}

//处理定位失败
- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error
{
    if(error.code == kCLErrorLocationUnknown)
    {
        NSLog(@"Currently unable to retrieve location.");
    }
    else if(error.code == kCLErrorNetwork)
    {
        NSLog(@"Network used to retrieve location is unavailable.");
    }
    else if(error.code == kCLErrorDenied)
    {
        NSLog(@"Permission to retrieve location is denied.");
        [self.locationManager stopUpdatingLocation];
        self.locationManager = nil;
    }
}

- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}
@end

Apple Watch与iPhone的对话

Watch :  嘿iPhone,我现在的心率是多少?
iPhone:  你等等,我翻一下记录员的笔记。
Watch :   快点,话费贵。
iPhone:  终于翻到最后了,最新记录是3.08跳每秒钟。
Watch :  什么?怎么这么快?
iPhone:  这孩子消消气,你现在心率漂升到220跳每分钟了,小心高血压。
Watch :  不跟你玩了,快没电了。
iPhone:  欢迎再来查阅。