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")
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:
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:
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:
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!
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.