NSTextView end editing action like NSTextField - cocoa

How can I detect an end editing action on a NSTextView, just like on NSTextField ? I can not see it as an action or in its delegate.

You can register for notifications, such as NSTextDidEndEditingNotification.
If you want to use the delegate pattern, then you should check the NSTextDelegate protocol. Docs here. The method sent on end editing is textDidEndEditing:.
NSTextView is a subclass of NSText, so it is a good idea to check the docs for that class, too.
Example
NSTextView has a NSTextViewDelegate property you can use to be notified about changes. The delegate methods are mere convenience methods to obtain the "end editing" notification, unlike control:textShouldEndEditing you may know from NSTextField, for example.
class SomeViewController: NSViewController, NSTextViewDelegate {
var textView: NSTextView!
func loadView() {
super.loadView()
textView.delegate = self
}
func textDidBeginEditing(notification: NSNotification) {
guard let editor = notification.object as? NSTextView else { return }
// ...
}
func textDidEndEditing(notification: NSNotification) {
guard let editor = notification.object as? NSTextView else { return }
// ...
}
}

Related

I want to use it as a UIButton when I tap UITextField

I created a custom keyboard screen on tvOS.
If possible, tap on UITextField as it is, I want to transition to the custom keyboard view.
But tapping the UITextField always displays the system keyboard.
What should I do now?
1) Make the view controller implement this delegate: UITextFieldDelegate
class YourViewController: UIViewController, UITextFieldDelegate {
// ...
yourTextField.delegate = self
// ...
}
2) Return false in textFieldShouldBeginEditing, so the text field doesn't respond and the keyboard doesn't open. Instead, open yours or do whatever you want.
func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool {
// HERE, open your keyboard or do whatever you want
return false
}
textField.inputView = UIView()
class YourViewController: UIViewController,UITextFieldDelegate { }
First set your delegate for textfieldtextField.delegate = self, Then
func textFieldDidBeginEditing(_ textField: UITextField) {
textField.addTarget(self, action: #selector(gettextFieldFunction), for: UIControlEvents.touchDown)
}

PerformSegueWithIdentifier not making the transition

So I am trying to perform a segue as soon as the window loads without having to click a button or have any action performed. The segue's are linked from a view controller, but not linked from the window controller because I can only get one segue to link from the Windowcontroller at a time. I tried this code in both "windowWillLoad" and "windowDidLoad" directly, and with it's own function. It isn't making the transition. Does it always have to be activated with a button? Can I not use segue's that aren't linked directly to the WindowController itself? I basically want to be able to choose between two different view controllers on the launch of the cocoa mac app.
import Cocoa
class WindowOne: NSWindowController {
var i = 0
override func windowWillLoad() {
}
override func prepareForSegue(segue: NSStoryboardSegue, sender: AnyObject?) {
if i == 0 {
performSegueWithIdentifier("segone", sender: self)
} else {
performSegueWithIdentifier("segtwo", sender: self)
}
}
override func windowDidLoad() {
super.windowDidLoad()
}
}
The problem here is that the segue cannot be performed until the window has actually appeared - and at the point that windowDidLoad gets called it hasn't.
In iOS land we would handle this by calling performSegueWithIdentifier in the viewDidAppear method.
Here there are two options, as per this answer: viewWillAppear or viewDidAppear on NSWindowController
Either override the showWindow method:
import AppKit
class WindowOne: NSWindowController {
var i = 1
override func showWindow(_ sender: Any?) {
super.showWindow(sender)
if i == 0 {
performSegueWithIdentifier("segone", sender: self)
} else {
performSegueWithIdentifier("segtwo", sender: self)
}
}
}
Or for more fine-grained behaviour one might catch NSWindowDelegate calls. For example if you wanted the segue to occur whenever the window comes to the foreground you might implement windowDidBecomeKey:
import AppKit
class WindowOne: NSWindowController, NSWindowDelegate {
var i = 1
override func windowDidLoad() {
super.windowDidLoad()
self.window?.delegate = self
}
func windowDidBecomeKey(_ notification: Notification) {
if i == 0 {
performSegueWithIdentifier("segone", sender: self)
} else {
performSegueWithIdentifier("segtwo", sender: self)
}
}
}

Replace NSViewController under Swift2 Storyboard MAC OSX

I am new to Mac OSX and with Apple promoting the fact that the bodies of code are becoming similar decided to tell the folk I am writing code for we should be able to do a Mac OSX version. iPhone and iPad versions are all good and about to release second version so no issues there.
So I am subclassing NSWindowController to get access to the Toolbar and worked out how to remove and add items on the toolbar, but for the life of me I can not get one NSViewController (firstViewController) to dismiss and bring up the second NSViewController (secondViewController) in the same NSWindowController.
So the 2 issues are that
1. I want to be able to performSegueWithIdentifier from the first NSViewController in code and
2. bring up the second NSViewController by replacing the first NSViewController in the same NSWindowController.
If I add a button to the firstViewController and put a segue to the secondViewController then when I select the button the secondViewController comes up just fine but in a seperate window not the same NSWindowController that I want it to and the firstViewController does not get replaced but stays in the NSWindowController.
So I know the segue idea will work but its not working in code and when I do insert the segue from a button it works but into a seperate NSViewController that is not part of the NSWindowController.
I am trying to find some programming guide from Apple on the issue but no luck so far.
Here is an overview from my Storyboard:
Here is my NSWindowController subclassed and the func loginToMe2Team is trigger from the NSToolBar and its working just find as the print statements show up on the console.
import Cocoa
class me2teamWindowsController: NSWindowController {
#IBOutlet var mySignUp : NSToolbarItem!
#IBOutlet var myToolbar : NSToolbar!
let controller = ViewController()
override func windowDidLoad() {
super.windowDidLoad()
print("window loaded")
}
override func windowWillLoad() {
print("window will load")
}
#IBAction func logInToMe2Team(sender: AnyObject){
controller.LogIn() //THIS IS THE FUNC I AM TESTING WITH
}
#IBAction func signUpToMe2Team(sender: AnyObject){
controller.signUp()
}
Here is my NSViewController subclassed with the func LogIn. Its getting selected just fine but the performSegueWithIdentifier is not. And I did cut and past the Identifier to make absolutely sure it was the same.
import Cocoa
import WebKit
class ViewController: NSViewController {
#IBOutlet weak var theWebPage: WebView!
#IBOutlet weak var progressIndicator: NSProgressIndicator!
override func viewDidLoad() {
super.viewDidLoad()
let urlString = "https://thewebpage.com.au"
self.theWebPage.mainFrame.loadRequest(NSURLRequest(URL: NSURL(string: urlString)!))
}
override func viewDidAppear() {
}
func LogIn() {
print("I logged in")
self.performSegueWithIdentifier("goToTeamPage", sender: self)
//THIS IS THE BIT THATS NOT WORKING
}
func signUp() {
print("I have to sign up now")
}
override var representedObject: AnyObject? {
didSet {
}
}
func webView(sender: WebView!, didStartProvisionalLoadForFrame frame: WebFrame!)
{
self.progressIndicator.startAnimation(self)
}
func webView(sender: WebView!, didFinishLoadForFrame frame: WebFrame!)
{
self.progressIndicator.stopAnimation(self)
}
}
You need to use a custom segue class (or possibly NSTabViewController if it’s enough for your needs). Set the segue’s type to Custom, with your class name specified:
…and implement it. With no animation, it’s simple:
class ReplaceSegue: NSStoryboardSegue {
override func perform() {
if let src = self.sourceController as? NSViewController,
let dest = self.destinationController as? NSViewController,
let window = src.view.window {
// this updates the content and adjusts window size
window.contentViewController = dest
}
}
}
In my case, I was using a sheet and wanted to transition to a different sheet with a different size, so I needed to do more:
class ReplaceSheetSegue: NSStoryboardSegue {
override func perform() {
if let src = self.sourceController as? NSViewController,
let dest = self.destinationController as? NSViewController,
let window = src.view.window {
// calculate new frame:
var rect = window.frameRectForContentRect(dest.view.frame)
rect.origin.x += (src.view.frame.width - dest.view.frame.width) / 2
rect.origin.y += src.view.frame.height - dest.view.frame.height
// don’t shrink visible content, prevent minsize from intervening:
window.contentViewController = nil
// animate resizing (TODO: crossover blending):
window.setFrame(window.convertRectToScreen(rect), display: true, animate: true)
// set new controller
window.contentViewController = dest
}
}
}

