@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,469 @@
|
|
|
1
|
+
# iOS UI Components and Conventions
|
|
2
|
+
|
|
3
|
+
## Table of Contents
|
|
4
|
+
|
|
5
|
+
- [iOS UI Components and Conventions](#ios-ui-components-and-conventions)
|
|
6
|
+
- [Table of Contents](#table-of-contents)
|
|
7
|
+
- [1. Overview](#1-overview)
|
|
8
|
+
- [2. iPad-Only UI Components](#2-ipad-only-ui-components)
|
|
9
|
+
- [Popover](#popover)
|
|
10
|
+
- [Popover Events](#popover-events)
|
|
11
|
+
- [SplitWindow](#splitwindow)
|
|
12
|
+
- [SplitWindow in Portrait](#splitwindow-in-portrait)
|
|
13
|
+
- [3. Badges](#3-badges)
|
|
14
|
+
- [App Icon Badge](#app-icon-badge)
|
|
15
|
+
- [Tab Badge](#tab-badge)
|
|
16
|
+
- [Accessing Preferences in App](#accessing-preferences-in-app)
|
|
17
|
+
- [Dynamic Quick Actions](#dynamic-quick-actions)
|
|
18
|
+
- [Handling Quick Actions](#handling-quick-actions)
|
|
19
|
+
- [Peek and Pop](#peek-and-pop)
|
|
20
|
+
- [Enabling Peek and Pop](#enabling-peek-and-pop)
|
|
21
|
+
- [Preview Action Styles](#preview-action-styles)
|
|
22
|
+
- [6. Navigation Bar (iOS)](#6-navigation-bar-ios)
|
|
23
|
+
- [NavigationWindow](#navigationwindow)
|
|
24
|
+
- [Toolbar](#toolbar)
|
|
25
|
+
- [System Buttons](#system-buttons)
|
|
26
|
+
- [7. Tab Bar](#7-tab-bar)
|
|
27
|
+
- [Creating Tab Bar](#creating-tab-bar)
|
|
28
|
+
- [Tab Bar Customization (iOS)](#tab-bar-customization-ios)
|
|
29
|
+
- [8. Activity Indicator](#8-activity-indicator)
|
|
30
|
+
- [iOS-Specific Location](#ios-specific-location)
|
|
31
|
+
- [9. Platform Best Practices](#9-platform-best-practices)
|
|
32
|
+
- [Follow iOS Human Interface Guidelines](#follow-ios-human-interface-guidelines)
|
|
33
|
+
- [iOS-Specific Patterns](#ios-specific-patterns)
|
|
34
|
+
- [10. Common Issues](#10-common-issues)
|
|
35
|
+
- [Settings Not Appearing](#settings-not-appearing)
|
|
36
|
+
- [3D Touch Not Working](#3d-touch-not-working)
|
|
37
|
+
- [SplitWindow Issues](#splitwindow-issues)
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## 1. Overview
|
|
42
|
+
|
|
43
|
+
iOS offers several UI components and conventions that differ from Android. This guide covers iPad-specific controls, app badges, Settings integration, 3D Touch, and iOS-specific navigation patterns.
|
|
44
|
+
|
|
45
|
+
## 2. iPad-Only UI Components
|
|
46
|
+
|
|
47
|
+
### Popover
|
|
48
|
+
|
|
49
|
+
Popovers present content temporarily without taking over the entire screen. Used for menus, options, or detail views.
|
|
50
|
+
|
|
51
|
+
```javascript
|
|
52
|
+
const button = Ti.UI.createButton({
|
|
53
|
+
title: 'Show popover',
|
|
54
|
+
width: 250,
|
|
55
|
+
height: 50,
|
|
56
|
+
top: 30,
|
|
57
|
+
right: 5
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
const popover = Ti.UI.iPad.createPopover({
|
|
61
|
+
width: 300,
|
|
62
|
+
height: 250,
|
|
63
|
+
title: 'Popover Content',
|
|
64
|
+
arrowDirection: Ti.UI.iPad.POPOVER_ARROW_DIRECTION_RIGHT
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// Add content to popover
|
|
68
|
+
const popoverContent = Ti.UI.createView({
|
|
69
|
+
backgroundColor: 'white'
|
|
70
|
+
});
|
|
71
|
+
popover.add(popoverContent);
|
|
72
|
+
|
|
73
|
+
button.addEventListener('click', (e) => {
|
|
74
|
+
popover.show({
|
|
75
|
+
view: button, // Anchor to this view
|
|
76
|
+
animated: true
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
win.add(button);
|
|
81
|
+
```
|
|
82
|
+
...
|
|
83
|
+
### Popover Events
|
|
84
|
+
|
|
85
|
+
```javascript
|
|
86
|
+
popover.addEventListener('hide', (e) => {
|
|
87
|
+
Ti.API.info('Popover hidden');
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
popover.addEventListener('show', (e) => {
|
|
91
|
+
Ti.API.info('Popover shown');
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// Dismiss programmatically
|
|
95
|
+
popover.hide();
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### SplitWindow
|
|
99
|
+
|
|
100
|
+
SplitWindow manages master-detail interface (left pane list, right pane details). iPad only.
|
|
101
|
+
|
|
102
|
+
```javascript
|
|
103
|
+
// Master (left) window
|
|
104
|
+
const masterWin = Ti.UI.createWindow({
|
|
105
|
+
backgroundColor: '#fff',
|
|
106
|
+
title: 'Master'
|
|
107
|
+
});
|
|
108
|
+
masterWin.add(Ti.UI.createLabel({
|
|
109
|
+
text: 'Master View - Select an item',
|
|
110
|
+
font: { fontWeight: 'bold', fontSize: 16 }
|
|
111
|
+
}));
|
|
112
|
+
|
|
113
|
+
// Detail (right) window
|
|
114
|
+
const detailWin = Ti.UI.createWindow({
|
|
115
|
+
backgroundColor: '#dfdfdf',
|
|
116
|
+
title: 'Detail'
|
|
117
|
+
});
|
|
118
|
+
detailWin.add(Ti.UI.createLabel({
|
|
119
|
+
text: 'Detail View - Item details',
|
|
120
|
+
font: { fontWeight: 'bold', fontSize: 16 }
|
|
121
|
+
}));
|
|
122
|
+
|
|
123
|
+
// Create split window
|
|
124
|
+
const splitwin = Ti.UI.iPad.createSplitWindow({
|
|
125
|
+
detailView: detailWin,
|
|
126
|
+
masterView: masterWin,
|
|
127
|
+
orientationModes: [
|
|
128
|
+
Ti.UI.LANDSCAPE_LEFT,
|
|
129
|
+
Ti.UI.LANDSCAPE_RIGHT
|
|
130
|
+
]
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
splitwin.open();
|
|
134
|
+
```
|
|
135
|
+
...
|
|
136
|
+
### SplitWindow in Portrait
|
|
137
|
+
|
|
138
|
+
By default, master view hides in portrait. To show master view:
|
|
139
|
+
|
|
140
|
+
```javascript
|
|
141
|
+
splitwin.addEventListener('visible', (e) => {
|
|
142
|
+
if (e.view === 'master' && !e.visible) {
|
|
143
|
+
// Master is hidden, show it in a popover or navigation
|
|
144
|
+
splitwin.showMasterViewInPopover();
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## 3. Badges
|
|
150
|
+
|
|
151
|
+
### App Icon Badge
|
|
152
|
+
|
|
153
|
+
```javascript
|
|
154
|
+
// Set badge to number
|
|
155
|
+
Ti.UI.iOS.appBadge = 23;
|
|
156
|
+
|
|
157
|
+
// Remove badge (set to null, not 0)
|
|
158
|
+
Ti.UI.iOS.appBadge = null;
|
|
159
|
+
|
|
160
|
+
// Note: Setting to 0 still displays "0"
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Tab Badge
|
|
164
|
+
|
|
165
|
+
```javascript
|
|
166
|
+
const tabGroup = Ti.UI.createTabGroup();
|
|
167
|
+
|
|
168
|
+
const win1 = Ti.UI.createWindow({ title: 'Window 1' });
|
|
169
|
+
|
|
170
|
+
const tab1 = Ti.UI.createTab({
|
|
171
|
+
icon: 'myIcon.png',
|
|
172
|
+
title: 'Tab 1',
|
|
173
|
+
window: win1,
|
|
174
|
+
badge: 10 // Show badge with number
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
// Update badge
|
|
178
|
+
tab1.setBadge(5);
|
|
179
|
+
tab1.badge = 5;
|
|
180
|
+
|
|
181
|
+
// Clear badge
|
|
182
|
+
tab1.setBadge(null);
|
|
183
|
+
tab1.badge = null;
|
|
184
|
+
```
|
|
185
|
+
...
|
|
186
|
+
### Accessing Preferences in App
|
|
187
|
+
|
|
188
|
+
```javascript
|
|
189
|
+
// Must match the "Key" value from Root.plist
|
|
190
|
+
const name = Ti.App.Properties.getString('name_preference');
|
|
191
|
+
const enabled = Ti.App.Properties.getBool('enabled_preference');
|
|
192
|
+
```
|
|
193
|
+
...
|
|
194
|
+
#### Dynamic Quick Actions
|
|
195
|
+
|
|
196
|
+
```javascript
|
|
197
|
+
if (Ti.UI.iOS.forceTouchSupported) {
|
|
198
|
+
const appShortcuts = Ti.UI.iOS.createApplicationShortcuts();
|
|
199
|
+
|
|
200
|
+
appShortcuts.addDynamicShortcut({
|
|
201
|
+
itemtype: 'com.myapp.share',
|
|
202
|
+
title: 'Share Photo',
|
|
203
|
+
subtitle: 'Share last photo',
|
|
204
|
+
icon: Ti.UI.iOS.SHORTCUT_ICON_TYPE_SHARE,
|
|
205
|
+
userInfo: {
|
|
206
|
+
action: 'share_photo',
|
|
207
|
+
photoId: '123'
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
// Remove specific shortcut
|
|
212
|
+
appShortcuts.removeDynamicShortcut('com.myapp.share');
|
|
213
|
+
|
|
214
|
+
// Remove all dynamic shortcuts
|
|
215
|
+
appShortcuts.removeAllDynamicShortcuts();
|
|
216
|
+
}
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
#### Handling Quick Actions
|
|
220
|
+
|
|
221
|
+
```javascript
|
|
222
|
+
Ti.App.iOS.addEventListener('shortcutitemclick', (e) => {
|
|
223
|
+
Ti.API.info(`Shortcut clicked: ${e.itemtype}`);
|
|
224
|
+
|
|
225
|
+
switch(e.itemtype) {
|
|
226
|
+
case 'com.myapp.add':
|
|
227
|
+
addNewItem();
|
|
228
|
+
break;
|
|
229
|
+
case 'com.myapp.recent':
|
|
230
|
+
openRecent();
|
|
231
|
+
break;
|
|
232
|
+
case 'com.myapp.share':
|
|
233
|
+
sharePhoto(e.userInfo.photoId);
|
|
234
|
+
break;
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
### Peek and Pop
|
|
240
|
+
|
|
241
|
+
Peek provides a preview of content, Pop opens it fully.
|
|
242
|
+
|
|
243
|
+
#### Enabling Peek and Pop
|
|
244
|
+
|
|
245
|
+
```javascript
|
|
246
|
+
if (Ti.UI.iOS.forceTouchSupported) {
|
|
247
|
+
|
|
248
|
+
const preview = Alloy.createController('preview').getView();
|
|
249
|
+
const detail = Alloy.createController('detail').getView();
|
|
250
|
+
|
|
251
|
+
const shareAction = Ti.UI.iOS.createPreviewAction({
|
|
252
|
+
title: "Share",
|
|
253
|
+
style: Ti.UI.iOS.PREVIEW_ACTION_STYLE_DEFAULT
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
shareAction.addEventListener('click', (e) => {
|
|
257
|
+
// Handle share action
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
const previewContext = Ti.UI.iOS.createPreviewContext({
|
|
261
|
+
preview: preview,
|
|
262
|
+
contentHeight: 400,
|
|
263
|
+
actions: [shareAction]
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
previewContext.addEventListener('peek', () => {
|
|
267
|
+
Ti.API.info('User started peeking');
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
previewContext.addEventListener('pop', () => {
|
|
271
|
+
Ti.API.info('User popped to full view');
|
|
272
|
+
detail.open();
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
// Attach to view
|
|
276
|
+
$.peekView.previewContext = previewContext;
|
|
277
|
+
}
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
#### Preview Action Styles
|
|
281
|
+
|
|
282
|
+
```javascript
|
|
283
|
+
Ti.UI.iOS.PREVIEW_ACTION_STYLE_DEFAULT
|
|
284
|
+
Ti.UI.iOS.PREVIEW_ACTION_STYLE_SELECTED // Blue background
|
|
285
|
+
Ti.UI.iOS.PREVIEW_ACTION_STYLE_DESTRUCTIVE // Red background
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
## 6. Navigation Bar (iOS)
|
|
289
|
+
|
|
290
|
+
### NavigationWindow
|
|
291
|
+
|
|
292
|
+
```javascript
|
|
293
|
+
const win1 = Ti.UI.createWindow({
|
|
294
|
+
backgroundColor: 'white',
|
|
295
|
+
title: 'First Window'
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
const navWindow = Ti.UI.createNavigationWindow({
|
|
299
|
+
window: win1
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
// Open second window
|
|
303
|
+
const win2 = Ti.UI.createWindow({
|
|
304
|
+
backgroundColor: 'gray',
|
|
305
|
+
title: 'Second Window'
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
navWindow.openWindow(win2, { animated: true });
|
|
309
|
+
|
|
310
|
+
// Close to go back
|
|
311
|
+
win2.close();
|
|
312
|
+
|
|
313
|
+
// Hide navigation bar
|
|
314
|
+
win1.navBarHidden = true;
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
### Toolbar
|
|
318
|
+
|
|
319
|
+
```javascript
|
|
320
|
+
const flexSpace = Ti.UI.createButton({
|
|
321
|
+
systemButton: Ti.UI.iOS.SystemButton.FLEXIBLE_SPACE
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
const refreshButton = Ti.UI.createButton({
|
|
325
|
+
title: 'Refresh'
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
const toolbar = Ti.UI.createToolbar({
|
|
329
|
+
items: [refreshButton, flexSpace],
|
|
330
|
+
bottom: 0
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
win.add(toolbar);
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
### System Buttons
|
|
337
|
+
|
|
338
|
+
```javascript
|
|
339
|
+
Ti.UI.iOS.SystemButton.DONE
|
|
340
|
+
Ti.UI.iOS.SystemButton.CANCEL
|
|
341
|
+
Ti.UI.iOS.SystemButton.EDIT
|
|
342
|
+
Ti.UI.iOS.SystemButton.SAVE
|
|
343
|
+
Ti.UI.iOS.SystemButton.ADD
|
|
344
|
+
Ti.UI.iOS.SystemButton.FLEXIBLE_SPACE
|
|
345
|
+
Ti.UI.iOS.SystemButton.FIXED_SPACE
|
|
346
|
+
// ... and more
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
## 7. Tab Bar
|
|
350
|
+
|
|
351
|
+
### Creating Tab Bar
|
|
352
|
+
|
|
353
|
+
```javascript
|
|
354
|
+
const tabGroup = Ti.UI.createTabGroup();
|
|
355
|
+
|
|
356
|
+
const tab1 = Ti.UI.createTab({
|
|
357
|
+
icon: 'home.png',
|
|
358
|
+
title: 'Home',
|
|
359
|
+
window: Ti.UI.createWindow({ backgroundColor: 'white' })
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
const tab2 = Ti.UI.createTab({
|
|
363
|
+
icon: 'settings.png',
|
|
364
|
+
title: 'Settings',
|
|
365
|
+
window: Ti.UI.createWindow({ backgroundColor: 'gray' })
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
tabGroup.addTab(tab1);
|
|
369
|
+
tabGroup.addTab(tab2);
|
|
370
|
+
|
|
371
|
+
tabGroup.open();
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
### Tab Bar Customization (iOS)
|
|
375
|
+
|
|
376
|
+
```javascript
|
|
377
|
+
tabGroup.setActiveTabIconColor('blue');
|
|
378
|
+
tabGroup.setActiveTabColor('#f0f0f0');
|
|
379
|
+
|
|
380
|
+
// Hide tab bar on specific window
|
|
381
|
+
win1.tabBarHidden = true;
|
|
382
|
+
|
|
383
|
+
// Set tab bar style (deprecated in iOS 13+)
|
|
384
|
+
tabGroup.tabsStyle = Ti.UI.iOS.TABS_STYLE_BOTTOM;
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
## 8. Activity Indicator
|
|
388
|
+
|
|
389
|
+
```javascript
|
|
390
|
+
const activityIndicator = Ti.UI.createActivityIndicator({
|
|
391
|
+
message: 'Loading...',
|
|
392
|
+
color: 'white',
|
|
393
|
+
backgroundColor: '#333',
|
|
394
|
+
font: { fontSize: 16, fontWeight: 'bold' }
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
activityIndicator.show();
|
|
398
|
+
|
|
399
|
+
// Later
|
|
400
|
+
activityIndicator.hide();
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
### iOS-Specific Location
|
|
404
|
+
|
|
405
|
+
```javascript
|
|
406
|
+
const activityIndicator = Ti.UI.createActivityIndicator({
|
|
407
|
+
message: 'Loading...',
|
|
408
|
+
location: Ti.UI.iOS.ACTIVITY_INDICATOR_DIALOG,
|
|
409
|
+
opacity: 0.8
|
|
410
|
+
});
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
## 9. Platform Best Practices
|
|
414
|
+
|
|
415
|
+
### Follow iOS Human Interface Guidelines
|
|
416
|
+
|
|
417
|
+
- Use NavigationWindow for hierarchical navigation
|
|
418
|
+
- Use Tab Bar for top-level categories
|
|
419
|
+
- Present modal views for focused tasks
|
|
420
|
+
- Use Popovers for auxiliary content (iPad)
|
|
421
|
+
- Respect 3D Touch conventions
|
|
422
|
+
|
|
423
|
+
### iOS-Specific Patterns
|
|
424
|
+
|
|
425
|
+
```javascript
|
|
426
|
+
// Use system buttons for standard actions
|
|
427
|
+
const doneButton = Ti.UI.createButton({
|
|
428
|
+
systemButton: Ti.UI.iOS.SystemButton.DONE
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
// Use NavigationWindow animations
|
|
432
|
+
navWindow.openWindow(win2, {
|
|
433
|
+
animated: true,
|
|
434
|
+
transition: Ti.UI.iOS.NavigationWindowTransitionStyle.NONE
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
// Respect safe area (iPhone X+)
|
|
438
|
+
const view = Ti.UI.createView({
|
|
439
|
+
top: Ti.Platform.displayCaps.platformHeight > 800 ? 44 : 20 // Account for notch
|
|
440
|
+
});
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
## 10. Common Issues
|
|
444
|
+
|
|
445
|
+
### Settings Not Appearing
|
|
446
|
+
|
|
447
|
+
**Problem**: Settings not visible in Settings app
|
|
448
|
+
|
|
449
|
+
**Solutions**:
|
|
450
|
+
1. Clean and rebuild project
|
|
451
|
+
2. Check plist syntax
|
|
452
|
+
3. Ensure Settings.bundle is in platform/iphone/
|
|
453
|
+
4. Kill and relaunch Settings app
|
|
454
|
+
|
|
455
|
+
### 3D Touch Not Working
|
|
456
|
+
|
|
457
|
+
**Problem**: 3D Touch features don't respond
|
|
458
|
+
|
|
459
|
+
**Solutions**:
|
|
460
|
+
1. Must test on physical 3D Touch device
|
|
461
|
+
2. Check `Ti.UI.iOS.forceTouchSupported` before using
|
|
462
|
+
3. Ensure iOS 9 or later
|
|
463
|
+
4. Verify `previewContext` is properly attached
|
|
464
|
+
|
|
465
|
+
### SplitWindow Issues
|
|
466
|
+
|
|
467
|
+
**Problem**: Master view not visible in portrait
|
|
468
|
+
|
|
469
|
+
**Solution**: This is default behavior. Use `showMasterViewInPopover()` or handle via NavigationWindow.
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
# Scrolling Views
|
|
2
|
+
|
|
3
|
+
## Table of Contents
|
|
4
|
+
|
|
5
|
+
- [Scrolling Views](#scrolling-views)
|
|
6
|
+
- [Table of Contents](#table-of-contents)
|
|
7
|
+
- [1. ScrollView vs ScrollableView](#1-scrollview-vs-scrollableview)
|
|
8
|
+
- [2. ScrollView](#2-scrollview)
|
|
9
|
+
- [Creating a ScrollView](#creating-a-scrollview)
|
|
10
|
+
- [ScrollView Events](#scrollview-events)
|
|
11
|
+
- [Android ScrollView Direction](#android-scrollview-direction)
|
|
12
|
+
- [3. ScrollableView](#3-scrollableview)
|
|
13
|
+
- [Creating a ScrollableView](#creating-a-scrollableview)
|
|
14
|
+
- [ScrollableView Events](#scrollableview-events)
|
|
15
|
+
- [Image Gallery](#image-gallery)
|
|
16
|
+
- [Onboarding Wizard](#onboarding-wizard)
|
|
17
|
+
- [Long Form Content](#long-form-content)
|
|
18
|
+
- [Scrollable Form](#scrollable-form)
|
|
19
|
+
- [ScrollableView](#scrollableview)
|
|
20
|
+
- [8. Detecting Scroll Position](#8-detecting-scroll-position)
|
|
21
|
+
- [Best Practices](#best-practices)
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## 1. ScrollView vs ScrollableView
|
|
26
|
+
|
|
27
|
+
| Feature | ScrollView | ScrollableView |
|
|
28
|
+
| ---------------- | ------------------------------ | -------------------------------- |
|
|
29
|
+
| Purpose | Scrollable area of content | Paging through multiple views |
|
|
30
|
+
| Scroll Direction | Vertical and/or horizontal | Horizontal only |
|
|
31
|
+
| Content Size | `contentHeight`/`contentWidth` | Full-screen views |
|
|
32
|
+
| User Interaction | Drag to scroll | Swipe to snap between pages |
|
|
33
|
+
| Use Case | Long content, forms, images | Image gallery, onboarding wizard |
|
|
34
|
+
|
|
35
|
+
## 2. ScrollView
|
|
36
|
+
|
|
37
|
+
### Creating a ScrollView
|
|
38
|
+
|
|
39
|
+
```javascript
|
|
40
|
+
const scrollView = Ti.UI.createScrollView({
|
|
41
|
+
width: 300,
|
|
42
|
+
height: 200,
|
|
43
|
+
contentWidth: Ti.UI.SIZE, // Auto-fit to content
|
|
44
|
+
contentHeight: Ti.UI.SIZE, // Auto-fit to content
|
|
45
|
+
backgroundColor: 'white'
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// Add content
|
|
49
|
+
const content = Ti.UI.createView({
|
|
50
|
+
width: 500,
|
|
51
|
+
height: 500,
|
|
52
|
+
backgroundColor: 'blue'
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
scrollView.add(content);
|
|
56
|
+
win.add(scrollView);
|
|
57
|
+
```
|
|
58
|
+
...
|
|
59
|
+
### ScrollView Events
|
|
60
|
+
|
|
61
|
+
```javascript
|
|
62
|
+
scrollView.addEventListener('scroll', (e) => {
|
|
63
|
+
Ti.API.info('Scrolling...');
|
|
64
|
+
Ti.API.info(`Content offset: x=${e.contentOffset.x} y=${e.contentOffset.y}`);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
scrollView.addEventListener('dragEnd', (e) => {
|
|
68
|
+
Ti.API.info('Drag ended');
|
|
69
|
+
e.source.setContentOffset({ x: 0, y: 0 }, { animated: true });
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
scrollView.addEventListener('scrollEnd', (e) => {
|
|
73
|
+
Ti.API.info('Scroll completely ended');
|
|
74
|
+
e.source.setContentOffset({ x: 0, y: 0 }, { animated: true });
|
|
75
|
+
});
|
|
76
|
+
```
|
|
77
|
+
...
|
|
78
|
+
### Android ScrollView Direction
|
|
79
|
+
|
|
80
|
+
Android ScrollView can be vertical OR horizontal, not both:
|
|
81
|
+
|
|
82
|
+
```javascript
|
|
83
|
+
// Explicit horizontal scroll
|
|
84
|
+
const scrollView = Ti.UI.createScrollView({
|
|
85
|
+
width: 300,
|
|
86
|
+
height: 200,
|
|
87
|
+
scrollType: 'horizontal', // Android: horizontal only
|
|
88
|
+
contentWidth: 600, // Must be larger than width
|
|
89
|
+
contentHeight: 200 // Same as height = no vertical scroll
|
|
90
|
+
});
|
|
91
|
+
```
|
|
92
|
+
...
|
|
93
|
+
## 3. ScrollableView
|
|
94
|
+
|
|
95
|
+
### Creating a ScrollableView
|
|
96
|
+
|
|
97
|
+
```javascript
|
|
98
|
+
const view1 = Ti.UI.createView({ backgroundColor: '#123' });
|
|
99
|
+
const view2 = Ti.UI.createView({ backgroundColor: '#234' });
|
|
100
|
+
const view3 = Ti.UI.createView({ backgroundColor: '#345' });
|
|
101
|
+
|
|
102
|
+
const scrollableView = Ti.UI.createScrollableView({
|
|
103
|
+
views: [view1, view2, view3],
|
|
104
|
+
showPagingControl: true,
|
|
105
|
+
pagingControlHeight: 30,
|
|
106
|
+
pagingControlColor: 'blue'
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
win.add(scrollableView);
|
|
110
|
+
```
|
|
111
|
+
...
|
|
112
|
+
### ScrollableView Events
|
|
113
|
+
|
|
114
|
+
```javascript
|
|
115
|
+
scrollableView.addEventListener('scroll', (e) => {
|
|
116
|
+
Ti.API.info(`Current page: ${e.currentPage}`);
|
|
117
|
+
Ti.API.info(`Views: ${e.view}`); // View object reference
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
scrollableView.addEventListener('dragEnd', (e) => {
|
|
121
|
+
Ti.API.info(`Drag ended, settled on page: ${e.currentPage}`);
|
|
122
|
+
});
|
|
123
|
+
```
|
|
124
|
+
...
|
|
125
|
+
### Image Gallery
|
|
126
|
+
|
|
127
|
+
```javascript
|
|
128
|
+
const images = [];
|
|
129
|
+
for (let i = 1; i <= 10; i++) {
|
|
130
|
+
images.push(Ti.UI.createImageView({
|
|
131
|
+
image: `image${i}.jpg`,
|
|
132
|
+
width: Ti.UI.FILL,
|
|
133
|
+
height: Ti.UI.FILL
|
|
134
|
+
}));
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const gallery = Ti.UI.createScrollableView({
|
|
138
|
+
views: images,
|
|
139
|
+
showPagingControl: true
|
|
140
|
+
});
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### Onboarding Wizard
|
|
144
|
+
|
|
145
|
+
```javascript
|
|
146
|
+
const page1 = createOnboardingPage('Welcome', 'Get started with our app');
|
|
147
|
+
const page2 = createOnboardingPage('Features', 'Learn about key features');
|
|
148
|
+
const page3 = createOnboardingPage('Get Started', 'Create your account');
|
|
149
|
+
|
|
150
|
+
const onboarding = Ti.UI.createScrollableView({
|
|
151
|
+
views: [page1, page2, page3],
|
|
152
|
+
showPagingControl: true
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
const nextButton = Ti.UI.createButton({
|
|
156
|
+
title: 'Next',
|
|
157
|
+
bottom: 20
|
|
158
|
+
});
|
|
159
|
+
nextButton.addEventListener('click', () => {
|
|
160
|
+
const current = onboarding.currentPage;
|
|
161
|
+
if (current < 2) {
|
|
162
|
+
onboarding.scrollToView(current + 1);
|
|
163
|
+
} else {
|
|
164
|
+
onboarding.close();
|
|
165
|
+
showMainApp();
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### Long Form Content
|
|
171
|
+
|
|
172
|
+
```javascript
|
|
173
|
+
const scrollView = Ti.UI.createScrollView({
|
|
174
|
+
width: Ti.UI.FILL,
|
|
175
|
+
height: Ti.UI.FILL,
|
|
176
|
+
contentHeight: Ti.UI.SIZE,
|
|
177
|
+
layout: 'vertical'
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
// Add many components
|
|
181
|
+
for (let i = 0; i < 50; i++) {
|
|
182
|
+
scrollView.add(Ti.UI.createLabel({
|
|
183
|
+
text: `Item ${i}`,
|
|
184
|
+
top: 10,
|
|
185
|
+
height: 40
|
|
186
|
+
}));
|
|
187
|
+
}
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### Scrollable Form
|
|
191
|
+
|
|
192
|
+
```javascript
|
|
193
|
+
const formScrollView = Ti.UI.createScrollView({
|
|
194
|
+
layout: 'vertical',
|
|
195
|
+
height: Ti.UI.FILL
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
const nameField = Ti.UI.createTextField({
|
|
199
|
+
hintText: 'Name',
|
|
200
|
+
top: 10, height: 40
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
const emailField = Ti.UI.createTextField({
|
|
204
|
+
hintText: 'Email',
|
|
205
|
+
top: 10, height: 40
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
const submitButton = Ti.UI.createButton({
|
|
209
|
+
title: 'Submit',
|
|
210
|
+
top: 10
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
formScrollView.add(nameField);
|
|
214
|
+
formScrollView.add(emailField);
|
|
215
|
+
formScrollView.add(submitButton);
|
|
216
|
+
```
|
|
217
|
+
...
|
|
218
|
+
### ScrollableView
|
|
219
|
+
|
|
220
|
+
```javascript
|
|
221
|
+
// Move to page 2
|
|
222
|
+
scrollableView.scrollToView(1);
|
|
223
|
+
|
|
224
|
+
// Move to last page
|
|
225
|
+
scrollableView.scrollToView(scrollableView.views.length - 1);
|
|
226
|
+
|
|
227
|
+
// Get current page
|
|
228
|
+
const current = scrollableView.currentPage;
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
## 8. Detecting Scroll Position
|
|
232
|
+
|
|
233
|
+
```javascript
|
|
234
|
+
scrollView.addEventListener('scroll', (e) => {
|
|
235
|
+
const { x, y } = e.contentOffset;
|
|
236
|
+
|
|
237
|
+
// Detect near bottom
|
|
238
|
+
const maxScroll = scrollView.contentHeight - scrollView.height;
|
|
239
|
+
if (y > maxScroll - 50) {
|
|
240
|
+
loadMoreContent();
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
## Best Practices
|
|
246
|
+
|
|
247
|
+
1. **Use ScrollableView** for paginated content (galleries, wizards)
|
|
248
|
+
2. **Use ScrollView** for continuous scrolling content (long forms, articles)
|
|
249
|
+
3. **Specify dimensions** explicitly on Android when possible
|
|
250
|
+
4. **Consider performance** with large datasets
|
|
251
|
+
5. **Test on real devices** - simulator behavior may differ
|
|
252
|
+
6. **Use paging controls** for ScrollableView to improve UX
|