What is the current proper way to load an image from local filesystem?

I'm just trying to display an image that is stored in the local filesystem, but the more I dig into this the more confused I get.

So previously I used this code (it's simplified):

func findImage(name: String) -> UIImage? {
    do {
        let url = try FileManager.default.url(for: .applicationSupportDirectory,
                                              in: .userDomainMask,
                                              appropriateFor: nil,
                                              create: false)
            .appendingPathComponent("MyFolder")
            .appendingPathComponent("\(name).png")
        
        guard let image = UIImage(contentsOfFile: url.path) else {
            return nil
        }
        return image
        
    } catch {
        print(error.localizedDescription)
    }
    
    return nil
}

Notice I create the URL with just .appendingPathComponent() and turning URL to path via url.path.

It works! So what's the question?

  • In Improving performance and stability when accessing the file system I've read that you better use the new appendingPathComponent(_:isDirectory:), that's good, will do.

  • Also url.path is deprecated in iOS18. Should I use url.path(percentEncoded:) instead? What should be the value of percentEncoded when accessing the local filesystem?

In this adjacent thread I've read:

Don't use UIImage(contentsOfFile:) either, because it's a path-based API. There's no URL-based equivalent, which is an Apple clue that should be doing something else.

Is this true? Then how should I store and load my images?

Just FYI, I create images like this:

private func generateThumbnail(name: String) {
    guard let drawingWidth = canvasGeo?.size.width,
          let drawingHeight = canvasGeo?.size.height else { return }
    
    let thumbnailRect = CGRect(x: 0, y: 0, width: drawingWidth, height: drawingHeight)
    
    Task {
        UITraitCollection(userInterfaceStyle: .light).performAsCurrent {
            let image = self.canvasView.drawing.image(from: thumbnailRect, scale: UIScreen.main.scale)
            guard let data = image.pngData() else { return } // -- HERE
            
            do {
                try FileManager.default.createDirectory(at: try FileManager.default.url(for: .applicationSupportDirectory,
                                                                                        in: .userDomainMask,
                                                                                        appropriateFor: nil,
                                                                                        create: true)
                    .appendingPathComponent("MyFolder"),
                                                        withIntermediateDirectories: true,
                                                        attributes: nil)
                
                let filename = "\(name).png"
                let url = try FileManager.default.url(for: .applicationSupportDirectory,
                                                      in: .userDomainMask,
                                                      appropriateFor: nil,
                                                      create: true)
                    .appendingPathComponent("MyFolder")
                    .appendingPathComponent(filename)
                
                try data.write(to: url, options: .atomic) // -- and HERE
                
            } catch {
                print(error.localizedDescription)
            }
            
        }
    }
}

My usecase — just save the user's PencilKit Canvas as an image and display it back to him on a different View. I'm on SwiftUI and iOS 16+.

Would be happy to learn the correct way, thanks!

Answered by DTS Engineer in 797832022

Regarding .appendingPathComponent(…), there is now appending(path:directoryHint:), which is even more explicit.

Note that in this situation the cost of the extra stat required to confirm whether a URL is a directory or a file will be completely drowned out by the cost of reading and decompressing the image.

Regarding, url.path, my preferred option is to simple ignore the deprecation and continue using that property O-: However, the answer to your direct question is that you should pass in false. Consider:

import Foundation

func main() {
    let u = URL(fileURLWithPath: "/Users/quinn/naïve.txt")
    print(u.path)
    print(u.path())
    print(u.path(percentEncoded: false))
    print(u.path(percentEncoded: true))
}

main()

which prints:

/Users/quinn/naïve.txt
/Users/quinn/nai%CC%88ve.txt
/Users/quinn/naïve.txt
/Users/quinn/nai%CC%88ve.txt

Is this true? Then how should I store and load my images?

It’s hard to say for sure. There are two common reasons for not replacing paths with file URLs:

  • That API is deprecated in some way, perhaps formally or perhaps informally.

  • We’ve just not got around to it!

It’s hard to tell these apart. For non-UI framework I have enough experience that I can offer up an opinion. For UI frameworks like this I’m on less solid ground. If you really want an answer, I recommend that you start a new thread in the UI Frameworks > UIKit topic area.

I will, however, point out that UIImage(contentsOfFile:) is most definitely a convenience method. There are lots of APIs for wrangling images, with lots of very cool features.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

I would recommend using an image loading library - https://github.com/onevcat/Kingfisher , which is designed to work with images in an optimized way.

Accepted Answer

Regarding .appendingPathComponent(…), there is now appending(path:directoryHint:), which is even more explicit.

Note that in this situation the cost of the extra stat required to confirm whether a URL is a directory or a file will be completely drowned out by the cost of reading and decompressing the image.

Regarding, url.path, my preferred option is to simple ignore the deprecation and continue using that property O-: However, the answer to your direct question is that you should pass in false. Consider:

import Foundation

func main() {
    let u = URL(fileURLWithPath: "/Users/quinn/naïve.txt")
    print(u.path)
    print(u.path())
    print(u.path(percentEncoded: false))
    print(u.path(percentEncoded: true))
}

main()

which prints:

/Users/quinn/naïve.txt
/Users/quinn/nai%CC%88ve.txt
/Users/quinn/naïve.txt
/Users/quinn/nai%CC%88ve.txt

Is this true? Then how should I store and load my images?

It’s hard to say for sure. There are two common reasons for not replacing paths with file URLs:

  • That API is deprecated in some way, perhaps formally or perhaps informally.

  • We’ve just not got around to it!

It’s hard to tell these apart. For non-UI framework I have enough experience that I can offer up an opinion. For UI frameworks like this I’m on less solid ground. If you really want an answer, I recommend that you start a new thread in the UI Frameworks > UIKit topic area.

I will, however, point out that UIImage(contentsOfFile:) is most definitely a convenience method. There are lots of APIs for wrangling images, with lots of very cool features.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

@DTS Engineer is correct. I ended up utilizing the new URL's static variable like URL.applicationSupportDirectory and the new method appending(path:directoryHint:) and the final code is:

func findImage(name: String) -> UIImage? {
    let url = URL.applicationSupportDirectory
        .appending(path: "MyFolder", directoryHint: .isDirectory)
        .appending(path: "\(name).png", directoryHint: .notDirectory)
    
    return UIImage(contentsOfFile: url.path(percentEncoded: false))
}

Writing this as an answer to checkout the final code.

Don't know whether there is a better way than UIImage(contentsOfFile:) but it does the job for now ¯\ _ (ツ) _ /¯

What is the current proper way to load an image from local filesystem?
 
 
Q