Public asset symbol constants in Swift packages
Xcode auto-generates typed asset constants from .xcassets catalogs, letting you write Color(.mintGreen) / Image(.logoMark) (backed by ColorResource / ImageResource). Unfortunately, currently there's no solution for making such assets public in a package like AppCommons that wants to provide assets for other packages to use. The constants are always generated internal, so other packages can't see them. There's no public-access option, and the generator can't be turned off from Package.swift.
Workaround
Convention: inside each .xcassets catalog, put assets meant for cross-package use into a Public/ subfolder:

With the little help of a Ruby script, you can auto-generate constants like this:
Image(.AppCommons.logoMark)
Color(.AppCommons.mintGreen)The constants are nested under an enum named after the containing package to avoid clashing with Xcode's own generated (internal) symbols.
The script scans the project for **/*.xcassets/Public/ and writes a <PackageName>+Generated.swift next to each catalog:
public extension ImageResource {
enum AppCommons {
public static let logoMark = ImageResource.logoMark
}
}
public extension ColorResource {
enum AppCommons {
public static let skyBlue = ColorResource.skyBlue
}
}The script
# Scans a root path for `<name>.xcassets/Public/` folders and generates a
# public asset-constants file next to each catalog. The nested enum name is
# derived from the SPM package (the /Sources/<Name>/ path segment).
# Xcode's own asset symbols are always `internal` and can't be turned off
# inside an SPM package, so this is only for cross-package (public) access.
# See https://forums.swift.org/t/xcode15-generated-imageresource-with-public-access/67293
#
# Usage: ruby gen-app-assets.rb <root>
require 'pathname'
root = ARGV[0]
abort "usage: gen-app-assets.rb <root>" if root.nil?
def ident(name)
parts = name.split(/[_\-.\s]+/).reject(&:empty?)
first, *rest = parts
first[0].downcase + first[1..].to_s + rest.map { |p| p[0].upcase + p[1..].to_s }.join
end
Dir.glob(File.join(root, '**', '*.xcassets', 'Public')).sort.each do |public_dir|
public_dir = Pathname.new(public_dir)
catalog = public_dir.parent
ns = catalog.to_s.match(%r{/Sources/([^/]+)/})&.captures&.first
if ns.nil?
warn "skip #{catalog}: cannot derive namespace (expected .../Sources/<Name>/...)"
next
end
images, colors = [], []
Dir.glob(public_dir + '**/*').sort.each do |p|
base = File.basename(p)
case File.extname(base)
when '.imageset' then images << File.basename(base, '.imageset')
when '.colorset' then colors << File.basename(base, '.colorset')
end
end
lines = []
lines << '// This file is auto-generated by tools/gen-app-assets. Do not edit.'
lines << '// Re-exports Xcode-generated internal asset constants publicly.'
lines << ''
lines << 'import DeveloperToolsSupport'
lines << ''
lines << 'public extension ImageResource {'
lines << " enum #{ns} {"
images.each { |n| id = ident(n); lines << " public static let #{id} = ImageResource.#{id}" }
lines << ' }'
lines << '}'
lines << ''
lines << 'public extension ColorResource {'
lines << " enum #{ns} {"
colors.each { |n| id = ident(n); lines << " public static let #{id} = ColorResource.#{id}" }
lines << ' }'
lines << '}'
lines << ''
out = catalog.parent + "#{ns}+Generated.swift"
out.write(lines.join("\n"))
puts "Wrote #{out} (enum #{ns}, #{images.size} images, #{colors.size} colors)"
endRerun after adding or renaming assets, e.g. as a just recipe:
gen-app-assets:
ruby tools/gen-app-assets/gen-app-assets.rb PackagesFeedback
FB18574624: Generating Asset symbol constants with public access in a Swift package
I'm using an Asset catalog in a Swift package to define colors that are to be used in other Swift packages.
Currently there seems no way to make the extensions to „DeveloperToolsSupport.ColorResource" (and the like) public, nor to disable the generation at all.
This is cumbersome. As a workaround, I write the extension to Color/UIColor myself, but then these colors are to be used differently when they come from a package (Color.skyBlue) vs. when they come from the local package (Color(.skyBlue) — I'd prefer the ColorResource style).
Maybe a 2nd option „Public Asset Symbols" next to „Asset Catalog » Asset Symbols" in the .xcassets that would apply both to ColorResource / Color extensions?
See also: ↗ Swift Forums discussion