Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
bd86306
fix(swift): add contentful.swift Entry adapter for OptimizedEntry [NT…
nalchevanidze Jul 30, 2026
73a0632
fix(swift): map full asset metadata and FileMetadata field values [NT…
nalchevanidze Jul 30, 2026
e6a5dbd
fix(swift): mirror Contentful.Entry's readable surface on ResolvedEnt…
nalchevanidze Jul 30, 2026
95932b0
test(swift): cover a 5-level entry chain and a 3-node link cycle [NT-…
nalchevanidze Jul 30, 2026
1eb75f8
feat(swift): add Contentful.Entry overload of resolveOptimizedEntry […
nalchevanidze Jul 30, 2026
574a318
refactor(swift): replace OptimizationEntryMapping/ResolvedEntry with …
nalchevanidze Jul 30, 2026
77ecc31
refactor(swift): use CTEntry for OptimizedEntry's stored baseline ent…
nalchevanidze Jul 30, 2026
6dd903b
refactor(swift): back CTEntry with the typed CDA.EntryEnvelope contract
nalchevanidze Jul 30, 2026
5c44a4c
refactor(swift): simplify CTEntry.init(any:)/toJSON via built-in Foun…
nalchevanidze Jul 30, 2026
3923bd1
refactor(swift): trim CTEntry doc comments and rename CDA.EntryEnvelo…
nalchevanidze Jul 31, 2026
bb10403
refactor(swift): convert CDA envelope from(...) factories to init(...)
nalchevanidze Jul 31, 2026
95b12e3
refactor(swift): add CTEntry.hasField, use it for OptimizedEntry's is…
nalchevanidze Jul 31, 2026
2c375f8
fix(swift): make CTEntry.toFoundation public, fix UIKit reference impl
nalchevanidze Jul 31, 2026
1425974
refactor(swift): add CTEntry.parseWithFallback, use it at every silen…
nalchevanidze Jul 31, 2026
3cbb09f
refactor(swift): add CTEntry.toDictionary(fallback:), narrow toFounda…
nalchevanidze Jul 31, 2026
814b34f
refactor(swift): cache JSONEncoder/ISO8601DateFormatter in CTEntry, t…
nalchevanidze Aug 3, 2026
dc71217
Merge branch 'main' into fix/nt-3808-ios-optimized-entry-contentful-m…
nalchevanidze Aug 3, 2026
3765aa5
refactor(swift): move JSONValue.encoded(_:) into CTEntry, cache JSOND…
nalchevanidze Aug 3, 2026
5c2ed1d
refactor(swift): merge CTEntry.init(any:) and parseWithFallback into …
nalchevanidze Aug 3, 2026
56e903b
refactor(swift): drop Envelope/Value suffixes from CDA mirror types
nalchevanidze Aug 3, 2026
4d9dba6
fix(swift): omit contentType.sys.id instead of defaulting to "" in CD…
nalchevanidze Aug 3, 2026
fed4165
refactor(swift): rename stale envelope/linkValue switch bindings in C…
nalchevanidze Aug 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 35 additions & 9 deletions documentation/internal/sdk-knowledge/native/ios.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,16 @@ lifecycle, SwiftUI views, and preview-panel UI. Swift source root:
Single module: `import ContentfulOptimization`. There is one SDK; both guides consume it — SwiftUI
apps mostly use the view surface, UIKit apps mostly use the imperative `OptimizationClient` surface.

| Import path (`ContentfulOptimization`) | Public symbol or purpose | source |
| -------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| SwiftUI surface | `OptimizationRoot`, `OptimizedEntry`, `OptimizationScrollView`, `.trackScreen(name:)` / `ScreenTrackingModifier`, `TrackingConfig`, `ScrollContext` | extern:SwiftUI views/modifiers — packages/ios/ContentfulOptimization/Sources/ContentfulOptimization/Views/OptimizationRoot.swift#OptimizationRoot; extern:packages/ios/ContentfulOptimization/Sources/ContentfulOptimization/Views/ScreenTrackingModifier.swift#trackScreen |
| Imperative client | `OptimizationClient` (`@MainActor` `ObservableObject`); `EventEmissionResult` | extern:OptimizationClient is a @MainActor ObservableObject facade wrapping the JS bridge — packages/ios/ContentfulOptimization/Sources/ContentfulOptimization/Core/OptimizationClient.swift#OptimizationClient |
| Config | `OptimizationConfig`, `OptimizationApiConfig`, `StorageDefaults`, `OptimizationLogLevel`, `QueuePolicy`, `QueueFlushPolicy`, `QueueEvent`/`QueueEventType`, `BlockedEvent` | extern:packages/ios/ContentfulOptimization/Sources/ContentfulOptimization/Core/OptimizationConfig.swift#OptimizationConfig; extern:packages/ios/ContentfulOptimization/Sources/ContentfulOptimization/Core/OptimizationConfig.swift#StorageDefaults |
| Event payloads | `IdentifyPayload`, `PageEventPayload`, `ScreenEventPayload`, `TrackEventPayload`, `TrackViewPayload`, `TrackClickPayload` | extern:packages/ios/ContentfulOptimization/Sources/ContentfulOptimization/Core/EventPayloads.swift#ScreenEventPayload; extern:packages/ios/ContentfulOptimization/Sources/ContentfulOptimization/Core/TrackViewPayload.swift#TrackViewPayload |
| State / result types | `OptimizationState`, `ResolvedOptimizedEntry`, `PreviewState` (+ DTOs), `JSONValue`, `OptimizationError` | extern:packages/ios/ContentfulOptimization/Sources/ContentfulOptimization/Core/OptimizationState.swift#OptimizationState; extern:packages/ios/ContentfulOptimization/Sources/ContentfulOptimization/Core/OptimizationError.swift#OptimizationError |
| Tracking (imperative) | `ViewTrackingController`, `TrackingMetadata` | extern:ViewTrackingController is a @MainActor imperative view-timing engine for UIKit — packages/ios/ContentfulOptimization/Sources/ContentfulOptimization/Tracking/ViewTrackingController.swift#ViewTrackingController |
| Preview panel | `PreviewPanelOverlay` (SwiftUI), `PreviewPanelViewController` (UIKit), `PreviewPanelConfig`, `PreviewContentfulClient` / `ContentfulHTTPPreviewClient`, `PreviewPanelContent` | extern:packages/ios/ContentfulOptimization/Sources/ContentfulOptimization/Preview/PreviewPanelViewController.swift#PreviewPanelViewController; extern:packages/ios/ContentfulOptimization/Sources/ContentfulOptimization/Preview/PreviewContentfulClient.swift#ContentfulHTTPPreviewClient |
| Import path (`ContentfulOptimization`) | Public symbol or purpose | source |
| -------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| SwiftUI surface | `OptimizationRoot`, `OptimizedEntry`, `OptimizationScrollView`, `.trackScreen(name:)` / `ScreenTrackingModifier`, `TrackingConfig`, `ScrollContext` | extern:SwiftUI views/modifiers — packages/ios/ContentfulOptimization/Sources/ContentfulOptimization/Views/OptimizationRoot.swift#OptimizationRoot; extern:packages/ios/ContentfulOptimization/Sources/ContentfulOptimization/Views/ScreenTrackingModifier.swift#trackScreen |
| Imperative client | `OptimizationClient` (`@MainActor` `ObservableObject`); `EventEmissionResult` | extern:OptimizationClient is a @MainActor ObservableObject facade wrapping the JS bridge — packages/ios/ContentfulOptimization/Sources/ContentfulOptimization/Core/OptimizationClient.swift#OptimizationClient |
| Config | `OptimizationConfig`, `OptimizationApiConfig`, `StorageDefaults`, `OptimizationLogLevel`, `QueuePolicy`, `QueueFlushPolicy`, `QueueEvent`/`QueueEventType`, `BlockedEvent` | extern:packages/ios/ContentfulOptimization/Sources/ContentfulOptimization/Core/OptimizationConfig.swift#OptimizationConfig; extern:packages/ios/ContentfulOptimization/Sources/ContentfulOptimization/Core/OptimizationConfig.swift#StorageDefaults |
| Event payloads | `IdentifyPayload`, `PageEventPayload`, `ScreenEventPayload`, `TrackEventPayload`, `TrackViewPayload`, `TrackClickPayload` | extern:packages/ios/ContentfulOptimization/Sources/ContentfulOptimization/Core/EventPayloads.swift#ScreenEventPayload; extern:packages/ios/ContentfulOptimization/Sources/ContentfulOptimization/Core/TrackViewPayload.swift#TrackViewPayload |
| State / result types | `OptimizationState`, `ResolvedOptimizedEntry`, `ResolvedContentfulOptimizedEntry`, `PreviewState` (+ DTOs), `JSONValue`, `OptimizationError` | extern:packages/ios/ContentfulOptimization/Sources/ContentfulOptimization/Core/OptimizationState.swift#OptimizationState; extern:packages/ios/ContentfulOptimization/Sources/ContentfulOptimization/Core/ResolvedOptimizedEntry.swift#ResolvedContentfulOptimizedEntry; extern:packages/ios/ContentfulOptimization/Sources/ContentfulOptimization/Core/OptimizationError.swift#OptimizationError |
| `contentful.swift` adapter | `OptimizationEntryMapping.toOptimizationEntry(_:)` (`Contentful.Entry` → `[String: Any]`); `ResolvedEntry` | extern:packages/ios/ContentfulOptimization/Sources/ContentfulOptimization/Contentful/OptimizationEntryMapping.swift#OptimizationEntryMapping; extern:packages/ios/ContentfulOptimization/Sources/ContentfulOptimization/Contentful/ResolvedEntry.swift#ResolvedEntry |
| Tracking (imperative) | `ViewTrackingController`, `TrackingMetadata` | extern:ViewTrackingController is a @MainActor imperative view-timing engine for UIKit — packages/ios/ContentfulOptimization/Sources/ContentfulOptimization/Tracking/ViewTrackingController.swift#ViewTrackingController |
| Preview panel | `PreviewPanelOverlay` (SwiftUI), `PreviewPanelViewController` (UIKit), `PreviewPanelConfig`, `PreviewContentfulClient` / `ContentfulHTTPPreviewClient`, `PreviewPanelContent` | extern:packages/ios/ContentfulOptimization/Sources/ContentfulOptimization/Preview/PreviewPanelViewController.swift#PreviewPanelViewController; extern:packages/ios/ContentfulOptimization/Sources/ContentfulOptimization/Preview/PreviewContentfulClient.swift#ContentfulHTTPPreviewClient |

- SPM target links `JavaScriptCore` for consuming apps and copies `optimization-ios-bridge.umd.js`
as a package resource; platforms are iOS 15+ / macOS 12+. source: extern:Package.swift links JavaScriptCore and copies the UMD bundle resource, iOS 15/macOS 12 — packages/ios/ContentfulOptimization/Package.swift
Expand Down Expand Up @@ -152,6 +153,31 @@ viewportHeight:)` from its own scroll/layout callbacks and the controller applie
expanded inline `nt_mergetag` entry to the bridge, which reads the selector against the current
profile and returns the resolved string or `nil` (fallback). The app owns extracting the embedded
entry from Rich Text before calling it. source: extern:getMergeTagValue passes the mergetag entry to the bridge — packages/ios/ContentfulOptimization/Sources/ContentfulOptimization/Core/OptimizationClient.swift#OptimizationClient; core-sdk#resolvers/MergeTagValueResolver.ts#resolve; kb:shared/concepts.md
- `contentful.swift` integration: `OptimizationEntryMapping.toOptimizationEntry(_:)` converts a
`Contentful.Entry` into the `{sys, fields, metadata}` map the resolver expects, recursively
expanding resolved links/assets/rich text and always emitting `metadata: {tags, concepts}` (empty
when absent) so the resolver's entry guard never silently treats a real optimized entry as
non-optimized. `OptimizedEntry(entry: Contentful.Entry, ...)` is a second SwiftUI initializer that
runs the map once at construction and hands the resolved variant back through `ResolvedEntry`
instead of a raw dict; both initializers produce the same `OptimizedEntry<Content>` type.
source: extern:OptimizationEntryMapping.toOptimizationEntry always emits metadata, recurses links/rich text — packages/ios/ContentfulOptimization/Sources/ContentfulOptimization/Contentful/OptimizationEntryMapping.swift#OptimizationEntryMapping; extern:OptimizedEntry(entry: Contentful.Entry) initializer wraps the mapping and ResolvedEntry — packages/ios/ContentfulOptimization/Sources/ContentfulOptimization/Views/OptimizedEntry.swift#OptimizedEntry
- The imperative UIKit path has the same `Contentful.Entry` overload as the SwiftUI view:
`OptimizationClient.resolveOptimizedEntry(baseline: Contentful.Entry, selectedOptimizations:)`
maps `baseline` through `OptimizationEntryMapping` once, then delegates to the dict-based
overload and wraps its result in `ResolvedContentfulOptimizedEntry` (`entry: ResolvedEntry`
instead of a raw dict). It inherits the dict-based overload's fail-soft behavior exactly — not
initialized, a serialization error, or an unparseable bridge result all fall back to the mapped
baseline with `selectedOptimization`/`optimizationContextId` nil.
source: extern:OptimizationClient.resolveOptimizedEntry(baseline: Contentful.Entry) maps then delegates to the dict overload — packages/ios/ContentfulOptimization/Sources/ContentfulOptimization/Core/OptimizationClient.swift#OptimizationClient; extern:packages/ios/ContentfulOptimization/Sources/ContentfulOptimization/Core/ResolvedOptimizedEntry.swift#ResolvedContentfulOptimizedEntry
- `ResolvedEntry` mirrors `Contentful.Entry`'s own readable surface — `id`, `localeCode`,
`createdAt`, `updatedAt`, `getField<T>(_:)`, and the `String`/`Int` convenience subscripts — so a
resolved variant reads exactly like a fetched `Entry` instead of a raw map. It does **not** mirror
`Entry.type`/`currentlySelectedLocale`/`metadata`/`setLocale(withCode:)`: `type: ContentType?` and
`currentlySelectedLocale: Locale` are full fetched resources the resolved map never carries and
`contentful.swift` gives no public initializer to fabricate, and `Metadata` has no public
initializer either, so `metadata.tags` on the resolved map can only be read via `getField`, never
wrapped back into a real `Metadata` value.
source: extern:ResolvedEntry mirrors Entry.id/localeCode/createdAt/updatedAt/getField/subscripts, omits type/currentlySelectedLocale/metadata/setLocale by design — packages/ios/ContentfulOptimization/Sources/ContentfulOptimization/Contentful/ResolvedEntry.swift#ResolvedEntry

## Identifier ownership

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ final class OptimizedEntryUIView: UIView {
baseline: entry,
selectedOptimizations: effectiveOptimizations
)
resolvedEntry = result.entry
resolvedEntry = result.entry.toDictionary(fallback: entry)
resolvedOptimization = result.selectedOptimization
} else {
resolvedEntry = entry
Expand Down
14 changes: 14 additions & 0 deletions packages/ios/ContentfulOptimization/Package.resolved

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 11 additions & 2 deletions packages/ios/ContentfulOptimization/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import PackageDescription

let package = Package(
let package: Package = Package(
name: "ContentfulOptimization",
platforms: [.iOS(.v15), .macOS(.v12)],
products: [
Expand All @@ -11,9 +11,15 @@ let package = Package(
targets: ["ContentfulOptimization"]
),
],
dependencies: [
.package(url: "https://github.com/contentful/contentful.swift", exact: "5.5.15"),
],
targets: [
.target(
name: "ContentfulOptimization",
dependencies: [
.product(name: "Contentful", package: "contentful.swift"),
],
resources: [
.copy("Resources/optimization-ios-bridge.umd.js"),
],
Expand All @@ -23,7 +29,10 @@ let package = Package(
),
.testTarget(
name: "ContentfulOptimizationTests",
dependencies: ["ContentfulOptimization"]
dependencies: [
"ContentfulOptimization",
.product(name: "Contentful", package: "contentful.swift"),
]
),
]
)
Loading
Loading