Monthly Archives: May 2018

Icon for File with UIKit

There’s a general consensus among many Mac and (mostly) iOS developers, that AppKit is “old and busted” and UIKit is “new and refined.” I am still fairly limited in my experience with UIKit, but in many ways I agree that in developing the framework, they left some of the more annoying baggage of AppKit behind. However, they also left off many conveniences that AppKit developers like myself have come to rely upon.

One of the easiest things in the world in AppKit is asking the framework for an image to represent a file’s icon. Say you’ve got a Swift source file handy and you want to show it in your app with an appropriate icon. It’s an easy one-liner on the Mac:

let swiftIcon = NSWorkspace.shared.icon(forFile: "/tmp/Test.swift")

Screenshot of Xcode showing code to request an icon from NSWorkspace.

On UIKit, as far as I can tell, it takes a bit more work. There is no UIWorkspace, which is probably fine, but there is also no UIImage.iconForFile, or similar method to make this quite as straightforward as it is on the Mac.

I came across “UIDocumentInteractionController” which seems to be the key to easily obtaining icons for arbitrary image types on iOS. After initializing it with a file URL, you can ask it for an array of icons, which it will reveal in order from smallest to largest. Tying this all together in an extension on UIImage, you could add a pretty handy helper method to your collection of code (sorry, one of these days I’ll get syntax highlighting working on this blog):

import UIKit

extension UIImage {
	public enum FileIconSize {
		case smallest
		case largest
	}

	public class func icon(forFileURL fileURL: URL, preferredSize: FileIconSize = .smallest) -> UIImage {
		let myInteractionController = UIDocumentInteractionController(url: fileURL)
		let allIcons = myInteractionController.icons

		// allIcons is guaranteed to have at least one image
		switch preferredSize {
		case .smallest: return allIcons.first!
		case .largest: return allIcons.last!
		}
	}
}

Now I’ve got basically the same functionality I had on AppKit:

Screenshot of invoking the helper method from code snippet above.

This is cool enough, but what’s even cooler is the UIKit method works on a URL irrespective of whether the underlying file actually exists! This, combined with the fact that a URL can be initialized with just about any String, means we can expand our icon utilities with a method for obtaining icons by file name alone:

extension UIImage {
	public class func icon(forFileNamed fileName: String, preferredSize: FileIconSize = .smallest) -> UIImage {
		return icon(forFileURL: URL(fileURLWithPath: fileName), preferredSize: preferredSize)
	}
}

Here we use it to get the icon for an arbitrary Pages file which may or may not exist:

Screenshot of calling the code snippet above to obtain a file icon for a Pages document.

Ah, now this is starting to feel even cooler than AppKit. But wait, NSWorkspace also supports another handy method:

let zipIcon = NSWorkspace.shared.icon(forFileType: "zip")

Well, we can achieve similar by taking advantage of the icon(forFileNamed:) method:

extension UIImage {
	public class func icon(forPathExtension pathExtension: String, preferredSize: FileIconSize = .smallest) -> UIImage {
		let baseName = "Generic"
		let fileName = (baseName as NSString).appendingPathExtension(pathExtension) ?? baseName
		return icon(forFileNamed: fileName, preferredSize: preferredSize)
	}
}

Now we’re really catching up:

Screenshot of code calling the iconForPathExtension example above.

But AppKit’s equivalent actually works on UTI type strings, as well. Wouldn’t that be cool to support on iOS? Seeing as MobileCoreServices gives us access to low level functions for converting UTI and file extensions, we can take a UTI, convert it, and let our existing helper methods take it from there:

import MobileCoreServices

extension FileManager {
	public func fileExtension(forUTI utiString: String) -> String? {
		guard
			let cfFileExtension = UTTypeCopyPreferredTagWithClass(utiString as CFString, kUTTagClassFilenameExtension)?.takeRetainedValue() else
		{
			return nil
		}

		return cfFileExtension as String
	}
}

extension UIImage {
	public class func icon(forUTI utiString: String, preferredSize: FileIconSize = .smallest) -> UIImage? {
		guard let fileExtension = FileManager.default.fileExtension(forUTI: utiString) else {
			return nil
		}
		return icon(forPathExtension: fileExtension, preferredSize: preferredSize)
	}
}

And voila!

Screenshot of code calling the iconForUTI code above.

It would be fairly easy to further extend this to support creating icon images from MIME types. Hope this helps some of you folks, especially coming from the Mac, who expected getting icons for file types to be slightly easier than it is.

Internal Typealias Promotion

In some scenarios it might be useful to declare a typealias internally to a module, to make it easier to implement the functionality of the module itself, but less useful to export that typealias to clients of the module. For example, consider an image manipulation framework that can work with NSImage or UIImage instances, depending on the platform. Internally to the module, I might define:

#if os(macOS)
	typealias RSPlatformNativeImage = NSImage
#else
	typealias RSPlatformNativeImage = UIImage
#endif

Then I can implement methods, including public facing methods like:

public func monochromeImage(fromImage image: RSPlatformNativeImage) -> RSPlatformNativeImage { ... }

Since I haven’t marked my typealiases as ‘public’, they won’t be exported to clients, but the above will also fail to compile. Swift requires that public methods work only with public types. This makes sense, because if the types aren’t public, how are clients expected to be able to work with them?

But if I mark the typealiases public, I impose a new type “RSPlatformNativeImage” on clients, when as far as they are concerned, this method operates on either an NSImage or UIImage. They might quickly get the idea that RSPlatformNativeImage is just a typealias, but it’s a bit of unwanted clutter on the public-facing API.

Obviously I can solve this by adding more platform-specific directives to the module so that whole functions are declared as working with either NSImage or UIImage, but it would be nice if Swift would help me out here. Instead of giving a compiler error, Swift could simply export the method using the public type that the typealias resolves to. In which case a client of the module for iOS would see the method as:

public func monochromeImage(fromImage image: UIImage) -> UIImage { ... }

And for Mac:

public func monochromeImage(fromImage image: NSImage) -> NSImage { ... }

Handling internal typealiases like this would cause the behavior for Swift clients to match what is already being done for Objective-C clients. The generated Module-Swift.h for this method in a Mac project is:

- (NSImage * _Nonnull) monochromeImageFromImage:(NSImage * _Nonnull)image SWIFT_WARN_UNUSED_RESULT;

Thus for Objective-C clients the clutter of the typelias definition is tidied away, but for Swift clients, it must still be dealt with. I filed a bug requesting this behavior in the Swift bug tracking system.

Getting a CFNumber’s Value in Swift

Recently, as a consequence of working with the CGImageSource API, I found myself in a situation where I had hold of a CFNumber and wanted to get its value, as a CGFloat, in Swift.

CFNumber wraps numeric values in such a way that, to get the value out, you have to specify both the desired type, and provide a pointer to the memory of the variable that will hold the value. This kind of direct memory manipulation is not particularly suited to Swift’s priorities for type safety and memory protection. Here’s the API I’d need to use in Swift:

func CFNumberGetValue(_ number: CFNumber!, 
                    _ theType: CFNumberType, 
                    _ valuePtr: UnsafeMutableRawPointer!) -> Bool

The first two parameters are straightforward, but whenever I see types like “UnsafeMutableRawPointer” in Swift, my brain melts down a little. I have never really sat down to truly understand the nuanced differences between these types, so I usually just try something and hope it works. Here I am hoping for a gift from Swift’s implicit bridging:

// myCFNumber is 30.5
var myFloat: CGFloat = 0
CFNumberGetValue(myCFNumber, .floatType, &myFloat)
print(myFloat) // "5.46688490824244e-315\n"

Welp. That didn’t work. Let’s see if we can refresh our memory about UnsafeMutableRawPointer. In the section titled “Raw, Unitialized Memory” or I read:

You can use methods like initializeMemory(as:from:) and moveInitializeMemory(as:from:count:) to bind raw memory to a type and initialize it with a value or series of values.

Oh jeez, am I really going to have to manually create an UnsafeMutableRawPointer? I’ll try anything:

var myFloat: CGFloat = 0
var myFloatPointer = UnsafeMutableRawPointer(mutating: &myFloat)
CFNumberGetValue(myCFNumber, .floatType, myFloatPointer)
print(myFloat) // "5.46688490824244e-315\n"

Alas, same problem. Surely somebody has figured this out? I try Googling for “CFNumberGetValue Swift GitHub” and find a promising result from an authoritative source. The Swift standard library itself!

var value: Float = 0
CFNumberGetValue(_cfObject, kCFNumberFloatType, &value)

Aha! Practically the same thing I was doing, except for one nuanced detail: the var value is declared as a Float instead of a CGFloat. But wait a minute, what file is this implementation in? NSNumber.swift? Oh, right. NSNumber and CFNumber are toll-free bridged, and Swift’s standard library fulfills that promise too:

let myFloat = (myCFNumber as NSNumber).floatValue
print(myFloat) // 30.5

In fact, Swift’s Float type is even cozier with CFNumber than I expected. What started as a confused mission to make use of CFNumberGetValue and its unsafe pointer argument culminated in a bit of sample code from GitHub that ultimately led me to the understanding that the way to get a CFNumber’s value in Swift is … simply to ask for it:

let myFloat = Float(myCFNumber)
print(myFloat) // 30.5