A project I'm working on was recently split into multiple smaller projects for reasons outside of my control.
We have some helper methods in one project to create shorthand and type safe image requests... The reason for this is to have flexibility around theming. Maybe one themes detailDisclosure isn't the same as anothers.
The syntax looks like this
public extension UIImageView {
convenience init(_ key: UIImage.Key) {
self.init(image: UIImage(named: theme.imageName(for: key)))
}
}
let imageView = UIImageView(.detailDisclosure)
and it's sister function
let image = UIImage(.detailDisclosure)
Pretty simple stuff when all of the images and themes live in the same place. However now we have different projects, which have different assets inside different asset folders.
So what I had to add to make this work was this...
convenience init(_ key: UIImage.Key, in locality: AnyClass? = nil) {
self.init(image: UIImageView.localImage(named: key.rawValue, in: locality))
}
// Currently assumes this method and default assets are in the main bundle by default
fileprivate static func localImage(named name: String, in locality: AnyClass?) -> UIImage? {
let bundle = (locality != nil) ? Bundle(for: locality!) : Bundle.main
return UIImage(named: name, in: bundle, compatibleWith: nil)
}
let image = UIImage(.detailDisclosure, in: ThisProjectTheme.self)
ThisProjectTheme
can actually be any class inside this bundle, and technically you can go to another bundle and share its resources in this way too.
From a consumer perspective though, this extra effort is something I'd be looking to avoid, and it's also pretty dangerous for newcomers in my opinion.
What would be better, would be unless the consumer of this API specifies a different locality
, we find their locality for them automatically; instead of the current solution to go to the main bundle.
In the future most of these requests will come from projects that have their own assets.
I've seen for example
file: String = #file
convenience init(_ key: UIImage.Key, file: String = #file, in locality: AnyClass? = nil)
Means we can hack at it obviously, but I'm wondering if there's an elegant solution to get a sender or for that matter bundle without the consumer implicitly sending it to the function?
Thanks for your time
"what bundle a function was called from automatically"
As appealing as this might sound, you reallllly don't want that. The moment you find that copy/pasting some code from one project to another makes it behave differently, is the moment you'll lose your marbles.
Instead, I think this entire approach needs to be reworked. For one, UIImage
seems to be the wrong point of abstraction for this. Instead, I would use something like this:
import UIKit
class ImageProvider {
let bundle: Bundle
init(bundle: Bundle) {
self.bundle = bundle
}
init(forMainClass mainClass: AnyClass) {
self.init(bundle: Bundle(for: mainClass)!)
}
func image(
named: String,
with configuration: UIImage.Configuration? = nil
) -> UIImage {
return UIImage(named: name, in: self.bundle, with: configuration)?
}
}
Each app will create its own ImageProvider
, which searches for assets within its own bundle.
This has several key advantages: