NSImage.Name and assets catalog

Hi!


Seems like NSImage(named: String) has been replaced by NSImage(named: NSImage.Name) in Swift 4. Does this mean that we have to use NSImage.Name(rawValue: "my-image") everywhere or there's a way to get an image from the assets catalog by name?


I don't see the point of having all these .Name types added in AppKit, especially if you need to extend NSImage.Name just to add images that are already in the catalog.

Accepted Reply

The need to "wrap" a string in a type like "NSImage.Name" can be a nuisance, but there are actually some good reasons to do this, if you take the (small) trouble to fully adopt the pattern:


1. It's a type-safety thing. You can't specify something that's not an image name to API like "image(named:). Of course, that isn't particularly safe if you just write "NSImage.Name(rawValue: "my-image")" in the call — BTW, you can actually write just "NSImage.Name("my-image")" because there're a convenience initializer for it — but there's more …


2. The NSImage.Name is a struct, so it's extensible. Instead of writing "NSImage.Name("my-image")" directly in your calls, you can extend the struct:


extension NSImage.Name {
     static let myImage = NSImage.Name("my-image")
}


How does that help? Well, now you can write "image (named: .myImage)". And how does that help, especially if you only have to call it in one place? Well, …


3. If your chosen name conflicts with an existing one, you get a compilation error in your NSImage.Name extension, and know to choose a different name, preventing a hard-to-find bug. Also, …


4. If you do need to use the image name in multiple places, your extension will cause the name to be offered by autocompletion, which can be a big win for your fingers.


This might seem like overkill sometimes, but once you've absorbed the pattern it can be worth it.


One other thing. "NSImage.Name" is a struct, which means it's extensible with your own names, as above. In cases where the API has a fixed set of names for something, Apple uses an enum, to which you can't add cases. (Otherwise, using the enum is a lot like using the struct for this kind of purpose.) That's how Apple stops you from extending something that shouldn't be extended with new names.

Replies

The need to "wrap" a string in a type like "NSImage.Name" can be a nuisance, but there are actually some good reasons to do this, if you take the (small) trouble to fully adopt the pattern:


1. It's a type-safety thing. You can't specify something that's not an image name to API like "image(named:). Of course, that isn't particularly safe if you just write "NSImage.Name(rawValue: "my-image")" in the call — BTW, you can actually write just "NSImage.Name("my-image")" because there're a convenience initializer for it — but there's more …


2. The NSImage.Name is a struct, so it's extensible. Instead of writing "NSImage.Name("my-image")" directly in your calls, you can extend the struct:


extension NSImage.Name {
     static let myImage = NSImage.Name("my-image")
}


How does that help? Well, now you can write "image (named: .myImage)". And how does that help, especially if you only have to call it in one place? Well, …


3. If your chosen name conflicts with an existing one, you get a compilation error in your NSImage.Name extension, and know to choose a different name, preventing a hard-to-find bug. Also, …


4. If you do need to use the image name in multiple places, your extension will cause the name to be offered by autocompletion, which can be a big win for your fingers.


This might seem like overkill sometimes, but once you've absorbed the pattern it can be worth it.


One other thing. "NSImage.Name" is a struct, which means it's extensible with your own names, as above. In cases where the API has a fixed set of names for something, Apple uses an enum, to which you can't add cases. (Otherwise, using the enum is a lot like using the struct for this kind of purpose.) That's how Apple stops you from extending something that shouldn't be extended with new names.

Thanks! Do you have any idea why this hasn't been implemented in UIKit or it's just a matter of time before they do so in a future beta?

It's a good question. I didn't notice that UIKit was different, and I don't know why. (OTOH, shared types like Notification.Name are present in iOS.)


One possible reason is that there aren't any standard images in iOS, AFAICT. (In IB, when I look at the Media library tab, it's empty. Or am I missing something?) For NSImage, there's a whole stack of system-provided images.

Are these types available on older macOS versions? I’m targeting 10.11 but NSImage.Name and other .Name types are marked as 10.13, yet Xcode isn’t throwing any errors or warnings.

The NSImage.Name type is a fiction constructed by Swift when it bridges the 10.13 SDK. (There's an annotation in NSImage.h:


     typedef NSString * NSImageName NS_EXTENSIBLE_STRING_ENUM;


that tells it to construct type NSImage.Name.) Behind the scenes, therefore, it's actually passing a NString*, so it works on all versions of macOS.