Dial
The Dial module provides comprehensive watch face management for peripheral devices, including operations to push, delete, switch, and monitor watch faces. It supports built-in watch faces, cloud watch faces, and custom watch faces with full lifecycle management capabilities.
Prerequisites
- Device must be connected and authenticated
- Cloud watch-face packages must be downloaded to a readable local file before installation
- Device firmware must support watch face operations
- Sufficient storage space on device for new watch faces
Data Models
TSDialModel
General watch face information model used for device watch face operations.
| Property | Type | Description |
|---|---|---|
dialId | NSString * | Unique identifier for the watch face |
dialName | NSString * | Display name shown to users |
dialType | TSDialType | Type of watch face (BuiltIn, Customer, or Cloud) |
isCurrent | BOOL | YES if currently active on device |
locationIndex | UInt8 | Position index where watch face is stored |
version | NSString * | Version number for update management |
TSDialTime
| Property | Type | Description |
|---|---|---|
timeImage | UIImage * | Time style image, preferred over the path |
timeImagePath | NSString * | Local or bundle image path |
timePosition | TSDialTimePosition | Fallback position when timeRect is unset |
timeRect | CGRect | Time area in background-image pixel coordinates |
timeColor | UIColor * | Optional tint for monochrome time images |
style | TSDialTimeStyle | Built-in time-style identifier |
TSDialDraft and TSDialDraftItem
Create TSDialDraft with initWithDraftType:templateFilePath:items:. Its draft type, template path, and items are read-only; dialId, previewImage, and multiplePlayIntervalMillis are optional inputs. Create items with itemWithImage:time: or itemWithVideoFilePath:time:; image pixels must match the device screen size. TSDialTime is immutable and uses its designated initializer.
TSDialDanMuItem
Create a scrolling image with initWithImage:. Only TSDialDraftTypeDanMu uses it, and the background item's danMuItems must contain at least one element. A static image is required; animationFilePath optionally references a readable local GIF. walkSpeed is a finite positive number in pixels/second (default 60); leftToRight defaults to NO.
imageX, imageY, animationX, and animationY use TSDialDanMuCoordinate, containing anchor and offset. Construct a coordinate with TSDialDanMuCoordinateMake(anchor, offset). Offsets are signed pixels in the range -524288…524287. Absolute coordinates use the offset directly; other anchors align the original content size on that axis, then add the offset.
TSDialDanMuAnchor | Value | Meaning |
|---|---|---|
TSDialDanMuAnchorAbsolute | 0 | Absolute top-left coordinate |
TSDialDanMuAnchorStart | 1 | Left on X, top on Y |
TSDialDanMuAnchorCenter | 2 | Center on the axis |
TSDialDanMuAnchorEnd | 3 | Right on X, bottom on Y |
Current unified dial models
| Model | Key properties | Purpose |
|---|---|---|
TSDialCapability | supportsCustom, supportsVideo, maxVideoDuration, supportsSlideshow, supportsDanMu, maxSlideshowImages, colorTintable, supportsComponent, shouldRenderTimeInPreview, maxInstallCount, maxInnerCount, screenWidth, screenHeight, deviceCornerRadius, previewWidth, previewHeight, previewCornerRadius, shape | Read-only static capability snapshot |
TSStorageSpace | available, total | Watch-face storage in bytes; total == 0 means unknown |
TSDialDraft | dialId, draftType, templateFilePath, items, previewImage, multiplePlayIntervalMillis | Custom-dial build input |
TSDialDraftItem | itemType, image, videoFilePath, time, danMuItems | One image or video resource |
TSDialDanMuItem | image, imageX, imageY, walkSpeed, leftToRight, animationFilePath, animationX, animationY | One scrolling image with an optional associated GIF |
TSDialArtifact | dialId, dialType, filePath | The only install input, obtained from a custom build or constructed for a downloaded cloud package |
TSComposePreviewInput | backgroundImage, time, maxKBSize, keepTransparentBackground | Preview-composition input |
TSCustomDialStyleOption | style, previewImageURL, size | Available time style |
TSCustomDialPositionOption | position, frame | Available time position |
TSCustomDialStyleConstraint | screenSize, screenShape, styles, positions, allowColorTint | Provider-neutral custom-style constraints |
Build and preview rules
Check dialCapability before building; it may be nil until cached device information is available. maxInstallCount == -1 means unlimited; 0 means custom/cloud installation is unavailable. maxSlideshowImages == 0 means no explicit image limit. Use each support flag before selecting a draft type.
Single-image, video, and DanMu drafts require exactly one item; multiple-image drafts require one or more image items. Time configuration is required except for DanMu. A nil template path lets the Provider resolve a compatible template. Nonpositive multiplePlayIntervalMillis uses the Provider default; positive values are quantized during build. Keep video and GIF files readable and unchanged until build completes.
shouldRenderTimeInPreview controls the Provider's time overlay policy for generated installation previews, independently of supportsComponent. It requires a time configuration and does not control the device's live clock or modify an App-supplied preview. A supplied draft.previewImage must match TSPeripheralScreen.dialPreviewSize in pixels.
fetchDialStorage: returns watch-face storage only. available remains usable when total is unknown. TSStorageSpace also provides used, usagePercentage, hasEnoughSpaceForSize:, and formattedStorageInfo; an unknown total yields 0 for used space and percentage, which must not be interpreted as actual usage.
Enumerations
TSDialType
Watch face type enumeration.
| Value | Name | Description |
|---|---|---|
0 | eTSDialTypeBuiltIn | Built-in watch face from device |
1 | eTSDialTypeCustomer | Custom watch face created by user |
2 | eTSDialTypeCloud | Watch face downloaded from cloud |
TSDialDraftType
Custom watch face resource type enumeration.
| Value | Name | Description |
|---|---|---|
1 | TSDialDraftTypeSingleImage | Single image-based custom watch face |
2 | TSDialDraftTypeMultipleImage | Multiple images-based custom watch face |
3 | TSDialDraftTypeVideo | Video-based custom watch face |
4 | TSDialDraftTypeDanMu | DanMu image-based watch face |
TSDialTimePosition
Time display position enumeration.
| Value | Name | Description |
|---|---|---|
0 | eTSDialTimePositionTop | Top position |
1 | eTSDialTimePositionBottom | Bottom position |
2 | eTSDialTimePositionLeft | Left position |
3 | eTSDialTimePositionRight | Right position |
4 | eTSDialTimePositionTopLeft | Top-left corner |
5 | eTSDialTimePositionBottomLeft | Bottom-left corner |
6 | eTSDialTimePositionTopRight | Top-right corner |
7 | eTSDialTimePositionBottomRight | Bottom-right corner |
8 | eTSDialTimePositionCenter | Center position |
TSDialInstallResult
Watch face install operation result enumeration.
| Value | Name | Description |
|---|---|---|
0 | eTSDialInstallResultStart | Install operation started |
1 | eTSDialInstallResultProgress | Install in progress |
2 | eTSDialInstallResultSuccess | Install completed successfully |
3 | eTSDialInstallResultFailed | Install operation failed |
4 | eTSDialInstallResultCompleted | Install flow ended |
Build types and errors
TSDialDraftType:TSDialDraftTypeSingleImage,TSDialDraftTypeMultipleImage,TSDialDraftTypeVideo,TSDialDraftTypeDanMu.TSDialDraftItemType:TSDialDraftItemTypeImage,TSDialDraftItemTypeVideo.TSDialErrorCode:TSDialErrorInvalidDraftType,TSDialErrorMissingTemplateFilePath,TSDialErrorTemplateFileNotFound,TSDialErrorEmptyDraftItems,TSDialErrorInvalidDraftItemCount,TSDialErrorInvalidDraftItemType,TSDialErrorMissingDraftItemTime,TSDialErrorMissingImageResource,TSDialErrorMissingVideoFilePath,TSDialErrorVideoFileNotFound,TSDialErrorInvalidScreenSize,TSDialErrorImageSizeMismatch,TSDialErrorMissingArtifactDialId,TSDialErrorMissingArtifactFilePath,TSDialErrorArtifactFileNotFound,TSDialErrorMissingPreviewBackgroundImage,TSDialErrorInvalidArtifactDialType,TSDialErrorInvalidDanMuItem.
TSDialTimeStyle
Time display style enumeration.
| Value | Name | Description |
|---|---|---|
0 | eTSDialTimeStyleNone | No style |
1 | eTSDialTimeStyle1 | Style 1 |
2 | eTSDialTimeStyle2 | Style 2 |
3 | eTSDialTimeStyle3 | Style 3 |
4 | eTSDialTimeStyle4 | Style 4 |
5 | eTSDialTimeStyle5 | Style 5 |
6 | eTSDialTimeStyle6 | Style 6 |
7 | eTSDialTimeStyle7 | Style 7 |
Callback types
| Callback | Description |
|---|---|
TSDialInstallCompletionBlock | Required final install result and error; inspect result to determine the outcome |
TSDialInstallProgressBlock | Install state and 0–100 progress |
TSDialListBlock | Watch-face list and error |
TSDialWidgetsBlock | Supported widget dictionary and error for Fw devices |
TSCustomDialStyleConstraintBlock | Style constraints and error |
Current API Reference
Capability, style, and queries
- (nullable TSDialCapability *)dialCapability;
- (BOOL)isSupportCustomDialStyleConstraint;
- (void)fetchCustomDialStyleConstraint:(TSCustomDialStyleConstraintBlock)completion;
- (void)fetchAllDials:(TSDialListBlock)completion;
- (void)fetchCurrentDial:(void (^)(TSDialModel *_Nullable dial, NSError *_Nullable error))completion;
- (void)fetchDialStorage:(void (^)(TSStorageSpace *_Nullable storage, NSError *_Nullable error))completion;
The capability snapshot comes from cached device information. Style constraints, dial state, and storage are returned asynchronously.
Selection, build, and preview
- (void)selectDial:(NSString *)dialId completion:(TSCompletionBlock)completion;
- (void)buildDialWithDraft:(TSDialDraft *)draft
completion:(void (^)(TSDialArtifact *_Nullable artifact, NSError *_Nullable error))completion;
- (void)composeDialPreview:(TSComposePreviewInput *)input
completion:(void (^)(UIImage *_Nullable previewImage, NSError *_Nullable error))completion;
The App prepares cropped or trimmed resources at the target screen size, then passes the returned TSDialArtifact to the install API.
Install, uninstall, and events
- (void)installDial:(TSDialArtifact *)artifact
progressBlock:(nullable TSDialInstallProgressBlock)progressBlock
completion:(TSDialInstallCompletionBlock)completion;
- (void)cancelDialInstall:(TSCompletionBlock)completion;
- (void)uninstallDial:(NSString *)dialId completion:(TSCompletionBlock)completion;
- (void)registerDialListDidChangeHandler:(void (^)(NSArray<TSDialModel *> *_Nullable allDials,
NSError *_Nullable error))handler;
- (void)fetchSupportedDialWidgets:(TSDialWidgetsBlock)completion;
Install progress uses 0–100. Built-in dials cannot be uninstalled. Only the latest list-change handler is retained.
Current API examples
Use these snippets independently inside your App's methods after SDK initialization and connection. Replace the sample asset names, paths, and IDs with your own resources. Obtain the interface first:
id<TSPeripheralDialInterface> dialInterface = [TopStepComKit sharedInstance].dial;
if (dialInterface == nil) {
TSLog(@"[Dial] Interface unavailable");
return;
}
Query capabilities and installed watch faces
TSDialCapability *capability = [dialInterface dialCapability];
if (capability == nil) {
TSLog(@"[Dial] Device capabilities are not available yet");
return;
}
TSLog(@"custom=%@ video=%@ maxVideo=%ld sec",
capability.supportsCustom ? @"YES" : @"NO",
capability.supportsVideo ? @"YES" : @"NO",
(long)capability.maxVideoDuration);
[dialInterface fetchAllDials:^(NSArray<TSDialModel *> *dials, NSError *error) {
if (error) {
TSLog(@"[Dial] List failed: %@", error.localizedDescription);
return;
}
for (TSDialModel *dial in dials) {
TSLog(@"%@ %@", dial.dialName, dial.isCurrent ? @"(current)" : @"");
}
}];
[dialInterface fetchCurrentDial:^(TSDialModel *dial, NSError *error) {
if (!error && dial) {
TSLog(@"[Dial] Current watch face: %@", dial.dialName);
}
}];
Query style constraints and storage
if ([dialInterface isSupportCustomDialStyleConstraint]) {
[dialInterface fetchCustomDialStyleConstraint:^(TSCustomDialStyleConstraint *constraint, NSError *error) {
if (!error && constraint) {
TSLog(@"[Dial] Available styles: %lu", (unsigned long)constraint.styles.count);
}
}];
}
[dialInterface fetchDialStorage:^(TSStorageSpace *storage, NSError *error) {
if (error || !storage) {
TSLog(@"[Dial] Storage query failed: %@", error.localizedDescription);
return;
}
TSLog(@"[Dial] Available: %llu bytes", storage.available);
if (storage.total > 0) {
TSLog(@"[Dial] Total: %llu bytes", storage.total);
}
}];
Build, preview, and install a single-image watch face
UIImage *image = [UIImage imageNamed:@"dial_background"];
if (!image || ![dialInterface dialCapability].supportsCustom) {
TSLog(@"[Dial] Provide a screen-sized image on a device that supports custom watch faces");
return;
}
TSDialTime *time = [[TSDialTime alloc] initWithTimeImage:[UIImage imageNamed:@"time_style"]
timeImagePath:nil
timePosition:eTSDialTimePositionTop
timeRect:CGRectZero
timeColor:nil
style:eTSDialTimeStyle1];
TSDialDraftItem *item = [TSDialDraftItem itemWithImage:image time:time];
TSDialDraft *draft = [[TSDialDraft alloc] initWithDraftType:TSDialDraftTypeSingleImage
templateFilePath:nil items:@[item]];
[dialInterface composeDialPreview:[[TSComposePreviewInput alloc]
initWithBackgroundImage:image time:time maxKBSize:300 keepTransparentBackground:NO]
completion:^(UIImage *preview, NSError *error) {
if (!error && preview) {
TSLog(@"[Dial] Preview generated: %@", preview);
}
}];
[dialInterface buildDialWithDraft:draft completion:^(TSDialArtifact *artifact, NSError *error) {
if (error || !artifact) {
TSLog(@"[Dial] Build failed: %@", error.localizedDescription);
return;
}
[dialInterface installDial:artifact progressBlock:^(TSDialInstallResult result, NSInteger progress) {
TSLog(@"Install progress: %ld%%", (long)progress);
} completion:^(TSDialInstallResult result, NSError *error) {
TSLog(@"[Dial] Install result: %ld, error: %@", (long)result, error.localizedDescription);
}];
}];
Video draft
Reuse the time configuration from the single-image example. Prepare the video at the device screen size, within maxVideoDuration, and keep the file readable until the build finishes.
if (![dialInterface dialCapability].supportsVideo) {
TSLog(@"[Dial] Video watch faces are not supported");
return;
}
TSDialDraftItem *item = [TSDialDraftItem itemWithVideoFilePath:@"/path/to/watchface.mp4" time:time];
TSDialDraft *draft = [[TSDialDraft alloc] initWithDraftType:TSDialDraftTypeVideo
templateFilePath:nil items:@[item]];
[dialInterface buildDialWithDraft:draft completion:^(TSDialArtifact *artifact, NSError *error) {
if (error || !artifact) {
TSLog(@"[Dial] Build failed: %@", error.localizedDescription);
return;
}
[dialInterface installDial:artifact progressBlock:nil completion:^(TSDialInstallResult result, NSError *installError) {
TSLog(@"[Dial] Video install result: %ld, error: %@", (long)result, installError.localizedDescription);
}];
}];
Select, cancel, or uninstall
These are independent actions; do not run them together after starting an installation. Select an ID from fetchAllDials: and uninstall only custom or cloud watch faces.
[dialInterface selectDial:@"dial-id" completion:^(BOOL success, NSError *error) {
TSLog(@"Selection: %@", success ? @"success" : error.localizedDescription);
}];
[dialInterface cancelDialInstall:^(BOOL success, NSError *error) {
TSLog(@"Cancel: %@", success ? @"success" : error.localizedDescription);
}];
[dialInterface uninstallDial:@"custom-dial-id" completion:^(BOOL success, NSError *error) {
TSLog(@"Uninstall: %@", success ? @"success" : error.localizedDescription);
}];
Observe list changes and supported widgets
[dialInterface registerDialListDidChangeHandler:^(NSArray<TSDialModel *> *dials, NSError *error) {
if (!error && dials) {
TSLog(@"[Dial] Watch-face list changed: %lu", (unsigned long)dials.count);
}
}];
[dialInterface fetchSupportedDialWidgets:^(NSDictionary *widgets, NSError *error) {
if (!error && widgets) {
TSLog(@"[Dial] Supported widgets: %@", widgets);
}
}];
Important notes
- Use
TSDialDraft,TSDialDraftItem, andTSDialTimefor the current custom watch-face build flow; image pixels must match the device screen size. TSStorageSpacevalues are bytes. Checkavailablebefore installing;total == 0means unknown.- Single-image, video, and DanMu drafts require one item; multiple-image drafts accept multiple image items, and DanMu requires non-empty
danMuItems. - Installation progress is 0–100. Built-in watch faces cannot be uninstalled.
- A new
registerDialListDidChangeHandler:registration replaces the previous handler. The event does not identify whether an install, uninstall, or selection caused the change.