Skip to content

Accept asset catalog entries in localResources#465

Merged
yusuftor merged 15 commits intodevelopfrom
feat/local-resources-asset-catalog
Apr 27, 2026
Merged

Accept asset catalog entries in localResources#465
yusuftor merged 15 commits intodevelopfrom
feat/local-resources-asset-catalog

Conversation

@yusuftor
Copy link
Copy Markdown
Collaborator

@yusuftor yusuftor commented Apr 24, 2026

Changes in this pull request

Generalises SuperwallOptions.localResources from [String: URL] to [String: AssetResource] so customers can register paywall assets that live inside an asset catalog (.xcassets), achieving parity with the Android SDK's PaywallResource.FromResources(R.drawable.*).

Customer ask (Pylon): customers whose assets live in .xcassets had no clean way to register them because asset catalog entries don't expose a file URL.

API

New AssetResource protocol is the dictionary's value type. Two conforming types:

  • URL — existing behavior, a file on disk. Conforms out of the box, so existing call sites that assign URL dictionaries compile and behave unchanged.
  • UIImage — register an in-memory image directly; served to the webview as image/png via pngData(). This covers the asset catalog case via UIImage(named: "Logo").
options.localResources = [
  "hero-image": Bundle.main.url(forResource: "hero", withExtension: "png")!,
  "logo":       UIImage(named: "Logo")!
]

Implementation notes

  • LocalFileSchemeHandler switches on the resource type. URL is read with Data(contentsOf:) and a MIME type derived from the path extension; UIImage is re-encoded via pngData() and served as image/png. The PNG bytes are cached per UIImage (NSCache) so repeat webview requests don't re-encode on the main thread.
  • ObjC bridge (@objc(localResources)) is typed [String: NSObject] and accepts both NSURL and UIImage values; anything else is dropped. Note: the ObjC property's generic type widened from NSDictionary<NSString *, NSURL *> * to NSDictionary<NSString *, NSObject *> *. Setter assignments and dictionary literals continue to work; ObjC code that previously assigned the getter into an NSDictionary<NSString *, NSURL *> * local will now see an "incompatible pointer types" warning that's resolved by retyping the local or casting.
  • SWLocalResourcesViewController (debug view) previews both URL and UIImage entries.
  • Customers with non-image asset catalog entries (Data Sets — video, Lottie JSON, etc.) should continue to ship those as files on disk and register them via URL. Asset catalog support is scoped to images, which covers the Pylon ask.
  • Unrelated cleanup: dropped a stale trailing_closure lint violation in ConfigLogic.swift:310 while in the area.

Tests

Extends LocalFileSchemeHandlerTests:

  • URL registered through the unified dictionary still loads bytes and a MIME type.
  • UIImage registered directly is served as image/png with bytes matching pngData().
  • Existing 9 tests continue to pass with the new value type.

Checklist

  • All unit tests pass.
  • All UI tests pass.
  • Demo project builds and runs on iOS.
  • Demo project builds and runs on Mac Catalyst.
  • Demo project builds and runs on visionOS.
  • I added/updated tests or detailed why my change isn't tested.
  • I added an entry to the CHANGELOG.md for any breaking changes, enhancements, or bug fixes.
  • I have run swiftlint in the main directory and fixed any issues.
  • I have updated the SDK documentation as well as the online docs.
  • I have reviewed the contributing guide

🤖 Generated with Claude Code

Greptile Summary

Generalises SuperwallOptions.localResources from [String: URL] to [String: AssetResource], adding UIImage conformance so customers can register asset-catalog Image Sets without needing a file URL. The ObjC bridge, PNG caching, debug preview, and tests are all cleanly handled, and all issues raised in previous review rounds appear to have been addressed in this iteration.

Confidence Score: 5/5

PR is safe to merge; no P0/P1 issues found in the current implementation.

The change is well-scoped and backwards-compatible. URL call sites continue to work unchanged, the ObjC bridge uses the established @available(swift, obsoleted:) pattern correctly, the manual encode(to:) already excludes localResources so Encodable synthesis is not a concern, NSCache is thread-safe and UIImage.pngData() is documented as thread-safe, and both new code paths have test coverage. All issues flagged in prior review threads have been resolved in this iteration.

No files require special attention.

Important Files Changed

Filename Overview
Sources/SuperwallKit/Config/Options/AssetResource.swift New protocol defining the AssetResource type with URL and UIImage conformances; clean and minimal.
Sources/SuperwallKit/Paywall/View Controller/Web View/LocalFileSchemeHandler.swift Adds UIImage dispatch with per-instance NSCache; thread-safe and handles pngData nil guard correctly.
Sources/SuperwallKit/Config/Options/SuperwallOptions.swift Widens localResources to [String: AssetResource] with a correct @available(swift, obsoleted:) ObjC bridge; manual encode(to:) already omits the property, avoiding Encodable synthesis issues.
Sources/SuperwallKit/Debug/SWLocalResourcesViewController.swift Debug view updated to dispatch on resource type; UIImage case renders the image directly, avoiding the prior NSDataAsset Image-Set gap.
Tests/SuperwallKitTests/Paywall/View Controller/Web View/LocalFileSchemeHandlerTests.swift Two new tests cover URL conformance and UIImage round-trip; existing suite remains intact.
Sources/SuperwallKit/Config/ConfigLogic.swift Minor trailing-closure style cleanup; semantically identical.
CHANGELOG.md Enhancement entry added; accurate description of the new UIImage support.

