User IDs can be used for reporting purposes, storing the read status of Clips and Stories, followed categories, user preferences and other features. By default, the Storyteller SDK creates an externalId for users when the SDK is first initialized. externalId is stored until the user uninstalls the app or until the SDK is initialized with a different externalId.
However, if you have a user account system then you may wish to set your own user IDs within the Storyteller SDK.
The externalId should be an identifier that is unique per user and does not change. Therefore, using something like the user's email address is not a good choice for an externalId, as the user may change it at some point in the future. However, using a unique UUID/GUID would be a good choice as it is guaranteed not to change over time. The SDK hashes externalId, externalId isn't stored or sent to the server in the raw form.
apiKey is a required parameter while userInput is optional and its default value is nil. If userInput is nil, the SDK will use a default autogenerated externalId. When calling Storyteller.shared.initialize, if the new userInput value is different from the previous one, all local data related to the previous user will be deleted.
Storyteller.shared.initialize should be called as soon as the value for the externalId is known in your app. Avoid making simultaneous Storyteller.shared.initialize calls, as it might result in unexpected behavior.
Note: StorytellerUserInput(externalId:) is a failable initializer and returns nil if externalId is missing or blank.
Use an ISO 639-1 two-letter language code when available, or an ISO 639-2 three-letter language code when needed:
Storyteller.shared.user.setLocale("fr")
Storyteller.shared.user.setLocale("ace")
The SDK lowercases the supplied value and validates it against Locale.isoLanguageCodes. An invalid code is ignored and leaves the previously stored locale unchanged. If your app needs a diagnostic instead of that silent no-op, validate before calling the SDK:
If you use login in your app and wish to allow users to logout and log back in as a new user (or proceed as an anonymous user) then when a user logs out you should call initialize again specifying a new externalId. Note that this will reset the local store of user data, for example which Pages or Clips the user has viewed.
One of the features of Storyteller is the ability to set custom attributes for requests. These custom attributes can be used to provide additional information about a request, such as the user's location or the device they are using. It can be also used for audience targeting and content personalization. These custom attributes are sent with every request made by the SDK.
To set a custom attribute, you can use the setCustomAttribute() method of the Storyteller.shared.user object. This method takes two parameters: the key of the custom attribute and its value. For example, to set a custom attribute for the user's location, you can use the following code:
This will add a custom attribute to all requests made by the user, with the key "location" and the value "New York". You can set multiple custom attributes by calling setCustomAttribute() multiple times with different keys and values. String type values are allowed.
To set multiple custom attributes at once, use the setCustomAttributes() method with a dictionary of string keys and values. Note that this replaces all previously stored custom attributes. For example:
To remove custom attribute, you can use the removeCustomAttribute() method of the Storyteller.shared.user object. This method takes one parameter: the key of the custom attribute to remove. For example, to remove a custom attribute for the user's location, you can use the following code
Storyteller provides a mechanism for managing followed categories associated with clips. Following or unfollowing a category updates the corresponding plus or tick icon in the Clips UI. The SDK supports two following models, one of which is configured in the System Admin settings for your tenant.
Your app manages following state and handles synchronization with your systems. This gives you full control over the following logic and data storage. When interacting with Storyteller, you have the following methods at your disposal:
When using these methods, the SDK automatically updates the user attributes to reflect the changes in followed categories. This ensures consistency between the followed categories state and user attributes sent with requests.
Use setFollowedCategories(_:) when you have the complete list of categories that should be followed and need the SDK's current followed category state to match that list exactly. This method is asynchronous, so you can wait for the SDK to update local followed category IDs and request personalization attributes before reloading Storyteller content.
Storyteller resolves the supplied IDs before applying the replacement. IDs that are not returned by Storyteller are omitted, while the resolved subset is applied successfully. If none of the supplied IDs resolve, the followed category state is cleared. Request or response-decoding failures still throw an error and leave the previous state unchanged.
React to followed categories changes within the SDK#
Whenever a user follows or unfollow a category from within the SDK's UI (e.g. the Clips Player), the SDK triggers the following delegate method on StorytellerDelegate.categoryFollowActionTaken method, which allows the integrating app to synchronize its state/UI.
The SDK handles following activity internally and automatically syncs with Storyteller's servers. This simplifies integration by removing the need for custom following logic in your app.
This means that all of the above methods on Storyteller.shared.user are no-ops in this mode. Also the categoryFollowActionTaken is not called.
To check if a specific category is being followed, use isCategoryFollowed(_:). To retrieve a list of all currently followed categories, read followedCategories.
To retrieve the backend-backed list of categories the current user can follow, use Storyteller.shared.user.getFollowableCategories(). This async method returns category metadata plus SDK-derived current followed state for each category, which is useful when building a custom followable category management UI.
Each returned category includes a non-optional id, optional name, displayTitle, externalId, type, thumbnailUrl, and placement, plus SDK-derived isFollowed.
Use Storyteller.shared.user.followedCategories and Storyteller.shared.user.isCategoryFollowed(_:) when you only need the SDK's current local followed category IDs. Use Storyteller.shared.user.getFollowableCategories() when you need renderable category data from Storyteller's backend with isFollowed derived from the SDK's local followed category state.
{"slug": "users", "page_title": "Identify and Personalize Users", "page_url": "Users/", "canonical_url": "/ios/Users/", "markdown": "# Working with Users and User customization\n\nThis page describes how to customize Storyteller's behaviour on a per-user basis.\n\n## Working with Users\n\nUser IDs can be used for reporting purposes, storing the read status of Clips and Stories, followed categories, user preferences and other features. By default, the Storyteller SDK creates an `externalId` for users when the SDK is first initialized. `externalId` is stored until the user uninstalls the app or until the SDK is initialized with a different `externalId`.\n\nHowever, if you have a user account system then you may wish to set your own user IDs within the Storyteller SDK.\n\nThe `externalId` should be an identifier that is unique per user and does not change. Therefore, using something like the user's email address is not a good choice for an `externalId`, as the user may change it at some point in the future. However, using a unique UUID/GUID would be a good choice as it is guaranteed not to change over time. The SDK hashes `externalId`, `externalId` isn't stored or sent to the server in the raw form.\n\n### Setting a User ID\n\nIn order to supply the `externalId` to the Storyteller SDK, call the following method:\n\n<!-- storyteller-swift-example: id=users-01 target=sdk-ios context=statements -->\n\n```swift\nlet userInput = StorytellerUserInput(externalId: \"user-id\")\n\nTask {\n do {\n try await Storyteller.shared.initialize(apiKey: \"[APIKEY]\", userInput: userInput)\n } catch {\n // handle error\n }\n}\n```\n\n`apiKey` is a required parameter while `userInput` is optional and its default value is `nil`. If `userInput` is `nil`, the SDK will use a default autogenerated `externalId`. When calling `Storyteller.shared.initialize`, if the new `userInput` value is different from the previous one, all local data related to the previous user will be deleted.\n\n`Storyteller.shared.initialize` should be called as soon as the value for the `externalId` is known in your app. Avoid making simultaneous `Storyteller.shared.initialize` calls, as it might result in unexpected behavior.\n\n> Note: `StorytellerUserInput(externalId:)` is a failable initializer and returns `nil` if `externalId` is missing or blank.\n\n### Setting the User's Locale\n\nUse an ISO 639-1 two-letter language code when available, or an ISO 639-2 three-letter language code when needed:\n\n<!-- storyteller-swift-example: id=users-02 target=sdk-ios context=statements -->\n\n```swift\nStoryteller.shared.user.setLocale(\"fr\")\n```\n\n<!-- storyteller-swift-example: id=users-04 target=sdk-ios context=statements -->\n\n```swift\nStoryteller.shared.user.setLocale(\"ace\")\n```\n\nThe SDK lowercases the supplied value and validates it against `Locale.isoLanguageCodes`. An invalid code is ignored and leaves the previously stored locale unchanged. If your app needs a diagnostic instead of that silent no-op, validate before calling the SDK:\n\n<!-- storyteller-swift-example: id=users-locale-validation target=sdk-ios context=declarations -->\n\n```swift\nimport Foundation\nimport StorytellerSDK\n\nfunc applyStorytellerLocale(_ locale: String) {\n let normalizedLocale = locale.lowercased()\n guard Locale.isoLanguageCodes.contains(normalizedLocale) else {\n assertionFailure(\"Unsupported Storyteller locale: \\(locale)\")\n return\n }\n\n Storyteller.shared.user.setLocale(normalizedLocale)\n}\n```\n\nTo clear or reset the `locale` settings, simply pass `nil` as the parameter:\n\n<!-- storyteller-swift-example: id=users-05 target=sdk-ios context=statements -->\n\n```swift\nStoryteller.shared.user.setLocale(nil)\n```\n\n### Changing Users\n\nIf you use login in your app and wish to allow users to logout and log back in as a new user (or proceed as an anonymous user) then when a user logs out you should call `initialize` again specifying a new `externalId`. Note that this will reset the local store of user data, for example which Pages or Clips the user has viewed.\n\n## User Customization\n\n### Custom Attributes\n\nOne of the features of Storyteller is the ability to set custom attributes for requests. These custom attributes can be used to provide additional information about a request, such as the user's location or the device they are using. It can be also used for audience targeting and content personalization. These custom attributes are sent with every request made by the SDK.\n\n#### How to Use\n\n##### Set custom attribute\n\nTo set a custom attribute, you can use the `setCustomAttribute()` method of the `Storyteller.shared.user` object. This method takes two parameters: the key of the custom attribute and its value. For example, to set a custom attribute for the user's location, you can use the following code:\n\n<!-- storyteller-swift-example: id=users-06 target=sdk-ios context=statements -->\n\n```swift\nStoryteller.shared.user.setCustomAttribute(key: \"location\", value: \"New York\")\n```\n\nThe Showcase app demonstrates both the UI for selecting attributes and the SDK calls that apply them: see the personalization UI in [`AccountView`](https://github.com/getstoryteller/storyteller-showcase-ios/blob/11.6.1/main/ShowcaseApp/Views/Account/AccountView.swift#L8) and the attribute wiring in [`StorytellerService.addValue`](https://github.com/getstoryteller/storyteller-showcase-ios/blob/11.6.1/main/ShowcaseApp/Storyteller/StorytellerService.swift#L141) and [`StorytellerService.removeValue`](https://github.com/getstoryteller/storyteller-showcase-ios/blob/11.6.1/main/ShowcaseApp/Storyteller/StorytellerService.swift#L158).\n\nThis will add a custom attribute to all requests made by the user, with the key \"location\" and the value \"New York\". You can set multiple custom attributes by calling setCustomAttribute() multiple times with different keys and values. String type values are allowed.\n\n##### Set custom attributes\n\nTo set multiple custom attributes at once, use the `setCustomAttributes()` method with a dictionary of string keys and values. Note that this replaces all previously stored custom attributes. For example:\n\n<!-- storyteller-swift-example: id=users-07 target=sdk-ios context=statements -->\n\n```swift\nStoryteller.shared.user.setCustomAttributes([\n \"location\": \"New York\",\n \"device_type\": \"mobile\"\n])\n```\n\n##### Remove custom attribute\n\nTo remove custom attribute, you can use the `removeCustomAttribute()` method of the `Storyteller.shared.user` object. This method takes one parameter: the key of the custom attribute to remove. For example, to remove a custom attribute for the user's location, you can use the following code\n\n<!-- storyteller-swift-example: id=users-08 target=sdk-ios context=statements -->\n\n```swift\nStoryteller.shared.user.removeCustomAttribute(key: \"location\")\n```\n\n### Followed Categories\n\nStoryteller provides a mechanism for managing followed categories associated with clips. Following or unfollowing a category updates the corresponding plus or tick icon in the Clips UI. The SDK supports two following models, one of which is configured in the System Admin settings for your tenant.\n\n#### App-Managed Following\n\nYour app manages following state and handles synchronization with your systems. This gives you full control over the following logic and data storage. When interacting with Storyteller, you have the following methods at your disposal:\n\n##### Update followed categories\n\nTo add a followed category, a list of them, or to remove a followed category, call one of the below methods by passing the respective category ID(s):\n\n<!-- storyteller-swift-example: id=users-09 target=sdk-ios context=statements -->\n\n```swift\nStoryteller.shared.user.addFollowedCategory(\"location\")\nStoryteller.shared.user.addFollowedCategories([\"location\", \"city\", \"country\"])\nStoryteller.shared.user.removeFollowedCategory(\"location\")\nStoryteller.shared.user.removeFollowedCategories([\"location\", \"city\"])\n```\n\nWhen using these methods, the SDK automatically updates the user attributes to reflect the changes in followed categories. This ensures consistency between the followed categories state and user attributes sent with requests.\n\nUse `setFollowedCategories(_:)` when you have the complete list of categories that should be followed and need the SDK's current followed category state to match that list exactly. This method is asynchronous, so you can wait for the SDK to update local followed category IDs and request personalization attributes before reloading Storyteller content.\n\nStoryteller resolves the supplied IDs before applying the replacement. IDs that are not returned by Storyteller are omitted, while the resolved subset is applied successfully. If none of the supplied IDs resolve, the followed category state is cleared. Request or response-decoding failures still throw an error and leave the previous state unchanged.\n\n<!-- storyteller-swift-example: id=users-10 target=sdk-ios context=statements -->\n\n```swift\nlet clipsView = StorytellerClipsViewController()\n\nTask {\n do {\n try await Storyteller.shared.user.setFollowedCategories([\"city\", \"country\"])\n clipsView.reloadData()\n } catch {\n // handle error\n }\n}\n```\n\n##### React to followed categories changes within the SDK\n\nWhenever a user follows or unfollow a category from within the SDK's UI (e.g. the Clips Player), the SDK triggers the following delegate method on [`StorytellerDelegate.categoryFollowActionTaken`](StorytellerDelegate.md#categoryfollowactiontaken) method, which allows the integrating app to synchronize its state/UI.\n\nSee the Showcase handling of follow/unfollow callbacks in [`StorytellerInstanceDelegate.categoryFollowActionTaken`](https://github.com/getstoryteller/storyteller-showcase-ios/blob/11.6.1/main/ShowcaseApp/Storyteller/StorytellerInstanceDelegate.swift#L89).\n\n#### Storyteller-Managed Following\n\nThe SDK handles following activity internally and automatically syncs with Storyteller's servers. This simplifies integration by removing the need for custom following logic in your app.\n\nThis means that all of the above methods on `Storyteller.shared.user` are no-ops in this mode. Also the `categoryFollowActionTaken` is not called.\n\n#### Common methods\n\nTo check if a specific category is being followed, use `isCategoryFollowed(_:)`. To retrieve a list of all currently followed categories, read `followedCategories`.\n\n<!-- storyteller-swift-example: id=users-11 target=sdk-ios context=statements -->\n\n```swift\nlet isFollowing: Bool = Storyteller.shared.user.isCategoryFollowed(\"location\")\nlet followedCategories: [String] = Storyteller.shared.user.followedCategories\n```\n\nTo retrieve the backend-backed list of categories the current user can follow, use `Storyteller.shared.user.getFollowableCategories()`. This async method returns category metadata plus SDK-derived current followed state for each category, which is useful when building a custom followable category management UI.\n\nEach returned category includes a non-optional `id`, optional `name`, `displayTitle`, `externalId`, `type`, `thumbnailUrl`, and `placement`, plus SDK-derived `isFollowed`.\n\n<!-- storyteller-swift-example: id=users-12 target=sdk-ios context=statements -->\n\n```swift\nTask {\n do {\n let followableCategories = try await Storyteller.shared.user.getFollowableCategories()\n\n for category in followableCategories.categories {\n print(\"\\(category.name ?? \"Unnamed category\"): \\(category.isFollowed)\")\n }\n } catch {\n // handle error\n }\n}\n```\n\nUse `Storyteller.shared.user.followedCategories` and `Storyteller.shared.user.isCategoryFollowed(_:)` when you only need the SDK's current local followed category IDs. Use `Storyteller.shared.user.getFollowableCategories()` when you need renderable category data from Storyteller's backend with `isFollowed` derived from the SDK's local followed category state.\n", "copy_markdown_include_header": false, "base_path": "", "ai_dir": "ai", "missing_payload_behavior": "empty"}