Kamil Walas
Kamil Walas

Reputation: 43

SPM and images in Storyboard

I have problem during developing my project. I've decided to switch to SPM and almost everything is working as expected, but I'm fighting with one thing quite long and I don't know that should I do.

I have couple of packages. One of them is UI package which contains some images, custom implementations of some reusable screens etc, but most important is that it contains some icons that are being used around other packages.

I have second package, which is covering implementation of one of part of my application. In this we can find all screens connected with this functionality, and also it has some storyboards inside (in the future I will rewrite this storyboards to code version). And there I have an problem - since storyboards cannot load images from other bundle during runtime so even if in Xcode I can see that storyboard has an image and this image is from other package, during runtime it is not loading. When I was using Pods for that, I had a little script which was creating reference between Assets folder form UI framework to FunctionalityFramework and after this operation images were loading properly (similar like described there: Storyboard, UIImageView. Load image from bundle)

But how I can achieve that with SPM? I cannot make reference between two packages and their folders. Is there any way to achieve that?

Upvotes: 2

Views: 2357

Answers (1)

richardpiazza
richardpiazza

Reputation: 1589

Swift 5.3 added improvements to SPM to provided resources, so one way to accomplish what you're asking is to have your swift package vend the images and then load those images via the UIViewController that backs the Storyboard view.

Apple's documentation for bundling resources can be found here.

Here's the highlights:

Make sure that your swift package has resources declared for a target:

targets: [
    .target(
        name: "MyLibrary",
        resources: [
            .process("Resources")
            // This will process the /Sources/MyLibrary/Resources directory.
        ]
    ),
]

When resources are declared, a new Bundle reference becomes available that references the package resources. The Bundle.module reference can be used to load and provide the images. In your swift package, you can specify the images available with something like:

#if canImport(UIKit)
import UIKit

public extension UIImage {
    static var myImage: UIImage? = UIImage(named: "ImageName", bundle: Bundle.module, compatibleWith: nil)
}
#endif

Now, in your UIViewController, you should be able to import your package, and assign the image to an @IBOutlet:

import UIKit
import MyLibrary

class ViewController: UIViewController {
    
    @IBOutlet var imageView: UIImageView!
    
    func viewDidLoad() {
        super.viewDidLoad
        imageView.image = .myImage
    }
}

Another approach is to provide a custom UIImageView class through your library that will load images from the Bundle.module. This can be implemented through the Storyboard, but you will loose the ability to see the image in-place. For example, If I declared this class in my swift package:

public class AppIconImageView: UIImageView {
    @IBInspectable var imageName: String = "" {
        didSet {
            image = UIImage(named: imageName, in: Bundle.module, compatibleWith: nil)
        }
    }
}

I could then use that class in a Storyboard. Provide the name of the image that you want to have displayed via the imageName runtime property. (You can then unset the 'image' setting in Interface Builder.)

enter image description here

Like I mentioned, the image will not show in the Storyboard (@IBDesignable in other modules is often tricky), but when run, the package resource should load.

Upvotes: 2

Related Questions