13

I want to be able to open an image in Swift. This is my first Swift project.

@IBAction func SelectFileToOpen(sender: NSMenuItem) {
    var openPanel = NSOpenPanel();
    openPanel.allowsMultipleSelection = false;
    openPanel.canChooseDirectories = false;
    openPanel.canCreateDirectories = false;
    openPanel.canChooseFiles = true;
    let i = openPanel.runModal();
    if(i == NSOKButton){
        print(openPanel.URL);
        var lettersPic = NSImage(contentsOfURL: openPanel.URL!);
        imageView.image = lettersPic;

    }
}

Output of my NSLog when using the open panel

Optional(file:///Users/ethansanford/Desktop/BigWriting.png)
fatal error: unexpectedly found nil while unwrapping an Optional value

How can I allow the user to open a png file of interest. When I specifying the same file in the code everything works well. An example of me indicating which file to open in the code without using the open file panel and acting as a user:

let pictureURl = NSURL(fileURLWithPath: "///Users/ethansanford/Desktop/BigWriting.png");
var lettersPic = NSImage(contentsOfURL: pictureURl!);
imageView.image = lettersPic; 

Is there a problem with the format of my URL or something? Any help would be appreciated.

Belphegor
  • 4,456
  • 11
  • 34
  • 59
jiminybob99
  • 857
  • 1
  • 8
  • 15
  • If its is difficult to find anything wrong with my code. I would be happy with an example, any example of nsopenpanel being used to open a png that worked. From what I can tell there is another way to use nsopenpanel using a completion handler – jiminybob99 Jan 18 '15 at 16:43

3 Answers3

20

Add a new file to your project (swift source file) and add this extension there

Xcode 9 • Swift 4

extension NSOpenPanel {
    var selectUrl: URL? {
        title = "Select Image"
        allowsMultipleSelection = false
        canChooseDirectories = false
        canChooseFiles = true
        canCreateDirectories = false
        allowedFileTypes = ["jpg","png","pdf","pct", "bmp", "tiff"]  // to allow only images, just comment out this line to allow any file type to be selected 
        return runModal() == .OK ? urls.first : nil
    }
    var selectUrls: [URL]? {
        title = "Select Images"
        allowsMultipleSelection = true
        canChooseDirectories = false
        canChooseFiles = true
        canCreateDirectories = false
        allowedFileTypes = ["jpg","png","pdf","pct", "bmp", "tiff"]  // to allow only images, just comment out this line to allow any file type to be selected
        return runModal() == .OK ? urls : nil
    }
}

In your View Controller:

class ViewController: NSViewController {
    @IBOutlet weak var imageView: NSImageView!
    @IBAction func saveDocument(_ sender: NSMenuItem) {
        print("SAVE")
    }
    @IBAction func newDocument(_ sender: NSMenuItem) {
        print("NEW")
    }
    // connect your view controller to the first responder window adding the openDocument method
    @IBAction func openDocument(_ sender: NSMenuItem) {
        print("openDocument ViewController")
        if let url = NSOpenPanel().selectUrl {
            imageView.image = NSImage(contentsOf: url)
            print("file selected:", url.path)
        } else {
            print("file selection was canceled")
        }
    }
}
Leo Dabus
  • 229,809
  • 59
  • 489
  • 571
  • You should always name your functions starting with a lowercase letter. So you should change SelectFileToOpen to selectFileToOpen – Leo Dabus Jan 18 '15 at 22:17
  • creation of the main.swift file seems rather complicated. apparently if you have a main.swift file the AppDelegate.swift file no longer works. Im curious were you learned this. What books do you read? – jiminybob99 Jan 18 '15 at 23:30
  • Are you using storyboards? – Leo Dabus Jan 19 '15 at 00:55
  • Yes I am using storyboards. Should I be? – jiminybob99 Jan 19 '15 at 01:03
  • I think you should try first doing it without storyboards. It will make your live a lot easier. – Leo Dabus Jan 19 '15 at 01:05
  • I did i without storyboards and it worked flawlessly. Thanks Leonardo. You were crazy patient with me. – jiminybob99 Jan 19 '15 at 01:46
  • @LeoDabus might i ask you if you have got a solution for swift 2.0? i can't get import Cocao or import AppKit, hence NSOpenPanel is not there? – alex Sep 14 '15 at 18:25
  • 1
    @alex i will update the answrer when i get back to my mac pro – Leo Dabus Sep 14 '15 at 19:51
  • This seems to generate an error: (Xcode 8.2 for mac app) [NSVBOpenPanel selectUrl]: unrecognized selector sent to instance 0x10202a3d0 [General] -[NSVBOpenPanel selectUrl]: unrecognized selector sent to instance 0x10202a3d0 – UKDataGeek Jan 16 '17 at 01:00
  • @MobileBloke probably not related to my code. Feel free to open a new question with a minimum verifiable example and if you would like me to take a look at it just post the link to it here – Leo Dabus Jan 16 '17 at 01:09
  • Here is a gist with the sample code that seems to work fine and doesn't generate the error : https://gist.github.com/grantkemp/a0c6591eea2d218071cf8fcb96265170 – UKDataGeek Jan 16 '17 at 01:23
  • You are probably forcing to unwrap the URL result which is an optional – Leo Dabus Jan 16 '17 at 01:28
  • +1 for the answer @LeoDabus do you have any example of how to import multiple selected files to append tableView data. – Joe Nov 21 '17 at 17:32
  • @Joe Added a selectedUrls properties to the extension NSOpenPanel. Note that it will return an array or urls instead of a single one. You can use those urls as the data source of your tableview. – Leo Dabus Nov 21 '17 at 20:26
  • thanks @LeoDabus but i am getting -- ternary operator error -- on return value `Result values in '? :' expression have mismatching types 'URL?' and '_'` .But i found the solution to use `runModal() == NSModalResponseOK ? urls : nil` instead. Thanks again... – Joe Nov 22 '17 at 02:43
  • @Joe are you sure you are using the code I posted above. Looks like you have changed the return type to `[URL]` instead of `[URL]?`You would need to return an empty array instead of nil also. – Leo Dabus Nov 22 '17 at 02:54
  • @LeoDabus No, I replaced my entire code with your answer and i was getting ternary operator error once i replaced `.OK` to `NSModalResponseOK` everything starts worked. Maybe,Swift 4 causing this issue. Sorry, I am new to macOS dev...thanks. – Joe Nov 22 '17 at 03:18
  • @Joe Yes Swift 3 you need to use `NSModalResponseOK` instead of `.OK` – Leo Dabus Nov 22 '17 at 03:19
  • @LeoDabus Can you help me on this https://stackoverflow.com/questions/47495278/how-to-insert-a-row-in-tableview-without-using-reloaddata-function-in-cocoa?noredirect=1#comment81990783_47495278 – Joe Nov 28 '17 at 05:25
  • Regarding the playback stopping you can make a Singleton. check this https://stackoverflow.com/questions/47477776/ble-peripheral-disconnects-when-navigating-to-different-viewcontroller/47481780#47481780 – Leo Dabus Nov 28 '17 at 05:29
  • About the selection add another property to the singleton with the index of the media playing and reselect after reloading it – Leo Dabus Nov 28 '17 at 05:31
  • After trying to implement this if you still have issues, edit your question with your attempt and let me know – Leo Dabus Nov 28 '17 at 05:33
  • Minor detail. I think this should be a func not a var as it is not a (computed) property of the NSOpenPanel. I changed it to: `func selectImage() -> URL?` But clearly, no need to start a war. :-) – Wizard of Kneup Aug 15 '19 at 19:38
5

Hmmm... I didn't see anything wrong necessarily with your code, so I test ran this code (selecting a PNG file on my desktop):

let openPanel = NSOpenPanel()
openPanel.allowsMultipleSelection = false
openPanel.canChooseDirectories = false
openPanel.canCreateDirectories = false
openPanel.canChooseFiles = true
let i = openPanel.runModal()
if(i == NSModalResponseOK){
    print(openPanel.URL)
    let lettersPic = NSImage(contentsOfURL: openPanel.URL!)
    print(lettersPic)      
}

What I got was:

Optional(file:///Users/jwlaughton/Desktop/flame%2012-32.png)
Optional( "NSBitmapImageRep 0x6000000a4140 Size={1440, 900} ColorSpace=(not yet loaded) BPS=8 BPP=(not yet loaded) Pixels=1440x900 Alpha=NO Planar=NO Format=(not yet loaded) CurrentBacking=nil (faulting) CGImageSource=0x608000160cc0" )>)

Which seems OK to me.

Maybe the issue is you need to say:

imageView.image = lettersPic!; 

EDIT:

So to test further, I extended the test code a little to:

if(i == NSOKButton){
    print(openPanel.URL);

    var lettersPic = NSImage(contentsOfURL: openPanel.URL!);
    print(lettersPic);
    let view:NSImageView = NSImageView();
    view.image = lettersPic

    print(view)
}

Everything still works OK. Sorry I couldn't duplicate your problem.

shim
  • 9,289
  • 12
  • 69
  • 108
jwlaughton
  • 905
  • 1
  • 6
  • 11
1

This is the code that ended up working for me. I had to disable story boards. I had to make a class called Main. This is not to be confused with a special class called main.swift which replaces appdelegate.swift.And i also had to import Cocoa. Then I had to Specify that main inhereted from nsobject. This was so I could first make the connections between interface builder and put In ibactions and outlets in my Main.swift file.

//
//  Main.swift
//  Open
//
//  Created by ethan sanford on 2015-01-18.
//  Copyright (c) 2015 ethan D sanford. All rights reserved.
//

import Foundation
import Cocoa


class Main: NSObject{

    @IBOutlet var imageWell: NSImageCell!
    var myURL = NSURL(fileURLWithPath: "")



    @IBAction func main(sender: AnyObject) {


        imageWell.image = NSImage(byReferencingURL: myURL!)

    }


    @IBAction func open(sender: AnyObject) {
        var openPanel = NSOpenPanel();
        openPanel.allowsMultipleSelection = false;
        openPanel.canChooseDirectories = false;
        openPanel.canCreateDirectories = false;
        openPanel.canChooseFiles = true;
        let i = openPanel.runModal();
        if(i == NSOKButton){
            print(openPanel.URL);
            myURL = openPanel.URL;
        }


    }


}

it works a little strangely you have to chose your file click open. Then hit the button connected with @IBAction func main(sender: AnyObject) {

jiminybob99
  • 857
  • 1
  • 8
  • 15
  • interesting seeing how its meant to be done. I've been going off of youtube videos mostly and a course in visual basic i took in high school. thanks for the lesson. – jiminybob99 Jan 19 '15 at 04:01