Sequence Diagram

sequenceDiagram
    participant App
    participant SuperwallOptions
    participant WKWebView
    participant LocalFileSchemeHandler
    participant NSCache

    App->>SuperwallOptions: localResources["logo"] = UIImage(named:)
    App->>SuperwallOptions: localResources["hero"] = Bundle.main.url(...)

    WKWebView->>LocalFileSchemeHandler: webView(_:start:) [swlocal://logo]
    LocalFileSchemeHandler->>SuperwallOptions: options.localResources["logo"]
    SuperwallOptions-->>LocalFileSchemeHandler: UIImage instance
    LocalFileSchemeHandler->>NSCache: object(forKey: image)
    alt Cache hit
        NSCache-->>LocalFileSchemeHandler: NSData (PNG bytes)
    else Cache miss
        LocalFileSchemeHandler->>LocalFileSchemeHandler: image.pngData()
        LocalFileSchemeHandler->>NSCache: setObject(data, forKey: image)
    end
    LocalFileSchemeHandler-->>WKWebView: URLResponse + PNG data (image/png)

    WKWebView->>LocalFileSchemeHandler: webView(_:start:) [swlocal://hero]
    LocalFileSchemeHandler->>SuperwallOptions: options.localResources["hero"]
    SuperwallOptions-->>LocalFileSchemeHandler: URL (file path)
    LocalFileSchemeHandler->>LocalFileSchemeHandler: Data(contentsOf: url)
    LocalFileSchemeHandler-->>WKWebView: URLResponse + file data (MIME from extension)
Loading

Reviews (6): Last reviewed commit: "Promote image PNG cache to static for cr..." | Re-trigger Greptile

Generalises SuperwallOptions.localResources from `[String: URL]` to
`[String: AssetResource]`. URL conforms to AssetResource so existing
call sites are unaffected. New `CatalogAsset(name:bundle:)` registers
a Data Set entry from an .xcassets, resolved at load time via
NSDataAsset — the iOS equivalent of Android's R.raw.* resource IDs.

ObjC keeps a URL-only shim under the same `localResources` name.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Comment thread Sources/SuperwallKit/Debug/SWLocalResourcesViewController.swift Outdated
Comment thread Sources/SuperwallKit/Config/Options/AssetResource.swift
yusuftor and others added 3 commits April 24, 2026 16:25
`UTType.preferredMIMEType` is iOS 14+. On iOS 13, fall back to
`UTTypeCopyPreferredTagWithClass` from MobileCoreServices so catalog
assets are served with a real MIME type (an `<img>` or `<video>` is
refused by WKWebView when the MIME is `application/octet-stream`).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- SWLocalResourcesViewController: when a catalog asset exists but isn't
  image-decodable, show its UTI and byte count instead of a blank cell.
- AssetResource: remove the unused UIKit import.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Satisfies the `trailing_closure` SwiftLint rule — the only remaining
project-level lint violation.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@yusuftor
Copy link
Copy Markdown
Collaborator Author

@greptileai

yusuftor and others added 5 commits April 24, 2026 16:47
- LocalFileSchemeHandler: remove unreferenced UIKit import.
- SuperwallOptions: ObjC setter now replaces only the URL subset of
  localResources, so CatalogAsset entries registered from Swift survive
  a subsequent ObjC assignment in mixed Swift/ObjC codebases.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
localResources is documented as set once before configure(), so the
"mixed Swift/ObjC post-configure mutation" scenario the merge was
guarding against isn't part of the intended usage pattern. The setter
goes back to replacing the full dict.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
NSDataAsset only resolves Data Sets, so a typical Image Set logo would
fail with "File not found". Try NSDataAsset first (lossless, any file
type), then fall back to UIImage(named:in:compatibleWith:)?.pngData()
so existing Image Sets work without restructuring the asset catalog.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Calling NSDataAsset on an Image Set triggers a CoreUI log about a
wrong-typed lookup. Flip the order so UIImage(named:) runs first —
Image Sets resolve cleanly, and NSDataAsset is only reached when
there's no Image Set to mistype.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Lets customers register an in-memory image directly:
  "logo": UIImage(named: "Logo")!
Served to the webview as image/png via pngData(). Complements
CatalogAsset for cases where eager decoding is acceptable.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@yusuftor
Copy link
Copy Markdown
Collaborator Author

@greptileai

Comment thread Sources/SuperwallKit/Debug/SWLocalResourcesViewController.swift Outdated
yusuftor and others added 3 commits April 25, 2026 13:07
Debug view was loading catalog assets via NSDataAsset only, so an
Image Set CatalogAsset previewed as "Asset not found" even though
the scheme handler resolved it successfully. Try UIImage(named:)
first, fall through to NSDataAsset for Data Sets.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@yusuftor
Copy link
Copy Markdown
Collaborator Author

@greptileai

Comment thread Sources/SuperwallKit/Config/Options/SuperwallOptions.swift Outdated
yusuftor and others added 3 commits April 27, 2026 16:10
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Avoids re-encoding on every swlocal:// request, which runs on the main thread.
Also updates the ObjC bridge doc to reflect that UIImage is accepted there too.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@yusuftor yusuftor merged commit 0397e04 into develop Apr 27, 2026
4 checks passed
@yusuftor yusuftor deleted the feat/local-resources-asset-catalog branch April 27, 2026 16:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant