Ratnesh Jain

Asset Plugin Library

Library: https://github.com/ratnesh-jain/AssetPluginLibrary

Utilizing the local Swift Package Manager (SPM) for Apple platform development offers a plethora of advantages:

  • Enhanced compile time: SPM efficiently manages dependencies, reducing compilation times.
  • Isolated feature creation: SPM facilitates the creation of isolated features, enhancing testability.
  • Improved Xcode preview integration: Swift package targets/modules seamlessly integrate with Xcode preview functionality, allowing for rapid iteration and refined UI development.

Since the release of Xcode 15, it automatically generates source code for type-safe asset access, a highly beneficial feature. This includes both Xcode project's Asset Catalogs and from Swift packages.

However, there's currently a limitation wherein Xcode doesn't generate public methods for xcassets within Swift packages. To address this gap, our library offers a solution akin to Xcode's functionality.

Let's explore the issue further and discover how leveraging the AssetPluginLibrary can effectively mitigate it

The Problem

Create a Swift package and add a folder inside the Sources directory named "Resources". Now let add Asset catelogue to the newly created Resources directory.
Adding Asset Catelogues

Let's add few images to the Media Asset Catelog.

Adding Assets

Now if we build the package, Xcode will generate some code to access the media assets as shown below.

Xcode Generated Code

As we will see, this generated code does not have any access control modifiers, which is internal by default. This will cause the access to the Resources module only, but we need to access these across other features creating Views for user interfaces.

The Solution

We can now add the AssetPluginLibrary as a dependency to our package.

let package = Package(
    name: "Application",
    platforms: [.iOS(.v17)],
    products: [
        .library(
            name: "Application",
            targets: ["Application"]),
    ],
    dependencies: [
+        .package(url: "https://github.com/ratnesh-jain/AssetPluginLibrary", .upToNextMajor(from: "0.0.1")),
    ],
    targets: [
        .target(name: "Resources", dependencies: []),
        .target(
            name: "Application", dependencies: ["Resources"]),
        .testTarget(
            name: "ApplicationTests",
            dependencies: ["Application"]),
    ]
)

Example Source: Package.swift

As we add this library as a package dependency, and it exports a SwiftPackagePlugin, Xcode will now shows an plugin action Generate Asset Resources to the our Package's context menu as shown below.

The Plugin

Xcode will now preset a modal for selecting module in which we have .xcassets file which is Resources in this example.

  • NOTE: For Xcode to show Resources as a input option, we need to make the Resources target as a product first as below.
let package = Package(
    name: "Application",
    platforms: [.iOS(.v17)],
    products: [
        // Products define the executables and libraries a package produces, making them visible to other packages.
        .library(
            name: "Application",
            targets: ["Application"]),
+        .library(
+            name: "Resources",
+            targets: ["Resources"]
+        ),
    ],

Select the Resources

This plugin action will create a new swift file called AssetResource in which it generates all the code similar to Xcode but with public access control modifier as shown in screenshot below.

PublicGeneratedCode

Now we can import the Resources module to our Application module, and we can access the newly generated assets using a new Image initialiser Image(asset: ImageAssetResource).

import Foundation
import Resources
import SwiftUI

public struct AppFeature: View {
    
    public var body: some View {
        VStack {
+            Image(asset: .appleMaps)
                .resizable()
                .aspectRatio(contentMode: .fit)
+            Image(asset: .swiftScript)
                .resizable()
                .aspectRatio(contentMode: .fit)
+            Image(asset: .swiftui)
                .resizable()
                .aspectRatio(contentMode: .fit)
        }
    }
    
}

Example: Application.swift

AccessAcrossModules

We trust you find this article insightful and informative. Thank you for reading.

Tagged with: