BeginSheet doesn't show window - cocoa

I have simple code :
var openPanel = NSOpenPanel();
openPanel.beginSheet(this.View.Window, (obj) => {
//do stuff
openPanel.endSheet(this.View.Window);
});
Sometime the sheet window is not shown and makes a sound like the window is busy. Is there anything wrong in my code?
I call this code from one item of splitViewcontroller.

I just made this same error, and had trouble with it for quite some time. I was following along with the Apple guide:
Using the Open and Save Panels
The main issue is that the Apple docs show us using the Objective-C method:
[panel beginSheetModalForWindow:window completionHandler:^(NSInteger result){ }
I did an ad hoc translation into Swift, with help from Xcode autocomplete:
let openPanel = NSOpenPanel()
openPanel.beginSheet(window) { (modalResponse: NSApplication.ModalResponse) in
}
This does not work. When the code is run, the Window's title bar disappears + no panel is shown.
Use the correct Swift method instead, beginSheetModal:
openPanel.beginSheetModal(for: window) { (modalResponse: NSApplication.ModalResponse) in
}

Try this:
let panel = NSOpenPanel()
self.window?.beginSheet(panel, completionHandler: { (modalResponse: NSModalResponse) in
if modalResponse == NSModalResponseOK {
// do your stuff
}
})

Related

Mac NSTextField won't resign firstResponder

I have a window with some NSTextFields. When I click in one and edit the value and press return, I want the focus to go back to what it was before. I don't want the blue ring around the text field and I don't want further keystrokes going to that text field. I would have thought this would happen automatically.
I tried these, and none of them work
sender.resignFirstResponder()
sender.window?.makeFirstResponder(nil)
InspectorWindowController.window?.makeFirstResponder(nil)
AnotherWindowController.window?.becomeFirstResponder()
I'm doing these at the end of my IBAction associated with the text field. Maybe I have to do it from somewhere else?
Thanks
I figured this out. I guess the sent action is happening on another thread. So you have to call makeFirstResponder using Dispatch async.
DispatchQueue.main.async { //omg
sender.window?.makeFirstResponder(nil)
}
I needed to dismiss first responder in my SwiftUI macOS app and here what I found working in a way I need:
func controlTextDidEndEditing(_ obj: Notification) {
DispatchQueue.main.async {
guard let window = self.textField.window else {
return
}
// https://stackoverflow.com/questions/5999148/how-to-determine-whether-an-nssearchfield-nstextfield-has-input-focus
// We need to make sure that our text field is still first responder.
guard let textView = window.firstResponder as? NSTextView,
textView.delegate === self.textField else {
return
}
window.makeFirstResponder(nil)
}
}

How do I get the selected text from a WKWebView from objective-C

I have a WKWebView.
When the user right-clicks on it, I can customize a contextual menu in my objective-c method. I'd like to add a menu item only if the user has selected some text in the WKWebView. And of course I'll need to retrieve the selected text later on to process it.
How can I retrieve the selection from a WKWebView from objective-c, make sure it is only text and get that text ?
Thanks
Here is how I managed to do that. Not a perfect solution, but good enough.
General explanation
It seems that anything that happens inside the WKWebView must be managed in JavaScript. And Apple provides a framework for exchanging information between the JavaScript world and the Objective-C (or Swift) world. This framework is based on some messages being sent from the JavaScript world and caught in the Objective-C (or Swift) world via a message handler that can be installed in the WKWebView.
First step - Install the message handler
In the Objective-C (or Swift) world, define an object that will be responsible for receiving the messages from the JavaScript world. I used my view controller for that. The code below installs the view controller as a "user content controller" that will receive events named "newSelectionDetected" that can be sent from JavaScript
- (void)viewDidLoad
{
[super viewDidLoad];
// Add self as scriptMessageHandler of the webView
WKUserContentController *controller = self.webView.configuration.userContentController ;
[controller addScriptMessageHandler:self
name:#"newSelectionDetected"] ;
... the rest will come further down...
Second step - Install a JavaScript in the view
This JavaScript will detect selection change, and send the new selection through a message named "newSelectionDetected"
- (void) viewDidLoad
{
...See first part up there...
NSURL *scriptURL = .. URL to file DetectSelection.js...
NSString *scriptString = [NSString stringWithContentsOfURL:scriptURL
encoding:NSUTF8StringEncoding
error:NULL] ;
WKUserScript *script = [[WKUserScript alloc] initWithSource:scriptString
injectionTime:WKUserScriptInjectionTimeAtDocumentEnd
forMainFrameOnly:YES] ;
[controller addUserScript:script] ;
}
and the JavaScript:
function getSelectionAndSendMessage()
{
var txt = document.getSelection().toString() ;
window.webkit.messageHandlers.newSelectionDetected.postMessage(txt) ;
}
document.onmouseup = getSelectionAndSendMessage ;
document.onkeyup = getSelectionAndSendMessage ;
document.oncontextmenu = getSelectionAndSendMessage ;
Third step - receive and treat the event
Now, every time we have a mouse up or a key up in the WKWebView, the selection (possibly empty) will be caught and send to the Objective-C world through the message.
We just need a handler in the view controller to handle that message
- (void) userContentController:(WKUserContentController*)userContentController
didReceiveScriptMessage:(WKScriptMessage*)message
{
// A new selected text has been received
if ([message.body isKindOfClass:[NSString class]])
{
...Do whatever you want with message.body which is an NSString...
}
}
I made a class which inherits from WKWebView, and has a NSString property 'selectedText'. So what I do in this handler, is to store the received NSString in this property.
Fourth step - update the contextual menu
In my daughter class of WKWebView, I just override the willOpenMenu:WithEvent: method to add a menu item if selectedText is not empty.
- (void) willOpenMenu:(NSMenu*)menu withEvent:(NSEvent*)event
{
if ([self.selectedText length]>0)
{
NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:#"That works !!!"
action:#selector(myAction:)
keyEquivalent:#""] ;
item.target = self ;
[menu addItem:item] ;
}
}
- (IBAction) myAction:(id)sender
{
NSLog(#"tadaaaa !!!") ;
}
Now why isn't that ideal? Well, if your web page already sets onmouseup or onkeyup, I override that.
But as I said, good enough for me.
Edit: I added the document.oncontextmenu line in the JavaScript, that solved the strange selection behavior I sometimes had.
Swift 5 translation
webView.configuration.userContentController.add(self, name: "newSelectionDetected")
let scriptString = """
function getSelectionAndSendMessage()
{
var txt = document.getSelection().toString() ;
window.webkit.messageHandlers.newSelectionDetected.postMessage(txt);
}
document.onmouseup = getSelectionAndSendMessage;
document.onkeyup = getSelectionAndSendMessage;
document.oncontextmenu = getSelectionAndSendMessage;
"""
let script = WKUserScript(source: scriptString, injectionTime: .atDocumentEnd, forMainFrameOnly: true)
webView.configuration.userContentController.addUserScript(script)
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
// Use message.body here
}
One only needs to evaluate simple js script
NSString *script = #"window.getSelection().toString()";
using evaluateJavaScript method
[wkWebView evaluateJavaScript:script completionHandler:^(NSString *selectedString, NSError *error) {
}];
The Swift version
let script = "window.getSelection().toString()"
wkWebView.evaluateJavaScript(script) { selectedString, error in
}

Simple displaying of second window: OS X & Swift

I'm trying to find how to bring up a second view/window after pushing a button on my primary window. I have read about segues and I can get the first window to display the second but the second is not connected to a view controller so I can't add any code to any controls on the second view. Try as I might I cannot create a SecondViewController.swift file and connect it to a window controller or a view controller. The tutorials I have found all deal with iOS and I want OS X which means there are just enough differences to keep me from figuring this out.
Can anyone show me how to do this?
Ta,
A.
First make new file like:
After that, put these codes in your classes and that should do it.
class SecondWindowController: NSWindowController {
convenience init() {
self.init(windowNibName: "SecondWindowController")
}
}
class ViewController: NSViewController {
private var secondWindowController: SecondWindowController?
#IBAction func showSecondWindow(sender: AnyObject) {
if secondWindowController == nil {
secondWindowController = SecondWindowController()
}
secondWindowController?.showWindow(self)
}
}

Opening NSSavePanel as sheet

I am using XCode7 beta2 to play around with Swift 2. Trying to use a File-Selection Dialog (NSSavePanel) brought me into some trouble.
Running the following code by clicking the associated button won't bring up the dialog as a sheet (not at all) but make my window's decoration disappear, leaving it in a broken state where otherwise functional sheets open as dialogs without decoration. Using the call to the deprecated API beginSheetModalForWindow, like in the commented line, works like expected.
#IBAction func openFileClicked(sender: AnyObject) {
let openPanel = NSSavePanel()
openPanel.canCreateDirectories = true
//openPanel.beginSheetModalForWindow(self.view.window!, completionHandler: {
openPanel.beginSheet(self.view.window!, completionHandler: {
(result) -> Void in
print("opening:\(result)" )
})
}
Is my code broken somehow or is there a issue with the API I am calling.

Using threads to update UI with Swift

I'm developing a software with Xcode 6 using Swift. When I press a button, my code takes some informations from the web and writes them on my NSWindow.
So imagine something like this:
#IBAction func buttonPressed(sender: AnyObject)
{
for page in listOfPages //Number of pages is 40.
{
var label: NSTextField = NSTextField();
label.stringValue = getInformetionsFromPage(page)
/*
some code to add label in the window here
*/
}
}
The problem is that once I click the button, it takes a while before I see the list of results, in the main time the app is frozen. That's because I'm not using any threads to handle this problem. How could I implement threads to see every label updated every step of the loop? I'm new with threads and Swift, so I would need some help!
Thank you guys.
Swift 3.0 + version
DispatchQueue.main.async() {
// your UI update code
}
Posted this because XCode cannot suggest the correct syntax from swift 2.0 version
There is GCD. Here is a basic usage:
for page in listOfPages {
var label = NSTextField()
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
let result = getInformationFromPage(page)
dispatch_async(dispatch_get_main_queue()) {
label.stringValue = result
}
}
}
dispatch_async function asynchronously runs the block of code on the given queue. In first dispatch_async call we dispatch the code to run on background queue. After we get result we update label on main queue with that result

Resources