Skip to content

Commit 99e75cd

Browse files
committed
UnicornChat: Version 1.0, 2016-10-27
First release Sample code from the "Extending Your Apps with SiriKit” WWDC 2016 session.
1 parent 7070c9b commit 99e75cd

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+1823
-0
lines changed

UnicornChat/LICENSE.txt

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
Sample code project: UnicornChat: Extending Your Apps with SiriKit
2+
Version: 1.0
3+
4+
IMPORTANT: This Apple software is supplied to you by Apple
5+
Inc. ("Apple") in consideration of your agreement to the following
6+
terms, and your use, installation, modification or redistribution of
7+
this Apple software constitutes acceptance of these terms. If you do
8+
not agree with these terms, please do not use, install, modify or
9+
redistribute this Apple software.
10+
11+
In consideration of your agreement to abide by the following terms, and
12+
subject to these terms, Apple grants you a personal, non-exclusive
13+
license, under Apple's copyrights in this original Apple software (the
14+
"Apple Software"), to use, reproduce, modify and redistribute the Apple
15+
Software, with or without modifications, in source and/or binary forms;
16+
provided that if you redistribute the Apple Software in its entirety and
17+
without modifications, you must retain this notice and the following
18+
text and disclaimers in all such redistributions of the Apple Software.
19+
Neither the name, trademarks, service marks or logos of Apple Inc. may
20+
be used to endorse or promote products derived from the Apple Software
21+
without specific prior written permission from Apple. Except as
22+
expressly stated in this notice, no other rights or licenses, express or
23+
implied, are granted by Apple herein, including but not limited to any
24+
patent rights that may be infringed by your derivative works or by other
25+
works in which the Apple Software may be incorporated.
26+
27+
The Apple Software is provided by Apple on an "AS IS" basis. APPLE
28+
MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION
29+
THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS
30+
FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND
31+
OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.
32+
33+
IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL
34+
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
35+
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
36+
INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION,
37+
MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED
38+
AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE),
39+
STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE
40+
POSSIBILITY OF SUCH DAMAGE.
41+
42+
Copyright (C) 2016 Apple Inc. All Rights Reserved.

UnicornChat/README.md

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# UnicornChat: Extending Your Apps with SiriKit
2+
3+
UnicornChat is a messaging application for unicorns to chat with each other.
4+
5+
As a sample app, this illustrates how to adopt SiriKit by adding an Intents extension and an Intents UI extension.
6+
7+
## Requirements
8+
9+
### Build
10+
11+
Xcode 8.0 or later; iOS 10.0 SDK or later
12+
13+
### Runtime
14+
15+
iOS 10.0 or later
16+
17+
Copyright (C) 2016 Apple Inc. All rights reserved.

