I tried to bind a NSTextView to a NSAttributedString declared in Document.swift for saving the text to a file but it doesn't do anything.
Here is the code in ViewController.swift :
var document: Document? {
return view.window?.windowController?.document as? Document
}
And the Model Key Path:
self.document.text
Related
I am creating a window controller that I want to add to my Document object in the Document's makeWindowControllers function. However, after I have added this window controller, I can not resolve the windowController's Document object. This property should be set by my Document on the window controller in the addWindowController call. Any ideas as to why this might be the case?
let movieStoryboard = NSStoryboard(name: NSStoryboard.Name("MovieWindowStoryboard"), bundle: nil)
movieWindowController = movieStoryboard.instantiateController(withIdentifier: NSStoryboard.SceneIdentifier("MovieWindowController")) as? NSWindowController
self.addWindowController(movieWindowController)
movieWindowController.window?.performClose(self)
My document-based cocoa app has a shared inspector window whose contents change depending on which document is active.
The inspector window controller is a shared singleton, instantiated form its storyboard on demand.
The document class simply creates its main window from a storyboard, and becomes the window's delegate:
class Document: NSDocument {
override func makeWindowControllers() {
let storyboard = NSStoryboard(name: "Main", bundle: nil)
guard let windowController = storyboard.instantiateController(withIdentifier: "Document Window Controller") as? NSWindowController else {
fatalError("Storyboard Inconsistency!")
}
windowController.window?.delegate = self
self.addWindowController(windowController)
}
Whenever a document's main window becomes active, it adds the inspector's window controller to its own:
extension Document: NSWindowDelegate {
func windowDidBecomeMain(_ notification: Notification) {
self.addWindowController(InspectorWindowController.shared)
}
}
(this also updates the window controller's document property)
In anticipation to the case where the last document is closed, I also added:
func windowWillClose(_ notification: Notification) {
self.removeWindowController(InspectorWindowController.shared)
}
(This is only needed for the last document, since otherwise the new active document takes over and the window controller is automatically removed from the closing document once it is added to the newly activated document)
The Inspector itself overrides the property document and the method windowTitle(forDocumentDisplayName:), in order to keep up with the active document:
class InspectorWindowController
override var document: AnyObject? {
didSet {
// (Update view controller's contents)
}
}
override func windowTitle(forDocumentDisplayName displayName: String) -> String {
if document == nil {
return "Inspector - No Active Document"
} else {
return "Inspector - \(displayName)"
}
}
The problem is, when I close the last open document window, the inspector's window title stays at the (custom) title set for the last document. That is, when the inspector window controller's document property is set to nil, windowTitle(forDocumentDisplayName:) is not called.
Even calling synchronizeWindowTitleWithDocumentName() does not help, since the docs clearly mention that:
Does nothing if the window controller has no associated document or
loaded window. This method queries the window controller’s document to
get the document’s display name and full filename path, then calls
windowTitle(forDocumentDisplayName:) to get the display name to show
in the window title.
(emphasis mine)
I can reset the Inspector's content to the "No document" state; How can I do the same for the window title?
OK, I found the (silly and obvious) answer:
override var document: AnyObject? {
didSet {
// (Update view controller's contents, etc...)
if document == nil {
self.window?.title = "Inspector - No Active Document"
}
}
}
I'm not sure if this is the correct way of dealing with it, but it does get the job done.
I will accept any better answer though.
OK, so I'm working on this OS X application in Swift with Xcode 7 and I can't figure out how to save a string. I want to save a string, so if I write I something in a text field in my application, it will save that string and if I close my application and reopen it that string will still exist and will contain everything that it had before I closed my application.
I hope I make sense.
You can use NSUseDefaults to make your field persist through launches. Just ctrl-click your text field and add a IBAction to it that will be called when the user ends editing the text field. Just save the text field (sender) stringValue there, connect one IBOutlet also to your text field and use NSUserDefaults stringForKey method to load your string back inside viewDidLoad method:
import Cocoa
class ViewController: NSViewController {
#IBOutlet var textField: NSTextField!
override func viewDidLoad() {
super.viewDidLoad()
textField.stringValue = NSUserDefaults().stringForKey("textFieldKey") ?? "default value"
}
override var representedObject: AnyObject? {
didSet {
}
}
#IBAction func textFieldAction(sender: NSTextField) {
NSUserDefaults().setObject(sender.stringValue, forKey: "textFieldKey")
}
}
If you just want to save simple data, such as a few strings, you can use NSUserDefaults. Check out a tutorial here.
If you want to save complicated data, you should use Core Data. It seems harder to learn at the beginning but you will enjoy its convenience and performance later. I learned Core Data here.
I am having trouble detecting a user's double click in swift, I want to detect when they double click on an NSTextField.
func someFunc() {
y.target = self
y.action = "editLabel:"
}
#IBAction func editLabel(obj:AnyObject?) {
NSLog("here");
}
The above code doesn't work, I can't seem to find the basic documentation that shows how to add event handlers. Is there a simpler way to do this?
I guess your text field is a label, not an editable text field in its normal state. Starting with OS X 10.10 (Yosemite), you can use NSClickGestureRecognizer:
func applicationDidFinishLaunching(aNotification: NSNotification) {
let gesture = NSClickGestureRecognizer()
gesture.buttonMask = 0x1 // left mouse
gesture.numberOfClicksRequired = 2
gesture.target = self
gesture.action = "editLabel:"
myLabel.addGestureRecognizer(gesture)
}
func editLabel(sender: NSGestureRecognizer) {
if let label = sender.view as? NSTextField {
print("Hello world")
}
}
A text field does not handling editing, as such. When a text field has focus, a text view is added to the window, overlapping the area of the text field. This is called the "field editor" and it is responsible for handling editing.
It seems the most likely place for you to change the behavior of a double-click is in the text storage object used by that text view. NSTextStorage inherits from NSMutableAttributedString which inherits from NSAttributedString which has a -doubleClickAtIndex: method. That method returns the range of the text that should be selected by a double-click at a particular index.
So, you'll want to implement a subclass of NSTextStorage that overrides that method and returns a different result in some circumstances. NSTextStorage is a semi-abstract base class of a class cluster. Subclassing it requires a bit more than usual. You have to implement the primitive methods of NSAttributedString and NSMutableAttributedString. See the docs about it.
There are a few places to customize the field editor by replacing its text storage object with an instance of your class:
You could implement a custom subclass of NSTextFieldCell. Set your text field to use this as its cell. In your subclass, override -fieldEditorForView:. In your override, instantiate an NSTextView. Obtain its layoutManager and call -replaceTextStorage: on that, passing it an instance of your custom text storage class. (This is easier than putting together the hierarchy of objects that is involved with text editing, although you could do that yourself.) Set the fieldEditor property of the text view to true and return it.
In your window delegate, implement -windowWillReturnFieldEditor:toObject:. Create, configure, and return an NSTextView using your custom text storage, as above.
PROBLEM: Getting a Thread 1: EXC_BAD_ACCESS (code=1, address=0x18) when setting the previewItem of a QLPreviewView object laid out in a window.
GOAL: Create an app with a preview of file contents and controls below for changing properties of the file being previewed. The preview must be tied to the size of the window and react to resizing of the window.
SETUP:
Using Xcode 6.1.1 I created a default OS X Cocoa Application using Swift, and Storyboards.
Using Storyboards for layout, there doesn't appear to be a QLPreviewView component in the Object Library, so I used a Custom View object and set the Class to QLPreviewView. I then set the constraints to allow the preview view to resize with the window frame.
I then linked the Custom View of class QLPreviewView into the ViewController.swift file as a member variable. In the viewDidLoad() method I added a set the previewItem of the QLPreviewView object to an implementation of the QLPreviewItem protocol. I also added an import Quartz.
Here is the source for ViewController.swift, as generated by Xcode, then modified by me to add the setup for QLPreviewView.
//
// ViewController.swift
// PreviewTest
//
// Created by Derek on 2015-03-02.
// Copyright (c) 2015 Derek. All rights reserved.
//
import Cocoa
import Quartz
class ViewController: NSViewController {
#IBOutlet var preview: QLPreviewView!
override func viewDidLoad() {
super.viewDidLoad()
preview.previewItem = {final class PreviewItem : NSObject, QLPreviewItem {
var previewItemURL: NSURL! {return NSURL(fileURLWithPath: "/Users/derek/Pictures/Scan.jpeg")}
var previewItemTitle: String! {return "Test" }
}
return PreviewItem()
} ()
// Do any additional setup after loading the view.
}
override var representedObject: AnyObject? {
didSet {
// Update the view, if already loaded.
}
}
}
The line where preview.previewItem is set gets the EXC_BAD_ACCESS error described at the top when the application is run.
I have successfully created a QLPreviewView and embedded it in a window, however I did it without using the storyboards or constraints, so it's size and position is not managed by the window, which is what I want.
The inline class PreviewItem works fine. I have tried switching it to a standalone class and then created it to a variable, then set QLPreviewView.previewItem to the variable. This fails in the same way as the set to previewItem doing it inline.
The inline class PreviewItem also works fine in the non storyboard case and the file scan.jpeg is shown as expected.
Other UI objects, like Text Field work fine when I set them up in a similar way.
I'm hoping to find out what I'm doing wrong here and how to correct it. Or suggestions on how to approach this goal in a different way.
Try defining PreviewItem as a nested class instead of the inline definition. JavaScript might define and invoke it with () like your program, but I think in Swift the nested class is more general.
class ViewController: NSViewController {
private final class PreviewItem : NSObject, QLPreviewItem {
var previewItemURL: NSURL! {return NSURL(fileURLWithPath: "/Users/derek/Pictures/Scan.jpeg")}
var previewItemTitle: String! {return "Test" }
}
#IBOutlet var preview: QLPreviewView!
override func viewDidLoad() {
super.viewDidLoad()
preview.previewItem = PreviewItem()
self.view.addSubview(preview)
// Do any additional setup after loading the view.
}
...
}
Also make sure the preview property is connected to the view by Interface Builder.