AppDelegate for Cocoa app using Storyboards in Xcode 6

I have an existing OS X app, and after converting to Storyboards as the main interface, my app delegate is no longer being used. Before, the MainMenu.xib had an "App Delegate" object, and I could set its class to my app delegate. However, the Storyboard contains no such object.
How do I get my AppDelegate back and keep storyboards? I feel like I'm missing something obvious.
If you don't specify it to be a Document-Based Application, Xcode will create an AppDelegate.swift class and connect it up in the Application Scene for you.
As of right now (Xcode Beta-2), new Document-Based apps don't come with a stub AppDelegate.swift file. Instead, there's ViewController.swift and Document.swift. Worse, the Document.swift file incorrectly instantiates the same Main.storyboard for documents.
Here's one way I got it to work:
Create an AppDelegate class (e.g.: an NSObject that adopts the NSApplicationDelegate protocol)
Drag an Object object from the Object library, into the Application Scene of Main.storyboard and set it to the AppDelegate class.
Control-drag from the Application object in the Application Scene to the AppDelegate object, and connect up its delegate.
Remove everything else from the Main.storyboard and create a new Document.storyboard for the Document window. Change the Document.swift file to instantiate that Storyboard instead of Main.
If you want to have a main application window and/or a preferences window in addition to your document windows, create an Application.storyboard and/or Preferences.storyboard for those windows, and use the AppDelegate class to instantiate them. This way, the AppDelegate can customize the main window appearance and do other handy things, including receiving IBActions sent from any window in the app.
Here's a working example of an AppDelegate.swift file for a Document-Based app that also has a separate, single main Application window, and a non-modal Preference window:
// AppDelegate.swift
import Cocoa
class AppDelegate: NSObject, NSApplicationDelegate {
//init() {
// super.init()
// remove this if you don't use it
//}
var application: NSApplication? = nil
func applicationDidFinishLaunching(notification: NSNotification) {
application = notification.object as? NSApplication
let path = NSBundle.mainBundle().pathForResource("Defaults", ofType: "plist")
let defaults = NSDictionary(contentsOfFile:path)
NSUserDefaults.standardUserDefaults().registerDefaults(defaults)
NSUserDefaultsController.sharedUserDefaultsController().initialValues = defaults
NSUserDefaultsController.sharedUserDefaultsController().appliesImmediately = true
}
func applicationDidBecomeActive(notification: NSNotification) {
if application?.orderedDocuments?.count < 1 { showApplication(self) }
}
//func applicationWillFinishLaunching(notification: NSNotification) {
// remove this if you don't use it
//}
func applicationWillTerminate(notification: NSNotification) {
NSUserDefaults.standardUserDefaults().synchronize()
}
func applicationShouldOpenUntitledFile(app: NSApplication) -> Bool { return false }
func applicationShouldTerminateAfterLastWindowClosed(app: NSApplication) -> Bool { return false }
var applicationController: NSWindowController?
#IBAction func showApplication(sender : AnyObject) {
if !applicationController {
let storyboard = NSStoryboard(name: "Application", bundle: nil)
applicationController = storyboard.instantiateInitialController() as? NSWindowController
if let window = applicationController?.window {
window.titlebarAppearsTransparent = true
window.titleVisibility = NSWindowTitleVisibility.Hidden
window.styleMask |= NSFullSizeContentViewWindowMask
}
}
if applicationController { applicationController!.showWindow(sender) }
}
var preferencesController: NSWindowController?
#IBAction func showPreferences(sender : AnyObject) {
if !preferencesController {
let storyboard = NSStoryboard(name: "Preferences", bundle: nil)
preferencesController = storyboard.instantiateInitialController() as? NSWindowController
}
if preferencesController { preferencesController!.showWindow(sender) }
}
}
Here's another cheap and easy way to do it, if all you want to do is customize the appearance of the main window before it appears:
Make your own subclass of NSWindowController, and connect it up as the delegate of the main window.
Implement windowDidUpdate as a hook to the window so you can set up the desired options, but also remove the window delegate so the function only gets called once. This is all the code you need to make that work:
// WindowController.swift
import Cocoa
class WindowController: NSWindowController, NSWindowDelegate {
func windowDidUpdate(notification: NSNotification!) {
if let window = notification.object as? NSWindow! {
window.titlebarAppearsTransparent = true
window.titleVisibility = NSWindowTitleVisibility.Hidden
window.styleMask |= NSFullSizeContentViewWindowMask
window.delegate = nil }
}
}
Actually, an even easier way to apply those appearance options to the window, is by using Interface Builder to add them as User Defined Runtime Attributes to the NSWindow object. You don't need to subclass NSWindowController or write any code at all. Just plug in these values to the window object via the Identity Inspector pane:
Keypath: titlebarAppearsTransparent, Type: Boolean, Value: Checked
Keypath: titleVisibility, Type: Number, Value: 1
Keypath: styleMask, Type: Number, Value: 32783
Of course, you can't specify individual bits of the styleMask, but it's easy enough to add them all together and get a single number to specify the style.
With Storyboard architecture, and the new powers given to NSViewController, there's not as much need to subclass NSWindowController anymore.