UnicornChat/SiriExtension/Info.plist

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3+
<plist version="1.0">
4+
<dict>
5+
<key>CFBundleDevelopmentRegion</key>
6+
<string>en</string>
7+
<key>CFBundleDisplayName</key>
8+
<string>SiriExtension</string>
9+
<key>CFBundleExecutable</key>
10+
<string>$(EXECUTABLE_NAME)</string>
11+
<key>CFBundleIdentifier</key>
12+
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
13+
<key>CFBundleInfoDictionaryVersion</key>
14+
<string>6.0</string>
15+
<key>CFBundleName</key>
16+
<string>$(PRODUCT_NAME)</string>
17+
<key>CFBundlePackageType</key>
18+
<string>XPC!</string>
19+
<key>CFBundleShortVersionString</key>
20+
<string>1.0</string>
21+
<key>CFBundleSignature</key>
22+
<string>????</string>
23+
<key>CFBundleVersion</key>
24+
<string>1</string>
25+
<key>NSExtension</key>
26+
<dict>
27+
<key>NSExtensionAttributes</key>
28+
<dict>
29+
<key>IntentsRestrictedWhileLocked</key>
30+
<array>
31+
<string>INSendMessageIntent</string>
32+
</array>
33+
<key>IntentsSupported</key>
34+
<array>
35+
<string>INSendMessageIntent</string>
36+
</array>
37+
</dict>
38+
<key>NSExtensionPointIdentifier</key>
39+
<string>com.apple.intents-service</string>
40+
<key>NSExtensionPrincipalClass</key>
41+
<string>$(PRODUCT_MODULE_NAME).UCIntentsHandler</string>
42+
</dict>
43+
</dict>
44+
</plist>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/*
2+
Copyright (C) 2016 Apple Inc. All Rights Reserved.
3+
See LICENSE.txt for this sample’s licensing information
4+
5+
Abstract:
6+
The main entry point to the Intents extension.
7+
*/
8+
9+
import Intents
10+
11+
class UCIntentsHandler: INExtension {
12+
13+
override func handler(for intent: INIntent) -> Any? {
14+
if intent is INSendMessageIntent {
15+
return UCSendMessageIntentHandler()
16+
}
17+
18+
return nil
19+
}
20+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
/*
2+
Copyright (C) 2016 Apple Inc. All Rights Reserved.
3+
See LICENSE.txt for this sample’s licensing information
4+
5+
Abstract:
6+
The handler class for INSendMessageIntent.
7+
*/
8+
9+
import Foundation
10+
import Intents
11+
import UnicornCore
12+
13+
class UCSendMessageIntentHandler: NSObject, INSendMessageIntentHandling {
14+
15+
// MARK: 1. Resolve
16+
func resolveRecipients(forSendMessage intent: INSendMessageIntent, with completion: @escaping ([INPersonResolutionResult]) -> Swift.Void) {
17+
18+
if let recipients = intent.recipients {
19+
var resolutionResults = [INPersonResolutionResult]()
20+
21+
for recipient in recipients {
22+
let matchingContacts = UCAddressBookManager().contacts(matchingName: recipient.displayName)
23+
24+
switch matchingContacts.count {
25+
case 2 ... Int.max:
26+
// We need Siri's help to ask user to pick one from the matches.
27+
let disambiguationOptions: [INPerson] = matchingContacts.map { contact in
28+
return contact.inPerson()
29+
}
30+
31+
resolutionResults += [.disambiguation(with: disambiguationOptions)]
32+
33+
case 1:
34+
let recipientMatched = matchingContacts[0].inPerson()
35+
resolutionResults += [.success(with: recipientMatched)]
36+
37+
case 0:
38+
resolutionResults += [.unsupported()]
39+
40+
default:
41+
break
42+
}
43+
}
44+
45+
completion(resolutionResults)
46+
47+
} else {
48+
// No recipients are provided. We need to prompt for a value.
49+
completion([INPersonResolutionResult.needsValue()])
50+
}
51+
}
52+
53+
func resolveContent(forSendMessage intent: INSendMessageIntent, with completion: @escaping (INStringResolutionResult) -> Swift.Void) {
54+
if let text = intent.content, !text.isEmpty {
55+
completion(INStringResolutionResult.success(with: text))
56+
}
57+
else {
58+
completion(INStringResolutionResult.needsValue())
59+
}
60+
}
61+
62+
// MARK: 2. Confirm
63+
func confirm(sendMessage intent: INSendMessageIntent, completion: @escaping (INSendMessageIntentResponse) -> Swift.Void) {
64+
65+
if UCAccount.shared().hasValidAuthentication {
66+
completion(INSendMessageIntentResponse(code: .success, userActivity: nil))
67+
}
68+
else {
69+
// Creating our own user activity to include error information.
70+
let userActivity = NSUserActivity(activityType: String(describing: INSendMessageIntent.self))
71+
userActivity.userInfo = [NSString(string: "error"):NSString(string: "UserLoggedOut")]
72+
73+
completion(INSendMessageIntentResponse(code: .failureRequiringAppLaunch, userActivity: userActivity))
74+
}
75+
}
76+
77+
// MARK: 3. Handle
78+
func handle(sendMessage intent: INSendMessageIntent, completion: @escaping (INSendMessageIntentResponse) -> Swift.Void) {
79+
if intent.recipients != nil && intent.content != nil {
80+
// Send the message.
81+
let success = UCAccount.shared().sendMessage(intent.content, toRecipients: intent.recipients)
82+
completion(INSendMessageIntentResponse(code: success ? .success : .failure, userActivity: nil))
83+
}
84+
else {
85+
completion(INSendMessageIntentResponse(code: .failure, userActivity: nil))
86+
}
87+
}
88+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
2+
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="11134" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="ObA-dk-sSI">
3+
<dependencies>
4+
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="11106"/>
5+
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
6+
</dependencies>
7+
<scenes>
8+
<!--Intent View Controller-->
9+
<scene sceneID="7MM-of-jgj">
10+
<objects>
11+
<viewController id="ObA-dk-sSI" customClass="IntentViewController" customModuleProvider="target" sceneMemberID="viewController">
12+
<layoutGuides>
13+
<viewControllerLayoutGuide type="top" id="qkL-Od-lgU"/>
14+
<viewControllerLayoutGuide type="bottom" id="n38-gi-rB5"/>
15+
</layoutGuides>
16+
<view key="view" contentMode="scaleToFill" id="zMn-AG-sqS">
17+
<rect key="frame" x="0.0" y="0.0" width="320" height="150"/>
18+
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
19+
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
20+
</view>
21+
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
22+
<size key="freeformSize" width="320" height="150"/>
23+
</viewController>
24+
<placeholder placeholderIdentifier="IBFirstResponder" id="X47-rx-isc" userLabel="First Responder" sceneMemberID="firstResponder"/>
25+
</objects>
26+
</scene>
27+
</scenes>
28+
</document>
+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3+
<plist version="1.0">
4+
<dict>
5+
<key>CFBundleDevelopmentRegion</key>
6+
<string>en</string>
7+
<key>CFBundleDisplayName</key>
8+
<string>SiriUIExtension</string>
9+
<key>CFBundleExecutable</key>
10+
<string>$(EXECUTABLE_NAME)</string>
11+
<key>CFBundleIdentifier</key>
12+
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
13+
<key>CFBundleInfoDictionaryVersion</key>
14+
<string>6.0</string>
15+
<key>CFBundleName</key>
16+
<string>$(PRODUCT_NAME)</string>
17+
<key>CFBundlePackageType</key>
18+
<string>XPC!</string>
19+
<key>CFBundleShortVersionString</key>
20+
<string>1.0</string>
21+
<key>CFBundleVersion</key>
22+
<string>1</string>
23+
<key>NSExtension</key>
24+
<dict>
25+
<key>NSExtensionAttributes</key>
26+
<dict>
27+
<key>IntentsSupported</key>
28+
<array>
29+
<string>INSendMessageIntent</string>
30+
</array>
31+
</dict>
32+
<key>NSExtensionMainStoryboard</key>
33+
<string>MainInterface</string>
34+
<key>NSExtensionPointIdentifier</key>
35+
<string>com.apple.intents-ui-service</string>
36+
</dict>
37+
</dict>
38+
</plist>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/*
2+
Copyright (C) 2016 Apple Inc. All Rights Reserved.
3+
See LICENSE.txt for this sample’s licensing information
4+
5+
Abstract:
6+
The view controller providing a user interface for the intent.
7+
*/
8+
9+
import IntentsUI
10+
import UnicornCore
11+
12+
class IntentViewController: UIViewController, INUIHostedViewControlling, INUIHostedViewSiriProviding {
13+
14+
// MARK: INUIHostedViewControlling
15+
16+
func configure(with interaction: INInteraction!, context: INUIHostedViewContext, completion: ((CGSize) -> Void)!) {
17+
var size: CGSize
18+
19+
// Check if the interaction describes a SendMessageIntent.
20+
if interaction.representsSendMessageIntent {
21+
// If it is, let's set up a view controller.
22+
let chatViewController = UCChatViewController()
23+
chatViewController.messageContent = interaction.messageContent
24+
25+
let contact = UCContact()
26+
contact.name = interaction.recipientName
27+
chatViewController.recipient = contact
28+
29+
switch interaction.intentHandlingStatus {
30+
case .unspecified, .inProgress, .ready, .failure:
31+
chatViewController.isSent = false
32+
33+
case .success, .deferredToApplication:
34+
chatViewController.isSent = true
35+
}
36+
37+
present(chatViewController, animated: false, completion: nil)
38+
39+
size = desiredSize
40+
}
41+
else {
42+
// Otherwise, we'll tell the host to draw us at zero size.
43+
size = CGSize.zero
44+
}
45+
46+
completion(size)
47+
}
48+
49+
var desiredSize: CGSize {
50+
return extensionContext!.hostedViewMaximumAllowedSize
51+
}
52+
53+
var displaysMessage: Bool {
54+
return true
55+
}
56+
}

0 commit comments

Comments
 (0)