@maccesar/titools 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS-TEMPLATE.md +173 -0
- package/README.md +867 -0
- package/agents/ti-researcher.md +108 -0
- package/bin/titools.js +53 -0
- package/lib/commands/agents.js +126 -0
- package/lib/commands/install.js +188 -0
- package/lib/commands/uninstall.js +215 -0
- package/lib/commands/update.js +159 -0
- package/lib/config.js +119 -0
- package/lib/downloader.js +153 -0
- package/lib/installer.js +253 -0
- package/lib/platform.js +108 -0
- package/lib/symlink.js +142 -0
- package/lib/utils.js +270 -0
- package/package.json +67 -0
- package/skills/alloy-expert/SKILL.md +247 -0
- package/skills/alloy-expert/assets/ControllerAutoCleanup.js +182 -0
- package/skills/alloy-expert/references/alloy-structure.md +381 -0
- package/skills/alloy-expert/references/anti-patterns.md +133 -0
- package/skills/alloy-expert/references/code-conventions.md +469 -0
- package/skills/alloy-expert/references/contracts.md +280 -0
- package/skills/alloy-expert/references/controller-patterns.md +520 -0
- package/skills/alloy-expert/references/error-handling.md +484 -0
- package/skills/alloy-expert/references/examples.md +735 -0
- package/skills/alloy-expert/references/migration-patterns.md +298 -0
- package/skills/alloy-expert/references/patterns.md +448 -0
- package/skills/alloy-expert/references/performance-patterns.md +855 -0
- package/skills/alloy-expert/references/security-patterns.md +847 -0
- package/skills/alloy-expert/references/state-management.md +779 -0
- package/skills/alloy-expert/references/testing.md +872 -0
- package/skills/alloy-guides/SKILL.md +214 -0
- package/skills/alloy-guides/references/CLI_TASKS.md +243 -0
- package/skills/alloy-guides/references/CONCEPTS.md +191 -0
- package/skills/alloy-guides/references/CONTROLLERS.md +298 -0
- package/skills/alloy-guides/references/MODELS.md +1028 -0
- package/skills/alloy-guides/references/PURGETSS.md +56 -0
- package/skills/alloy-guides/references/VIEWS_DYNAMIC.md +242 -0
- package/skills/alloy-guides/references/VIEWS_STYLES.md +388 -0
- package/skills/alloy-guides/references/VIEWS_WITHOUT_CONTROLLERS.md +109 -0
- package/skills/alloy-guides/references/VIEWS_XML.md +558 -0
- package/skills/alloy-guides/references/WIDGETS.md +176 -0
- package/skills/alloy-howtos/SKILL.md +203 -0
- package/skills/alloy-howtos/references/best_practices.md +138 -0
- package/skills/alloy-howtos/references/cli_reference.md +253 -0
- package/skills/alloy-howtos/references/config_files.md +87 -0
- package/skills/alloy-howtos/references/custom_tags.md +147 -0
- package/skills/alloy-howtos/references/debugging_troubleshooting.md +101 -0
- package/skills/alloy-howtos/references/samples.md +167 -0
- package/skills/purgetss/SKILL.md +442 -0
- package/skills/purgetss/assets/purgetss.config.cjs +17 -0
- package/skills/purgetss/references/EXAMPLES.md +247 -0
- package/skills/purgetss/references/animation-system.md +1294 -0
- package/skills/purgetss/references/apply-directive.md +375 -0
- package/skills/purgetss/references/arbitrary-values.md +612 -0
- package/skills/purgetss/references/class-index.md +1350 -0
- package/skills/purgetss/references/cli-commands.md +948 -0
- package/skills/purgetss/references/configurable-properties.md +654 -0
- package/skills/purgetss/references/custom-rules.md +161 -0
- package/skills/purgetss/references/customization-deep-dive.md +722 -0
- package/skills/purgetss/references/dynamic-component-creation.md +489 -0
- package/skills/purgetss/references/grid-layout.md +455 -0
- package/skills/purgetss/references/icon-fonts.md +609 -0
- package/skills/purgetss/references/installation-setup.md +366 -0
- package/skills/purgetss/references/opacity-modifier.md +291 -0
- package/skills/purgetss/references/platform-modifiers.md +479 -0
- package/skills/purgetss/references/smart-mappings.md +42 -0
- package/skills/purgetss/references/titanium-resets.md +359 -0
- package/skills/purgetss/references/ui-ux-design.md +1526 -0
- package/skills/ti-guides/SKILL.md +94 -0
- package/skills/ti-guides/references/advanced-data-and-images.md +19 -0
- package/skills/ti-guides/references/alloy-cli-advanced.md +84 -0
- package/skills/ti-guides/references/alloy-data-mastery.md +29 -0
- package/skills/ti-guides/references/alloy-widgets-and-themes.md +19 -0
- package/skills/ti-guides/references/android-manifest.md +97 -0
- package/skills/ti-guides/references/app-distribution.md +258 -0
- package/skills/ti-guides/references/application-frameworks.md +377 -0
- package/skills/ti-guides/references/cli-reference.md +402 -0
- package/skills/ti-guides/references/coding-best-practices.md +102 -0
- package/skills/ti-guides/references/commonjs-advanced.md +134 -0
- package/skills/ti-guides/references/hello-world.md +100 -0
- package/skills/ti-guides/references/hyperloop-native-access.md +62 -0
- package/skills/ti-guides/references/javascript-primer.md +411 -0
- package/skills/ti-guides/references/reserved-words.md +36 -0
- package/skills/ti-guides/references/resources.md +183 -0
- package/skills/ti-guides/references/style-and-conventions.md +48 -0
- package/skills/ti-guides/references/tiapp-config.md +609 -0
- package/skills/ti-howtos/SKILL.md +174 -0
- package/skills/ti-howtos/references/android-platform-deep-dives.md +658 -0
- package/skills/ti-howtos/references/automation-fastlane-appium.md +95 -0
- package/skills/ti-howtos/references/buffer-codec-streams.md +140 -0
- package/skills/ti-howtos/references/cross-platform-development.md +348 -0
- package/skills/ti-howtos/references/debugging-profiling.md +543 -0
- package/skills/ti-howtos/references/extending-titanium.md +723 -0
- package/skills/ti-howtos/references/google-maps-v2.md +169 -0
- package/skills/ti-howtos/references/ios-map-kit.md +143 -0
- package/skills/ti-howtos/references/ios-platform-deep-dives.md +783 -0
- package/skills/ti-howtos/references/local-data-sources.md +301 -0
- package/skills/ti-howtos/references/location-and-maps.md +252 -0
- package/skills/ti-howtos/references/media-apis.md +210 -0
- package/skills/ti-howtos/references/notification-services.md +599 -0
- package/skills/ti-howtos/references/remote-data-sources.md +349 -0
- package/skills/ti-howtos/references/tutorials.md +502 -0
- package/skills/ti-howtos/references/using-modules.md +237 -0
- package/skills/ti-howtos/references/web-content-integration.md +307 -0
- package/skills/ti-howtos/references/webpack-build-pipeline.md +78 -0
- package/skills/ti-ui/SKILL.md +179 -0
- package/skills/ti-ui/references/accessibility-deep-dive.md +242 -0
- package/skills/ti-ui/references/animation-and-matrices.md +599 -0
- package/skills/ti-ui/references/application-structures.md +655 -0
- package/skills/ti-ui/references/custom-fonts-styling.md +579 -0
- package/skills/ti-ui/references/event-handling.md +393 -0
- package/skills/ti-ui/references/gestures.md +473 -0
- package/skills/ti-ui/references/icons-and-splash-screens.md +409 -0
- package/skills/ti-ui/references/layouts-and-positioning.md +462 -0
- package/skills/ti-ui/references/listviews-and-performance.md +619 -0
- package/skills/ti-ui/references/orientation.md +362 -0
- package/skills/ti-ui/references/platform-ui-android.md +635 -0
- package/skills/ti-ui/references/platform-ui-ios.md +469 -0
- package/skills/ti-ui/references/scrolling-views.md +252 -0
- package/skills/ti-ui/references/tableviews.md +568 -0
|
@@ -0,0 +1,783 @@
|
|
|
1
|
+
# iOS Platform Deep Dives
|
|
2
|
+
|
|
3
|
+
## Table of Contents
|
|
4
|
+
|
|
5
|
+
- [iOS Platform Deep Dives](#ios-platform-deep-dives)
|
|
6
|
+
- [Table of Contents](#table-of-contents)
|
|
7
|
+
- [1. iOS 17+ Privacy Requirements (Critical)](#1-ios-17-privacy-requirements-critical)
|
|
8
|
+
- [PrivacyInfo.xcprivacy File](#privacyinfoxcprivacy-file)
|
|
9
|
+
- [2. Background Services \& Silent Push](#2-background-services--silent-push)
|
|
10
|
+
- [Overview](#overview)
|
|
11
|
+
- [Silent Push (Background Update)](#silent-push-background-update)
|
|
12
|
+
- [3. iCloud Services \& Backup Control](#3-icloud-services--backup-control)
|
|
13
|
+
- [Disable Individual Backup (Best Practice)](#disable-individual-backup-best-practice)
|
|
14
|
+
- [Recursive Folder Backup Disable](#recursive-folder-backup-disable)
|
|
15
|
+
- [4. WatchKit \& Ti.WatchSession](#4-watchkit--tiwatchsession)
|
|
16
|
+
- [Activate Session](#activate-session)
|
|
17
|
+
- [Send Message (Immediate)](#send-message-immediate)
|
|
18
|
+
- [Receive Data from Watch](#receive-data-from-watch)
|
|
19
|
+
- [5. SiriKit \& Siri Intents](#5-sirikit--siri-intents)
|
|
20
|
+
- [Configuration in tiapp.xml](#configuration-in-tiappxml)
|
|
21
|
+
- [Siri Extensions](#siri-extensions)
|
|
22
|
+
- [6. Spotlight Search (Core Spotlight)](#6-spotlight-search-core-spotlight)
|
|
23
|
+
- [7. Core Motion Module](#7-core-motion-module)
|
|
24
|
+
- [Overview](#overview-1)
|
|
25
|
+
- [Basic Workflow](#basic-workflow)
|
|
26
|
+
- [Coordinate System](#coordinate-system)
|
|
27
|
+
- [Accelerometer](#accelerometer)
|
|
28
|
+
- [Gyroscope](#gyroscope)
|
|
29
|
+
- [Magnetometer](#magnetometer)
|
|
30
|
+
- [Device Motion](#device-motion)
|
|
31
|
+
- [Activity API](#activity-api)
|
|
32
|
+
- [Pedometer](#pedometer)
|
|
33
|
+
- [Core Motion Best Practices](#core-motion-best-practices)
|
|
34
|
+
- [3. Spotlight Search](#3-spotlight-search)
|
|
35
|
+
- [Overview](#overview-2)
|
|
36
|
+
- [Creating Searchable Items](#creating-searchable-items)
|
|
37
|
+
- [Indexing Items](#indexing-items)
|
|
38
|
+
- [Deleting from Index](#deleting-from-index)
|
|
39
|
+
- [Handling Search Results](#handling-search-results)
|
|
40
|
+
- [Best Practices](#best-practices)
|
|
41
|
+
- [8. Handoff User Activities](#8-handoff-user-activities)
|
|
42
|
+
- [Handling Incoming Handoff](#handling-incoming-handoff)
|
|
43
|
+
- [Invalidating Activities](#invalidating-activities)
|
|
44
|
+
- [Declaring Activity Types in tiapp.xml](#declaring-activity-types-in-tiappxml)
|
|
45
|
+
- [5. iCloud Services](#5-icloud-services)
|
|
46
|
+
- [Keychain Storage](#keychain-storage)
|
|
47
|
+
- [Document Picker](#document-picker)
|
|
48
|
+
- [CloudKit](#cloudkit)
|
|
49
|
+
- [6. WatchKit Integration](#6-watchkit-integration)
|
|
50
|
+
- [Overview](#overview-3)
|
|
51
|
+
- [Integration Steps](#integration-steps)
|
|
52
|
+
- [Watch Connectivity](#watch-connectivity)
|
|
53
|
+
- [7. SiriKit Integration](#7-sirikit-integration)
|
|
54
|
+
- [Overview](#overview-4)
|
|
55
|
+
- [Supported Intents (iOS 10+)](#supported-intents-ios-10)
|
|
56
|
+
- [Implementation](#implementation)
|
|
57
|
+
- [Voice Shortcuts (iOS 12+)](#voice-shortcuts-ios-12)
|
|
58
|
+
- [8. Additional iOS Features](#8-additional-ios-features)
|
|
59
|
+
- [3D Touch (Force Touch)](#3d-touch-force-touch)
|
|
60
|
+
- [Haptic Feedback](#haptic-feedback)
|
|
61
|
+
- [Document Interaction](#document-interaction)
|
|
62
|
+
- [Best Practices Summary](#best-practices-summary)
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## 1. iOS 17+ Privacy Requirements (Critical)
|
|
67
|
+
Apple requires declaring the use of certain APIs to prevent "fingerprinting".
|
|
68
|
+
|
|
69
|
+
### PrivacyInfo.xcprivacy File
|
|
70
|
+
Create this file in `app/assets/iphone/` (Alloy) or `Resources/iphone/` (Classic):
|
|
71
|
+
```xml
|
|
72
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
73
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
74
|
+
<plist version="1.0">
|
|
75
|
+
<dict>
|
|
76
|
+
<key>NSPrivacyAccessedAPITypes</key>
|
|
77
|
+
<array>
|
|
78
|
+
<dict>
|
|
79
|
+
<key>NSPrivacyAccessedAPIType</key>
|
|
80
|
+
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
|
|
81
|
+
<key>NSPrivacyAccessedAPITypeReasons</key>
|
|
82
|
+
<array><string>AC6B.1</string></array>
|
|
83
|
+
</dict>
|
|
84
|
+
</array>
|
|
85
|
+
</dict>
|
|
86
|
+
</plist>
|
|
87
|
+
```
|
|
88
|
+
*Common categories: `UserDefaults` (Ti.App.Properties), `FileTimestamp` (file.createdAt), `SystemBootTime`.*
|
|
89
|
+
|
|
90
|
+
## 2. Background Services & Silent Push
|
|
91
|
+
|
|
92
|
+
### Overview
|
|
93
|
+
iOS allows limited background execution. For large downloads, use the `com.titaniumsdk.urlSession` module.
|
|
94
|
+
|
|
95
|
+
### Silent Push (Background Update)
|
|
96
|
+
Allows waking up the app to download content without showing a notification to the user.
|
|
97
|
+
|
|
98
|
+
**tiapp.xml**:
|
|
99
|
+
```xml
|
|
100
|
+
<key>UIBackgroundModes</key>
|
|
101
|
+
<array>
|
|
102
|
+
<string>remote-notification</string>
|
|
103
|
+
</array>
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
**app.js**:
|
|
107
|
+
```javascript
|
|
108
|
+
Ti.App.iOS.addEventListener('silentpush', (e) => {
|
|
109
|
+
// Start download or update
|
|
110
|
+
Ti.API.info(`Data received: ${JSON.stringify(e)}`);
|
|
111
|
+
|
|
112
|
+
// Mandatory to call upon completion (max 30 seconds)
|
|
113
|
+
Ti.App.iOS.endBackgroundHandler(e.handlerId);
|
|
114
|
+
});
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## 3. iCloud Services & Backup Control
|
|
118
|
+
|
|
119
|
+
### Disable Individual Backup (Best Practice)
|
|
120
|
+
Apple rejects apps that upload unnecessary data to iCloud. Disable backup for temporary or recreatable files.
|
|
121
|
+
|
|
122
|
+
```javascript
|
|
123
|
+
const file = Ti.Filesystem.getFile(Ti.Filesystem.applicationDataDirectory, 'cache.dat');
|
|
124
|
+
file.remoteBackup = false; // Prevents upload to iCloud/iTunes
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Recursive Folder Backup Disable
|
|
128
|
+
```javascript
|
|
129
|
+
function disableiCloudBackup(folder) {
|
|
130
|
+
const dir = Ti.Filesystem.getFile(folder);
|
|
131
|
+
const files = dir.getDirectoryListing();
|
|
132
|
+
files.forEach((name) => {
|
|
133
|
+
const f = Ti.Filesystem.getFile(folder, name);
|
|
134
|
+
f.remoteBackup = false;
|
|
135
|
+
if (f.isDirectory()) disableiCloudBackup(f.nativePath);
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## 4. WatchKit & Ti.WatchSession
|
|
141
|
+
|
|
142
|
+
For watchOS 2+, use `Ti.WatchSession` for bidirectional communication.
|
|
143
|
+
|
|
144
|
+
### Activate Session
|
|
145
|
+
```javascript
|
|
146
|
+
if (Ti.WatchSession.isSupported) {
|
|
147
|
+
Ti.WatchSession.activateSession();
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### Send Message (Immediate)
|
|
152
|
+
```javascript
|
|
153
|
+
if (Ti.WatchSession.isReachable) {
|
|
154
|
+
Ti.WatchSession.sendMessage({
|
|
155
|
+
orderId: '123',
|
|
156
|
+
status: 'shipped'
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### Receive Data from Watch
|
|
162
|
+
```javascript
|
|
163
|
+
Ti.WatchSession.addEventListener('receivemessage', (e) => {
|
|
164
|
+
Ti.API.info(`Message from Watch: ${e.message}`);
|
|
165
|
+
});
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## 5. SiriKit & Siri Intents
|
|
169
|
+
|
|
170
|
+
Allows your app to respond to Siri voice commands (Messaging, Payments, Workouts).
|
|
171
|
+
|
|
172
|
+
### Configuration in tiapp.xml
|
|
173
|
+
```xml
|
|
174
|
+
<key>NSSiriUsageDescription</key>
|
|
175
|
+
<string>Siri will use your voice to send messages in this app.</string>
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### Siri Extensions
|
|
179
|
+
Requires creating an **Intents Extension** in Xcode and adding it to the `extensions/` folder of your project. Then register it in `tiapp.xml`:
|
|
180
|
+
```xml
|
|
181
|
+
<ios>
|
|
182
|
+
<extensions>
|
|
183
|
+
<extension projectPath="extensions/SiriIntent/SiriIntent.xcodeproj">
|
|
184
|
+
<target name="SiriIntent">
|
|
185
|
+
<provisioning-profiles>
|
|
186
|
+
<device>PROVISIONING_PROFILE_UUID</device>
|
|
187
|
+
</provisioning-profiles>
|
|
188
|
+
</target>
|
|
189
|
+
</extension>
|
|
190
|
+
</extensions>
|
|
191
|
+
</ios>
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
## 6. Spotlight Search (Core Spotlight)
|
|
195
|
+
|
|
196
|
+
Indexes your app's content to appear in global iOS search results.
|
|
197
|
+
|
|
198
|
+
```javascript
|
|
199
|
+
const itemAttr = Ti.App.iOS.createSearchableItemAttributeSet({
|
|
200
|
+
itemContentType: Ti.App.iOS.UTTYPE_TEXT,
|
|
201
|
+
title: 'My Article',
|
|
202
|
+
contentDescription: 'Content description...',
|
|
203
|
+
keywords: ['titanium', 'help']
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
const item = Ti.App.iOS.createSearchableItem({
|
|
207
|
+
uniqueIdentifier: 'id-123',
|
|
208
|
+
domainIdentifier: 'articles',
|
|
209
|
+
attributeSet: itemAttr
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
const indexer = Ti.App.iOS.createSearchableIndex();
|
|
213
|
+
indexer.addToDefaultSearchableIndex([item], (e) => {
|
|
214
|
+
if (e.success) Ti.API.info('Indexed!');
|
|
215
|
+
});
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
## 7. Core Motion Module
|
|
219
|
+
|
|
220
|
+
### Overview
|
|
221
|
+
Core Motion provides access to hardware sensors: accelerometer, gyroscope, magnetometer, and more.
|
|
222
|
+
|
|
223
|
+
**Requirements**:
|
|
224
|
+
- Add module to `tiapp.xml`:
|
|
225
|
+
```xml
|
|
226
|
+
<modules>
|
|
227
|
+
<module platform="iphone">ti.coremotion</module>
|
|
228
|
+
</modules>
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
- **Can only test on device** - not simulator
|
|
232
|
+
- Motion Activity permission required for Activity API
|
|
233
|
+
|
|
234
|
+
### Basic Workflow
|
|
235
|
+
|
|
236
|
+
1. Require the module:
|
|
237
|
+
```javascript
|
|
238
|
+
var CoreMotion = require('ti.coremotion');
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
2. Check availability:
|
|
242
|
+
```javascript
|
|
243
|
+
var Accelerometer = CoreMotion.createAccelerometer();
|
|
244
|
+
if (Accelerometer.isAccelerometerAvailable()) {
|
|
245
|
+
// Use accelerometer
|
|
246
|
+
}
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
3. Start updates:
|
|
250
|
+
```javascript
|
|
251
|
+
Accelerometer.setAccelerometerUpdateInterval(1000); // 1 second
|
|
252
|
+
Accelerometer.startAccelerometerUpdates((e) => {
|
|
253
|
+
if (e.success) {
|
|
254
|
+
const data = e.acceleration;
|
|
255
|
+
Ti.API.info(`X: ${data.x} Y: ${data.y} Z: ${data.z}`);
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
4. Stop when done:
|
|
261
|
+
```javascript
|
|
262
|
+
Accelerometer.stopAccelerometerUpdates();
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
### Coordinate System
|
|
266
|
+
Hold device in portrait mode, screen facing you:
|
|
267
|
+
- **X-axis**: Width (positive = right, negative = left)
|
|
268
|
+
- **Y-axis**: Height (positive = up, negative = down)
|
|
269
|
+
- **Z-axis**: Through screen (positive = toward screen, negative = behind)
|
|
270
|
+
|
|
271
|
+
### Accelerometer
|
|
272
|
+
|
|
273
|
+
Measures g-force acceleration along three axes.
|
|
274
|
+
|
|
275
|
+
```javascript
|
|
276
|
+
var CoreMotion = require('ti.coremotion');
|
|
277
|
+
var Accelerometer = CoreMotion.createAccelerometer();
|
|
278
|
+
|
|
279
|
+
if (Accelerometer.isAccelerometerAvailable()) {
|
|
280
|
+
Accelerometer.setAccelerometerUpdateInterval(100);
|
|
281
|
+
Accelerometer.startAccelerometerUpdates(function(e) {
|
|
282
|
+
if (e.success) {
|
|
283
|
+
var data = e.acceleration;
|
|
284
|
+
updateDisplay(data.x, data.y, data.z);
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
**Use for**: Shake detection, device orientation, movement detection.
|
|
291
|
+
|
|
292
|
+
### Gyroscope
|
|
293
|
+
|
|
294
|
+
Measures rotational rate along three axes (radians).
|
|
295
|
+
|
|
296
|
+
```javascript
|
|
297
|
+
var Gyroscope = CoreMotion.createGyroscope();
|
|
298
|
+
|
|
299
|
+
if (Gyroscope.isGyroAvailable()) {
|
|
300
|
+
Gyroscope.setGyroUpdateInterval(100);
|
|
301
|
+
Gyroscope.startGyroUpdates(function(e) {
|
|
302
|
+
if (e.success) {
|
|
303
|
+
var data = e.rotationRate;
|
|
304
|
+
Ti.API.info('Rotation: X=' + data.x + ' Y=' + data.y + ' Z=' + data.z);
|
|
305
|
+
}
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
**Use for**: Rotation gestures, 3D motion tracking, enhanced UI.
|
|
311
|
+
|
|
312
|
+
### Magnetometer
|
|
313
|
+
|
|
314
|
+
Measures magnetic field strength (microteslas). Acts as digital compass.
|
|
315
|
+
|
|
316
|
+
```javascript
|
|
317
|
+
var Magnetometer = CoreMotion.createMagnetometer();
|
|
318
|
+
|
|
319
|
+
if (Magnetometer.isMagnetometerAvailable()) {
|
|
320
|
+
Magnetometer.startMagnetometerUpdates();
|
|
321
|
+
// Or poll manually:
|
|
322
|
+
var data = Magnetometer.getMagnetometerData();
|
|
323
|
+
Ti.API.info('Magnetic field: ' + JSON.stringify(data.magneticField));
|
|
324
|
+
}
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
**Use for**: Compass direction, magnetic field detection.
|
|
328
|
+
|
|
329
|
+
### Device Motion
|
|
330
|
+
|
|
331
|
+
Combines accelerometer, gyroscope, magnetometer for attitude and user acceleration.
|
|
332
|
+
|
|
333
|
+
```javascript
|
|
334
|
+
var DeviceMotion = CoreMotion.createDeviceMotion();
|
|
335
|
+
|
|
336
|
+
if (DeviceMotion.isDeviceMotionAvailable()) {
|
|
337
|
+
DeviceMotion.setDeviceMotionUpdateInterval(500);
|
|
338
|
+
|
|
339
|
+
// Check available reference frames
|
|
340
|
+
var frames = DeviceMotion.availableAttitudeReferenceFrames();
|
|
341
|
+
|
|
342
|
+
if (frames & CoreMotion.ATTITUDE_REFERENCE_FRAME_X_TRUE_NORTH_Z_VERTICAL) {
|
|
343
|
+
// Use true north reference
|
|
344
|
+
DeviceMotion.startDeviceMotionUpdatesUsingReferenceFrame(
|
|
345
|
+
{ referenceFrame: CoreMotion.ATTITUDE_REFERENCE_FRAME_X_TRUE_NORTH_Z_VERTICAL },
|
|
346
|
+
(e) => {
|
|
347
|
+
if (e.success) {
|
|
348
|
+
// Attitude: orientation (pitch, roll, yaw)
|
|
349
|
+
const attitude = e.attitude;
|
|
350
|
+
Ti.API.info(`Pitch: ${attitude.pitch} Roll: ${attitude.roll} Yaw: ${attitude.yaw}`);
|
|
351
|
+
|
|
352
|
+
// User acceleration: force applied by user (not gravity)
|
|
353
|
+
const userAccel = e.userAcceleration;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
**Attitude formats**:
|
|
362
|
+
- Pitch/Roll/Yaw (Euler angles)
|
|
363
|
+
- Quaternion (w, x, y, z)
|
|
364
|
+
- Rotation Matrix (m11-m33)
|
|
365
|
+
|
|
366
|
+
**Reference Frames**:
|
|
367
|
+
- `ATTITUDE_REFERENCE_FRAME_X_ARBITRARY_Z_VERTICAL` - Default
|
|
368
|
+
- `ATTITUDE_REFERENCE_FRAME_X_ARBITRARY_CORRECTED_Z_VERTICAL` - Uses magnetometer for yaw
|
|
369
|
+
- `ATTITUDE_REFERENCE_FRAME_X_MAGNETIC_NORTH_Z_VERTICAL` - Magnetic north
|
|
370
|
+
- `ATTITUDE_REFERENCE_FRAME_X_TRUE_NORTH_Z_VERTICAL` - True north (requires location)
|
|
371
|
+
|
|
372
|
+
### Activity API
|
|
373
|
+
|
|
374
|
+
```javascript
|
|
375
|
+
const MotionActivity = CoreMotion.createMotionActivity();
|
|
376
|
+
|
|
377
|
+
MotionActivity.startActivityUpdates((e) => {
|
|
378
|
+
const activity = e.activity;
|
|
379
|
+
|
|
380
|
+
// Check confidence
|
|
381
|
+
if (activity.confidence !== CoreMotion.MOTION_ACTIVITY_CONFIDENCE_LOW) {
|
|
382
|
+
if (activity.walking) {
|
|
383
|
+
Ti.API.info('User is walking');
|
|
384
|
+
} else if (activity.running) {
|
|
385
|
+
Ti.API.info('User is running');
|
|
386
|
+
} else if (activity.automotive) {
|
|
387
|
+
Ti.API.info('User in vehicle');
|
|
388
|
+
} else if (activity.stationary) {
|
|
389
|
+
Ti.API.info('Device stationary');
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
});
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
**Query historical activity**:
|
|
396
|
+
```javascript
|
|
397
|
+
var endDate = new Date();
|
|
398
|
+
var startDate = new Date(endDate.getTime() - 60 * 60 * 1000); // 1 hour ago
|
|
399
|
+
|
|
400
|
+
MotionActivity.queryActivity({
|
|
401
|
+
start: startDate,
|
|
402
|
+
end: endDate
|
|
403
|
+
}, function(e) {
|
|
404
|
+
var activities = e.activities;
|
|
405
|
+
// Process historical data
|
|
406
|
+
});
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
**Requires Motion Activity permission** in tiapp.xml:
|
|
410
|
+
```xml
|
|
411
|
+
<key>NSMotionUsageDescription</key>
|
|
412
|
+
<string>Need motion data for fitness tracking</string>
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
### Pedometer
|
|
416
|
+
|
|
417
|
+
```javascript
|
|
418
|
+
const Pedometer = CoreMotion.createPedometer();
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
if (Pedometer.isStepCountingAvailable()) {
|
|
422
|
+
// Start live updates
|
|
423
|
+
Pedometer.startPedometerUpdates({
|
|
424
|
+
start: new Date(new Date().getTime() - (60 * 60 * 1000)) // From 1 hour ago
|
|
425
|
+
}, (e) => {
|
|
426
|
+
Ti.API.info(`Steps: ${e.numberOfSteps}`);
|
|
427
|
+
Ti.API.info(`Distance: ${e.distance} meters`);
|
|
428
|
+
Ti.API.info(`Floors up: ${e.floorsAscended}`);
|
|
429
|
+
Ti.API.info(`Floors down: ${e.floorsDescended}`);
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// Query historical data
|
|
434
|
+
var endDate = new Date();
|
|
435
|
+
var startDate = new Date(endDate.getTime() - 24 * 60 * 60 * 1000); // 24 hours ago
|
|
436
|
+
|
|
437
|
+
Pedometer.queryPedometerData({
|
|
438
|
+
start: startDate,
|
|
439
|
+
end: endDate
|
|
440
|
+
}, function(e) {
|
|
441
|
+
Ti.API.info('Steps in last 24h: ' + e.numberOfSteps);
|
|
442
|
+
});
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
**Use for**: Fitness apps, step challenges, activity tracking.
|
|
446
|
+
|
|
447
|
+
### Core Motion Best Practices
|
|
448
|
+
|
|
449
|
+
1. **Always check availability** - Not all devices have all sensors
|
|
450
|
+
2. **Set appropriate update intervals** - High frequency = more CPU/battery
|
|
451
|
+
3. **Stop updates when not needed** - Conserve battery
|
|
452
|
+
4. **Handle errors gracefully** - Sensor may fail or be unavailable
|
|
453
|
+
5. **Test on physical device** - Sensors don't work in simulator
|
|
454
|
+
|
|
455
|
+
## 3. Spotlight Search
|
|
456
|
+
|
|
457
|
+
### Overview
|
|
458
|
+
Spotlight Search allows iOS to index your app's content, making it searchable from the home screen.
|
|
459
|
+
|
|
460
|
+
### Creating Searchable Items
|
|
461
|
+
|
|
462
|
+
```javascript
|
|
463
|
+
var SearchableItem = Ti.App.iOS.SearchableItem;
|
|
464
|
+
|
|
465
|
+
var item1 = SearchableItem.createSearchableItem({
|
|
466
|
+
uniqueIdentifier: 'article-123',
|
|
467
|
+
domainIdentifier: 'articles',
|
|
468
|
+
title: 'Titanium SDK Guide',
|
|
469
|
+
contentDescription: 'Complete guide to Titanium SDK development',
|
|
470
|
+
thumbnail: articleThumbnailImage, // Ti.Blob
|
|
471
|
+
keywords: ['titanium', 'mobile', 'javascript', 'ios', 'android']
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
var item2 = SearchableItem.createSearchableItem({
|
|
475
|
+
uniqueIdentifier: 'product-456',
|
|
476
|
+
domainIdentifier: 'products',
|
|
477
|
+
title: 'Product Name',
|
|
478
|
+
contentDescription: 'Product description here',
|
|
479
|
+
keywords: ['product', 'shopping']
|
|
480
|
+
});
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
### Indexing Items
|
|
484
|
+
|
|
485
|
+
```javascript
|
|
486
|
+
var SearchableIndex = Ti.App.iOS.SearchableIndex;
|
|
487
|
+
|
|
488
|
+
SearchableIndex.indexSearchableItems([item1, item2], function(e) {
|
|
489
|
+
if (e.success) {
|
|
490
|
+
Ti.API.info('Indexing successful');
|
|
491
|
+
} else {
|
|
492
|
+
Ti.API.error('Indexing failed: ' + e.error);
|
|
493
|
+
}
|
|
494
|
+
});
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
### Deleting from Index
|
|
498
|
+
|
|
499
|
+
```javascript
|
|
500
|
+
// Delete specific items
|
|
501
|
+
SearchableIndex.deleteSearchableItemsWithIdentifiers(['article-123'], function(e) {
|
|
502
|
+
if (e.success) {
|
|
503
|
+
Ti.API.info('Deleted successfully');
|
|
504
|
+
}
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
// Delete all items in domain
|
|
508
|
+
SearchableIndex.deleteSearchableItemsWithDomainIdentifiers(['articles'], function(e) {
|
|
509
|
+
if (e.success) {
|
|
510
|
+
Ti.API.info('Deleted all articles');
|
|
511
|
+
}
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
// Delete all
|
|
515
|
+
SearchableIndex.deleteAllSearchableItems(function(e) {
|
|
516
|
+
if (e.success) {
|
|
517
|
+
Ti.API.info('Cleared index');
|
|
518
|
+
}
|
|
519
|
+
});
|
|
520
|
+
```
|
|
521
|
+
|
|
522
|
+
### Handling Search Results
|
|
523
|
+
|
|
524
|
+
When user taps a Spotlight result, your app opens with the `continueactivity` event:
|
|
525
|
+
|
|
526
|
+
```javascript
|
|
527
|
+
Ti.App.iOS.addEventListener('continueactivity', function(e) {
|
|
528
|
+
if (e.activityType === 'com.apple.core SpotlightQuery') {
|
|
529
|
+
var identifier = e.userInfo.identifier;
|
|
530
|
+
var domain = e.userInfo.domainIdentifier;
|
|
531
|
+
|
|
532
|
+
Ti.API.info('Spotlight search selected: ' + identifier);
|
|
533
|
+
|
|
534
|
+
// Navigate to appropriate content
|
|
535
|
+
if (domain === 'articles') {
|
|
536
|
+
openArticle(identifier);
|
|
537
|
+
} else if (domain === 'products') {
|
|
538
|
+
openProduct(identifier);
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
});
|
|
542
|
+
```
|
|
543
|
+
|
|
544
|
+
**Important**: Use `NSUserActivityTypes` in tiapp.xml to declare supported activity types.
|
|
545
|
+
|
|
546
|
+
### Best Practices
|
|
547
|
+
|
|
548
|
+
1. **Index relevant content** - Don't index everything
|
|
549
|
+
2. **Use meaningful keywords** - Help users find content
|
|
550
|
+
3. **Provide thumbnails** - Improve visual recognition
|
|
551
|
+
4. **Keep index updated** - Re-index when content changes
|
|
552
|
+
5. **Delete obsolete items** - Don't clutter search results
|
|
553
|
+
|
|
554
|
+
## 8. Handoff User Activities
|
|
555
|
+
|
|
556
|
+
```javascript
|
|
557
|
+
const UserActivity = Ti.App.iOS.createUserActivity({
|
|
558
|
+
activityType: 'com.myapp.reading-article',
|
|
559
|
+
title: 'Reading: Titanium SDK Guide',
|
|
560
|
+
userInfo: {
|
|
561
|
+
articleId: '123',
|
|
562
|
+
scrollPosition: 450
|
|
563
|
+
},
|
|
564
|
+
webpageURL: 'https://myapp.com/articles/123' // For web fallback
|
|
565
|
+
});
|
|
566
|
+
|
|
567
|
+
// Mark as current activity
|
|
568
|
+
UserActivity.becomeCurrent();
|
|
569
|
+
```
|
|
570
|
+
|
|
571
|
+
### Handling Incoming Handoff
|
|
572
|
+
|
|
573
|
+
```javascript
|
|
574
|
+
Ti.App.iOS.addEventListener('continueactivity', function(e) {
|
|
575
|
+
if (e.activityType === 'com.myapp.reading-article') {
|
|
576
|
+
var userInfo = e.userInfo;
|
|
577
|
+
|
|
578
|
+
// Restore activity state
|
|
579
|
+
openArticle(userInfo.articleId, {
|
|
580
|
+
scrollTo: userInfo.scrollPosition
|
|
581
|
+
});
|
|
582
|
+
}
|
|
583
|
+
});
|
|
584
|
+
```
|
|
585
|
+
|
|
586
|
+
### Invalidating Activities
|
|
587
|
+
|
|
588
|
+
```javascript
|
|
589
|
+
// When user closes article
|
|
590
|
+
UserActivity.invalidate();
|
|
591
|
+
```
|
|
592
|
+
|
|
593
|
+
### Declaring Activity Types in tiapp.xml
|
|
594
|
+
|
|
595
|
+
```xml
|
|
596
|
+
<key>NSUserActivityTypes</key>
|
|
597
|
+
<array>
|
|
598
|
+
<string>com.myapp.reading-article</string>
|
|
599
|
+
<string>com.myapp.editing-document</string>
|
|
600
|
+
<string>com.myapp.viewing-product</string>
|
|
601
|
+
</array>
|
|
602
|
+
```
|
|
603
|
+
|
|
604
|
+
## 5. iCloud Services
|
|
605
|
+
|
|
606
|
+
### Keychain Storage
|
|
607
|
+
|
|
608
|
+
Securely sync small pieces of data (passwords, tokens) across user's devices.
|
|
609
|
+
|
|
610
|
+
```javascript
|
|
611
|
+
// Store value
|
|
612
|
+
Ti.App.iOS.setKeychainItem('username', 'john@example.com');
|
|
613
|
+
Ti.App.iOS.setKeychainItem('authToken', 'abc123token');
|
|
614
|
+
|
|
615
|
+
// Retrieve value
|
|
616
|
+
var username = Ti.App.iOS.getKeychainItem('username');
|
|
617
|
+
|
|
618
|
+
// Remove value
|
|
619
|
+
Ti.App.iOS.removeKeychainItem('authToken');
|
|
620
|
+
```
|
|
621
|
+
|
|
622
|
+
**Use for**: Syncing user credentials, preferences, small data.
|
|
623
|
+
|
|
624
|
+
**Requires** iCloud capability enabled in Xcode project.
|
|
625
|
+
|
|
626
|
+
### Document Picker
|
|
627
|
+
|
|
628
|
+
```javascript
|
|
629
|
+
const DocumentPicker = Ti.UI.iOS.createDocumentPicker({
|
|
630
|
+
mode: Ti.UI.iOS.DOCUMENT_PICKER_MODE_OPEN
|
|
631
|
+
});
|
|
632
|
+
|
|
633
|
+
DocumentPicker.addEventListener('select', (e) => {
|
|
634
|
+
const url = e.url; // File URL
|
|
635
|
+
Ti.API.info(`Selected: ${url}`);
|
|
636
|
+
|
|
637
|
+
// Read file
|
|
638
|
+
const file = Ti.Filesystem.getFile(url);
|
|
639
|
+
const contents = file.read();
|
|
640
|
+
});
|
|
641
|
+
|
|
642
|
+
DocumentPicker.addEventListener('cancel', function(e) {
|
|
643
|
+
Ti.API.info('Document picker canceled');
|
|
644
|
+
});
|
|
645
|
+
|
|
646
|
+
// Show picker
|
|
647
|
+
DocumentPicker.show();
|
|
648
|
+
```
|
|
649
|
+
|
|
650
|
+
**Modes**:
|
|
651
|
+
- `DOCUMENT_PICKER_MODE_OPEN` - Open existing document
|
|
652
|
+
- `DOCUMENT_PICKER_MODE_EXPORT` - Export to document provider
|
|
653
|
+
- `DOCUMENT_PICKER_MODE_IMPORT` - Import copy of document
|
|
654
|
+
- `DOCUMENT_PICKER_MODE_MOVE` - Move document to app's sandbox
|
|
655
|
+
|
|
656
|
+
### CloudKit
|
|
657
|
+
|
|
658
|
+
For more complex iCloud data sync, consider using CloudKit modules or Hyperloop.
|
|
659
|
+
|
|
660
|
+
## 6. WatchKit Integration
|
|
661
|
+
|
|
662
|
+
### Overview
|
|
663
|
+
Integrate Apple Watch apps built in Xcode with Titanium app.
|
|
664
|
+
|
|
665
|
+
### Integration Steps
|
|
666
|
+
|
|
667
|
+
1. **Create WatchKit app** in Xcode
|
|
668
|
+
2. **Add App Group** to both iOS and Watch targets
|
|
669
|
+
3. **Use App Group** for shared data:
|
|
670
|
+
|
|
671
|
+
```javascript
|
|
672
|
+
// In Titanium app, use App Group container
|
|
673
|
+
var container = 'group.com.myapp.shared';
|
|
674
|
+
var sharedDefaults = Ti.App.iOS.createUserDefaults({ suiteName: container });
|
|
675
|
+
|
|
676
|
+
sharedDefaults.setString('watchData', JSON.stringify(data));
|
|
677
|
+
```
|
|
678
|
+
|
|
679
|
+
4. **Watch app reads shared data** using same App Group container
|
|
680
|
+
|
|
681
|
+
### Watch Connectivity
|
|
682
|
+
|
|
683
|
+
For bidirectional communication, use Watch Connectivity framework via Hyperloop or modules.
|
|
684
|
+
|
|
685
|
+
## 7. SiriKit Integration
|
|
686
|
+
|
|
687
|
+
### Overview
|
|
688
|
+
SiriKit allows your app to handle specific intents via Siri voice commands.
|
|
689
|
+
|
|
690
|
+
### Supported Intents (iOS 10+)
|
|
691
|
+
- Messaging (send/read messages)
|
|
692
|
+
- Payments (send/request money)
|
|
693
|
+
- Workouts (start/stop/pause/resume)
|
|
694
|
+
- Rides (book ride)
|
|
695
|
+
- Photo (search/send photos)
|
|
696
|
+
- VoIP calling
|
|
697
|
+
|
|
698
|
+
### Implementation
|
|
699
|
+
|
|
700
|
+
Requires native module or Hyperloop to implement Intent Extension.
|
|
701
|
+
|
|
702
|
+
**Basic steps**:
|
|
703
|
+
1. Define supported intents in tiapp.xml
|
|
704
|
+
2. Create Intent Extension in Xcode
|
|
705
|
+
3. Handle intents in extension code
|
|
706
|
+
4. Provide UI for Siri interaction
|
|
707
|
+
|
|
708
|
+
### Voice Shortcuts (iOS 12+)
|
|
709
|
+
|
|
710
|
+
Add custom voice shortcuts for app actions:
|
|
711
|
+
|
|
712
|
+
```javascript
|
|
713
|
+
var INVoiceShortcut = Ti.UI.iOS.createINVoiceShortcut({
|
|
714
|
+
phrase: 'Open My Article',
|
|
715
|
+
activityType: 'com.myapp.reading-article',
|
|
716
|
+
userInfo: { articleId: '123' }
|
|
717
|
+
});
|
|
718
|
+
|
|
719
|
+
// Present to user for recording
|
|
720
|
+
INVoiceShortcut.present();
|
|
721
|
+
```
|
|
722
|
+
|
|
723
|
+
## 8. Additional iOS Features
|
|
724
|
+
|
|
725
|
+
### 3D Touch (Force Touch)
|
|
726
|
+
|
|
727
|
+
```javascript
|
|
728
|
+
// Check for 3D Touch support
|
|
729
|
+
if (Ti.Platform.forceTouchSupported) {
|
|
730
|
+
view.addEventListener('touchstart', (e) => {
|
|
731
|
+
const force = e.force || 0;
|
|
732
|
+
const maxForce = e.maximumForce || 1;
|
|
733
|
+
|
|
734
|
+
if (force / maxForce > 0.75) {
|
|
735
|
+
// Deep press
|
|
736
|
+
showQuickActions();
|
|
737
|
+
}
|
|
738
|
+
});
|
|
739
|
+
}
|
|
740
|
+
```
|
|
741
|
+
|
|
742
|
+
### Haptic Feedback
|
|
743
|
+
|
|
744
|
+
```javascript
|
|
745
|
+
// Generate haptic feedback
|
|
746
|
+
const generator = Ti.UI.iOS.createHapticFeedbackGenerator();
|
|
747
|
+
|
|
748
|
+
// Impact feedback (light, medium, heavy)
|
|
749
|
+
generator.impactOccurred(Ti.UI.iOS.HAPTIC_FEEDBACK_STYLE_MEDIUM);
|
|
750
|
+
|
|
751
|
+
// Selection feedback
|
|
752
|
+
generator.selectionChanged();
|
|
753
|
+
|
|
754
|
+
// Notification feedback (success, warning, error)
|
|
755
|
+
generator.notificationOccurred(Ti.UI.iOS.HAPTIC_FEEDBACK_TYPE_SUCCESS);
|
|
756
|
+
```
|
|
757
|
+
|
|
758
|
+
### Document Interaction
|
|
759
|
+
|
|
760
|
+
Open documents in other apps:
|
|
761
|
+
|
|
762
|
+
```javascript
|
|
763
|
+
var docController = Ti.UI.iOS.createDocumentViewer({
|
|
764
|
+
url: file.nativePath
|
|
765
|
+
});
|
|
766
|
+
|
|
767
|
+
docController.addEventListener('complete', function(e) {
|
|
768
|
+
Ti.API.info('Document interaction complete');
|
|
769
|
+
});
|
|
770
|
+
|
|
771
|
+
win.add(docController);
|
|
772
|
+
```
|
|
773
|
+
|
|
774
|
+
## Best Practices Summary
|
|
775
|
+
|
|
776
|
+
1. **Background Services**: Use sparingly, stop when done
|
|
777
|
+
2. **Core Motion**: Always check availability, test on device
|
|
778
|
+
3. **Spotlight**: Index content as it changes
|
|
779
|
+
4. **Handoff**: Provide web URL fallback
|
|
780
|
+
5. **iCloud**: Use for small synced data, not large files
|
|
781
|
+
6. **SiriKit**: Requires native extension, use only if beneficial
|
|
782
|
+
7. **Permissions**: Always include usage descriptions in tiapp.xml
|
|
783
|
+
8. **Testing**: Many features require physical device testing
|