How to access related files in the Mac Sandbox

I’m building an app which needs to load Objective C files.
If the user gives me access to the ‘.m’ file, I need to be able to open the ‘.h’ file.

You don’t get this ability without a bit of a dance. I didn’t find any clear tutorial or example, so I’m adding one now.

Firstly – define the related filetypes that you want to access by adding the file extension to document types in your info.plist
Here, I have added h as an an extension type, and set NSIsRelatedItemType to true.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>CFBundleDocumentTypes</key>
	<array>
        <dict>
            <key>CFBundleTypeExtensions</key>
            <array>
                <string>h</string>
            </array>
            <key>CFBundleTypeName</key>
            <string>Header File</string>
            <key>CFBundleTypeRole</key>
            <string>None</string>
            <key>NSIsRelatedItemType</key>
            <true/>
        </dict>
	</array>
</dict>
</plist>

Now, you can access the file – but only by using a FilePresenter.
And the file presenter _must_ be registered with NSFileCoordinator

class RelatedFilePresenter: NSObject, NSFilePresenter {
    var primaryPresentedItemURL: URL?
    var presentedItemURL: URL?
    var presentedItemOperationQueue: OperationQueue = OperationQueue.main
    
    enum Fail:Error {
        case unableToConstrucURL
    }
    
    init(primaryItemURL: URL) {
        self.primaryPresentedItemURL = primaryItemURL
    }

    func readDataFromRelated(fileExtension:String,
                             completion:(Result<Data, Error>)->Void) {
        presentedItemURL = primaryPresentedItemURL?.deletingPathExtension().appendingPathExtension(fileExtension)
        
        guard let url = presentedItemURL else {
            completion(.failure(Fail.unableToConstrucURL))
            return
        }
        
        let fc = NSFileCoordinator(filePresenter: self)
        var error:NSError?
        
        NSFileCoordinator.addFilePresenter(self)
               
        fc.coordinate(readingItemAt: url,
                      options: .withoutChanges,
                      error: &error) { (url) in
            do {
                let data = try Data(contentsOf: url, options: .uncached)
                completion(.success(data))
            } catch  {
                completion(.failure(error))
            }
        }
        
        if let error {
            completion(.failure(error))
        }
        
        NSFileCoordinator.removeFilePresenter(self)
    }
    
}

This class provides a method to read the data from a related file, but you could adapt it for any other operation.

You can then read the data with something like

let filePresenter = RelatedFilePresenter(primaryItemURL: mFileURL)
filePresenter.readDataFromRelated(fileExtension: "h") { result in
    if case .success(let data) = result {
        //do something with data
    }
}