NSImage (contentsOfFile) constructor problem: nil returned

As a newbie to the big wide world of Swift and Xcode I am grappling with hopefully a simple, easy to solve problem with a macOS app in the very initial stage of development. However, thus far I have spent ages on it and am getting nowhere!


I have a ViewController (descended from NSViewController) which has an NSImageView which, on application start-up, should show (via an NSImage) a picture of a horse read from disk (from file /tmp/horse2.jpg). However, the image doesn't show.


As per the code listing below, the ViewController method setImageOfImageViewer() is responsible for reading the contents of the file /tmp/horse2.jpg and setting the image of the NSImageView instance (imageView).


The method setImageOfImageViewer() is called from viewDidLoad() and also later, whenever the user presses a button.


Among others, jpg files can be dragged from Finder on to the NSImageView. These images do show (every time). However, when the first of these files is so dragged and dropped, the following problem is reported (just once - and only in response to the first drag and drop):

2018-02-14 08:05:05.453636+1030 Image_Digitiser[4835:117719] MessageTracer: load_domain_whitelist_search_tree:73: Search tree file's format version number (0) is not supported

2018-02-14 08:05:05.453681+1030 Image_Digitiser[4835:117719] MessageTracer: Falling back to default whitelist


Pressing the button repeatedly fails to show the horse image, until AFTER the horse image file itself is dragged and dropped on to the NSImageView. After dragging other images on to the NSImageView, clicking the button does now reshow the horse image (repeatedly).


Grateful if you can you help me overcome this problematic behaviour.


For your information, the class ViewController is shown below. The ViewController simply contains three outlets: an NSTextField, NSButton and NSImageView plus one action to respond to the button being clicked.




import Cocoa



class ViewController: NSViewController {

@IBOutlet weak var lblHello: NSTextField!

@IBOutlet weak var btnChangeLabel: NSButton!

@IBOutlet weak var imageView: NSImageView!



@IBAction func btnChangeLabelOnClick(_ sender: Any) {

lblHello.stringValue = "Horse image should now be showing!"

setImageOfImageViewer()

}


// Problem: This routine succeeds in showing the image "/tmp/horse2.jpg"

// ONLY AFTER the file is dragged on to the ImageView from Finder!!

fileprivate func setImageOfImageViewer() {

let imagePath = "/tmp/horse2.jpg"

//let imageUrl = NSURL(fileURLWithPath: imagePath)

//let myImage = NSImage(named: NSImage.Name("test")) // Works - does show that image on app start-up.

//let myImage = NSImage(byReferencingFile: imagePath) // Doesn't show the image on app start-up.

//let myImage = NSImage(byReferencing: imageUrl as URL) // Doesn't show the image on app start-up.

let myImage = NSImage(contentsOfFile: imagePath) // Doesn't show the image on app start-up.

print("myImage?.isValid = \(myImage?.isValid)") // Prints nil when image doesn't show, Optional(true) otherwise


imageView.image = myImage

imageView.isEditable = true

}


override func viewDidLoad() {

super.viewDidLoad()

lblHello.stringValue = "Hello World"

btnChangeLabel.title = "Show horse image"

setImageOfImageViewer()

}


override func viewWillAppear() {

super.viewWillAppear()

preferredContentSize = view.fittingSize

}


override var representedObject: Any? {

didSet {

// Update the view, if already loaded.

}

}

}

Accepted Reply

Sorry, I don't know the for-sure answer offhand, but the problem is likely your use of a path starting from "/tmp". IIRC, if your app is sandboxed (the Xcode default), the actual /tmp directory is outside your app's sandbox, and can't be referenced without explicit user permission. That's why dragging works: since the user dragged the file, it's implicitly regarding as giving your app to use the dragged image.


If you're going to have a default image, you should add it to your project so that it goes in the resources subdirectory of the final built app bundle. From there, you should be able to get it via the "NSImage(named:)" method, or get the URL to the file (in preference to the path to the file) via one of the Bundle methods.

Replies

Sorry, But I forgot to mention these details regarding the development environment that I'm using:

  • MacOS 10.13.3
  • Xcode 9.2 (9C40b)
  • Swift 4.0

Sorry, I don't know the for-sure answer offhand, but the problem is likely your use of a path starting from "/tmp". IIRC, if your app is sandboxed (the Xcode default), the actual /tmp directory is outside your app's sandbox, and can't be referenced without explicit user permission. That's why dragging works: since the user dragged the file, it's implicitly regarding as giving your app to use the dragged image.


If you're going to have a default image, you should add it to your project so that it goes in the resources subdirectory of the final built app bundle. From there, you should be able to get it via the "NSImage(named:)" method, or get the URL to the file (in preference to the path to the file) via one of the Bundle methods.

Your comment about sandboxing is interesting - and something that I was unaware of (as I start this long journey into realm of Swift, Xcode and Cocoa). I'll research that lead further!


Ultimately, I was intending to be able to read an arbitrary image at runtime, perhaps via a File > Open mechanism (although drag-and-drop does work).


Perhaps I'll need to get rid of sandboxing, since my problem occurred (at the time that ViewController.viewDidLoad() was called) no matter what folder the image resided in. (For this post, I just used the folder /tmp for simple, anonymous, illustrative purposes.)


It seems that initialising an NSImage via NSImage(contentsOfFile: <pathname>) is doomed to fail if the application is sandboxed - by design, I guess!


Thank you for your help, Quincey.

Quincey, I can now confirm that you are 100% correct. I looked at the <appName>.entitlements file via the Xcode Project Navigator pane and noted that the item "App Sandbox" (a Boolean) was set (by default) to YES. I changed this setting to NO and then the app behaved as I had expected.


Thank you once again for your help.