Text change notification for an NSTextField

I would like to use the code from the answer to this question: How to observe the value of an NSTextField on an NSTextField in order to observe changes on the string stored in the NSTextField.
[[NSNotificationCenter defaultCenter]
addObserverForName:NSTextViewDidChangeSelectionNotification
object:self.textView
queue:[NSOperationQueue mainQueue]
usingBlock:^(NSNotification *note){
NSLog(#"Text: %#", self.textView.textStorage.string);
}];
The class used here is an NSTextView. I can't find a notification in NSTextField to use instead of NSTextViewDidChangeSelectionNotification.
Is there a notification available in NSTextField that can be used in this case ?
If you just want to detect when the value of a text field has changed, you can use the controlTextDidChange: delegate method that NSTextField inherits from NSControl.
Just connect the delegate outlet of the NSTextField in the nib file to your controller class, and implement something like this:
- (void)controlTextDidChange:(NSNotification *)notification {
NSTextField *textField = [notification object];
NSLog(#"controlTextDidChange: stringValue == %#", [textField stringValue]);
}
If you're creating the NSTextField programmatically, you can use NSTextField's setDelegate: method after creation to specify the delegate:
NSTextField *textField = [[[NSTextField alloc] initWithFrame:someRect] autorelease];
[textField setDelegate:self]; // or whatever object you want
Delegation is one of the fundamental design patterns used throughout Cocoa. Briefly, it allows you to easily customize the behavior of standard objects (in this case, user interface objects) without the complexity involved in having to subclass the object to add that additional behavior. For example, another lower-level way to detect when the text in a textfield has changed might be to create your own custom NSTextField subclass in which you override the keyDown: method that NSTextField inherits from NSResponder. However, subclassing like that is difficult because it can require that you have an intimate knowledge of the object's inheritance hierarchy. For more info, definitely check out the following:
Cocoa Fundamentals Guide: Delegates and Data Sources
Regarding what id <NSTextFieldDelegate> means: it means a generic object (id) that declares itself as conforming to the <NSTextFieldDelegate> protocol. For more info on protocols, see The Objective-C Programming Language: Protocols.
Sample GitHub project at: https://github.com/NSGod/MDControlTextDidChange
Xcode 9.2. with Swift 4.0.3.
The NSTextField must be connected via interface builder for this implementation to work.
import Cocoa
#objc public class MyWindowController: NSWindowController, NSTextFieldDelegate {
#IBOutlet weak var myTextField: NSTextField!
// MARK: - ViewController lifecycle -
override public func windowDidLoad() {
super.windowDidLoad()
myTextField.delegate = self
}
// MARK: - NSTextFieldDelegate -
public override func controlTextDidChange(_ obj: Notification) {
// check the identifier to be sure you have the correct textfield if more are used
if let textField = obj.object as? NSTextField, self.myTextField.identifier == textField.identifier {
print("\n\nMy own textField = \(self.myTextField)\nNotification textfield = \(textField)")
print("\nChanged text = \(textField.stringValue)\n")
}
}
}
Console output:
My own textField = Optional(<myApp.NSTextField 0x103f1e720>)
Notification textfield = <myApp.NSTextField: 0x103f1e720>
Changed text = asdasdasddsada
You should use NSTextFieldDelegate and implement controlTextDidChange. Test in macOS 10.14 and Swift 4.2
import Cocoa
class ViewController: NSViewController, NSTextFieldDelegate {
#IBOutlet weak var textField: NSTextField!
override func viewDidLoad() {
super.viewDidLoad()
textField.delegate = self
}
func controlTextDidChange(_ obj: Notification) {
let textField = obj.object as! NSTextField
print(textField.stringValue)
}
}
I believe you want to read up on the field editor which is essentially a (hidden) NSTextView that handles the text input to all the NSTextFields in a given window. The section on "Using Delegation and Notification With the Field Editor" should point you in the right direction.
In Swift it's
public override func controlTextDidChange(_ obj: Notification) {
}

Resources