-
Notifications
You must be signed in to change notification settings - Fork 54
Accept asset catalog entries in localResources #465
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from 9 commits
dc89136
61d38f2
45fa834
db63ec1
3cd051d
2d8f980
5b23f97
e7f5c50
c65894e
de085d5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,63 @@ | ||
| // | ||
| // AssetResource.swift | ||
| // SuperwallKit | ||
| // | ||
| // Created by Yusuf Tör on 24/04/2026. | ||
| // | ||
|
|
||
| import Foundation | ||
| #if canImport(UIKit) | ||
| import UIKit | ||
| #endif | ||
|
|
||
| /// A type that can be registered against ``SuperwallOptions/localResources`` and | ||
| /// served to the paywall webview via the `swlocal://` URL scheme. | ||
| /// | ||
| /// Conforming types: | ||
| /// - `URL` — a file on disk. | ||
| /// - `UIImage` — re-encoded as PNG when served to the webview. | ||
| /// - ``CatalogAsset`` — a deferred lookup against an `.xcassets` entry. Handles | ||
| /// both Image Sets and Data Sets (video, Lottie JSON, etc.). | ||
| /// | ||
| /// ```swift | ||
| /// options.localResources = [ | ||
| /// "hero-image": Bundle.main.url(forResource: "hero", withExtension: "png")!, | ||
| /// "logo": UIImage(named: "Logo")!, | ||
| /// "hero-video": CatalogAsset(name: "HeroVideo") | ||
| /// ] | ||
| /// ``` | ||
| public protocol AssetResource {} | ||
|
|
||
| extension URL: AssetResource {} | ||
|
|
||
| #if canImport(UIKit) | ||
| extension UIImage: AssetResource {} | ||
| #endif | ||
|
|
||
| /// An entry in an asset catalog (`.xcassets`). | ||
| /// | ||
| /// Resolved at load time by trying `UIImage(named:in:compatibleWith:)` first | ||
| /// (Image Set, re-encoded as PNG), then falling back to | ||
| /// `NSDataAsset(name:bundle:)` (Data Set, raw bytes preserved with no | ||
| /// re-encoding). | ||
| /// | ||
| /// Use a Data Set for non-image content (video, Lottie JSON, etc.) or when | ||
| /// you need lossless bytes. Image Sets work as-is for typical paywall imagery | ||
| /// like logos and icons. | ||
| public struct CatalogAsset: AssetResource { | ||
| /// The name of the data asset as it appears in the asset catalog. | ||
| public let name: String | ||
|
|
||
| /// The bundle that contains the asset catalog. | ||
| public let bundle: Bundle | ||
|
|
||
| /// Creates a reference to a Data Set entry in an asset catalog. | ||
| /// | ||
| /// - Parameters: | ||
| /// - name: The name of the data asset as it appears in the asset catalog. | ||
| /// - bundle: The bundle that contains the asset catalog. Defaults to `.main`. | ||
| public init(name: String, bundle: Bundle = .main) { | ||
| self.name = name | ||
| self.bundle = bundle | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,7 +7,7 @@ import UIKit | |
| import AVFoundation | ||
|
|
||
| final class SWLocalResourcesViewController: UICollectionViewController { | ||
| private var resources: [(id: String, url: URL)] = [] | ||
| private var resources: [(id: String, resource: AssetResource)] = [] | ||
|
|
||
| init() { | ||
| let layout = UICollectionViewFlowLayout() | ||
|
|
@@ -53,7 +53,7 @@ final class SWLocalResourcesViewController: UICollectionViewController { | |
|
|
||
| resources = Superwall.shared.options.localResources | ||
| .sorted { $0.key < $1.key } | ||
| .map { (id: $0.key, url: $0.value) } | ||
| .map { (id: $0.key, resource: $0.value) } | ||
| } | ||
|
|
||
| @objc private func doneTapped() { | ||
|
|
@@ -86,7 +86,7 @@ final class SWLocalResourcesViewController: UICollectionViewController { | |
| // swiftlint:disable:next force_cast | ||
| ) as! LocalResourceCell | ||
| let resource = resources[indexPath.item] | ||
| cell.configure(id: resource.id, url: resource.url) | ||
| cell.configure(id: resource.id, resource: resource.resource) | ||
| return cell | ||
| } | ||
| } | ||
|
|
@@ -231,7 +231,22 @@ private final class LocalResourceCell: UICollectionViewCell { | |
| spinner.stopAnimating() | ||
| } | ||
|
|
||
| func configure(id: String, url: URL) { | ||
| func configure(id: String, resource: AssetResource) { | ||
| if let url = resource as? URL { | ||
| configureURL(id: id, url: url) | ||
| } else if let image = resource as? UIImage { | ||
| idLabel.text = "\(id) (UIImage)" | ||
| spinner.stopAnimating() | ||
| imageView.image = image | ||
| } else if let catalog = resource as? CatalogAsset { | ||
| configureCatalogAsset(id: id, catalog: catalog) | ||
| } else { | ||
| idLabel.text = id | ||
| showErrorText("Unsupported resource type") | ||
| } | ||
| } | ||
|
|
||
| private func configureURL(id: String, url: URL) { | ||
| let ext = url.pathExtension.lowercased() | ||
| idLabel.text = ext.isEmpty ? id : "\(id).\(ext)" | ||
| spinner.startAnimating() | ||
|
|
@@ -249,6 +264,26 @@ private final class LocalResourceCell: UICollectionViewCell { | |
| } | ||
| } | ||
|
|
||
| private func configureCatalogAsset(id: String, catalog: CatalogAsset) { | ||
| idLabel.text = "\(id) (asset: \(catalog.name))" | ||
| spinner.startAnimating() | ||
| DispatchQueue.global(qos: .userInitiated).async { [weak self] in | ||
| let asset = NSDataAsset(name: catalog.name, bundle: catalog.bundle) | ||
| let image = asset.flatMap { UIImage(data: $0.data) } | ||
| DispatchQueue.main.async { | ||
| self?.spinner.stopAnimating() | ||
| if let image = image { | ||
| self?.imageView.image = image | ||
| } else if let asset = asset { | ||
| let byteCount = ByteCountFormatter.string(fromByteCount: Int64(asset.data.count), countStyle: .file) | ||
| self?.showErrorText("No preview\n\(asset.typeIdentifier) · \(byteCount)") | ||
|
Comment on lines
265
to
+288
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Prompt To Fix With AIThis is a comment left during a code review.
Path: Sources/SuperwallKit/Debug/SWLocalResourcesViewController.swift
Line: 265-279
Comment:
**Image-Set `CatalogAsset` shows "Asset not found" in debug view**
`configureCatalogAsset` loads asset data exclusively via `NSDataAsset`, but `NSDataAsset` only resolves *Data Set* entries — it returns `nil` for *Image Sets*. The scheme handler in `LocalFileSchemeHandler.load(resource:key:)` tries `UIImage(named:in:compatibleWith:)` first, so an Image Set CatalogAsset is served successfully in the webview, but the debug preview shows "Asset not found" because `NSDataAsset` finds nothing. The fix is to mirror the scheme handler's resolution order: try `UIImage(named:)` first, and only then fall back to `NSDataAsset`.
How can I resolve this? If you propose a fix, please make it concise. |
||
| } else { | ||
| self?.showErrorText("Asset not found") | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
yusuftor marked this conversation as resolved.
|
||
|
|
||
| private func loadImage(from url: URL) { | ||
| DispatchQueue.global(qos: .userInitiated).async { [weak self] in | ||
| guard | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.