Swift OSX - update NSTextView from another class and maybe thread - swift

I'm brand new to Swift programming and my background has always been procedural languages so am coming to terms with both the language and some OO concepts.
I'm building an OSX application that has 1 GUI window, with 2 input fields and a button - when the button is clicked, the program takes image files from the folder entered in 1 of the text boxes and copies them to the folder entered in the second text box. On this main window us a NSTextView that acts as an output log so the user knows the progress of the copies.
ViewController.swift
utilities.swift
ImageCopy.swift
The 3 controls are defined in my ViewController
#IBOutlet var logView: NSTextView!
#IBOutlet weak var sourceFolder: NSTextField!
#IBOutlet weak var targetFolder: NSTextField!
The button code is:
#IBAction func ExecuteButtonPressed(_ sender: NSButtonCell){
utilities.writeToLog(lineOfText: "Copy process starting\n")
processImages(inputFolder, outputFolder) // <-- This can take some time
utilities.writeToLog(lineOfText: "Copy process ended\n")
}
in utilities.swift the writeToLog function is defined as:
class utilities{
static func writeToLog(lineOfText: String){
// Get current viewcontroller where the logview textview is
let vc = (NSApplication.shared.keyWindow?.contentViewController as! ViewController)
let calendar = Calendar.current
let time=calendar.dateComponents([.hour,.minute,.second], from: Date())
let outputLineOfText = "\(time.hour!):\(time.minute!):\(time.second!) - " + lineOfText
vc.logView.textStorage?.append(NSAttributedString(string: outputLineOfText))
vc.logView.scrollToEndOfDocument(self)
}
All the copy functionality is in the ImageCopy.swift file and utilises the writeToLog() function to attempt to write to the GUI control as it progresses.
The application is all working with one exception - as the ProcessImages() function can take so long, and from what I can understand is running on the main thread, it blocks the GUI updates so once the ProcessImages() function returns the logView NSTextView updates all at once.
I think I need to run the ProcessImages() function on a thread, however when I did that it still didn't update the logView.
Any help would be much appreciated.
************************************** UPDATE
I think I'm on the right path now. I'm investigating delegates and Notifications to communicate between the background thread and the UI View.

************************************** UPDATE
I think I'm on the right path now. I'm investigating delegates and Notifications to communicate between the background thread and the UI View.

Related

Swift 4 WebkitView URL change not doing its job

I am having the following situation.
I am using a WebkitView to display some session information. In most places it's generated when the ViewController comes to life, but I have one particular view in which I have to reload it on user interactions with buttons that change to next or previous month.
I have the following code:
// This is my connection to the storyboard element
#IBOutlet weak var areaNotasMes: WKWebView!
After that I have a function that does the URL handling:
func actualizarUrlNotasMes(fechaNueva: Date) {
let formateadorAdicional = DateFormatter()
formateadorAdicional.dateFormat = "yyyyMM"
let url = URL(string: "https://myurl/\(formateadorAdicional.string(from: fechaNueva))")
areaNotasMes.load(URLRequest(url: url!))
}
And I have a button event that uses the function with the next YEAR_MONTH combination like this:
#IBAction func mesSigPressed(_ sender: UIButton) {
// Some more code to get the fechaActual variable
actualizarUrlNotasMes(fechaNueva: fechaActual)
// Some more code
}
So, basically what's happening is that it loads the first time I open the ViewController, but when I click on the button to change to the next month, the date gets changed, correctly generated and passed to the function. It generates the correct URL, but when the areaNotasMes.load(...) function runs, nothing refreshes on the WebKitView. I tried adding areaNotasMes.reload(), but it also did nothing. Do I have to like destroy and recreate the WebKitView each time I change the URL? Or am I not handling the URL change correctly?
Also, the console pops up some of these messages from time to time, not always:
[BoringSSL] nw_protocol_boringssl_get_output_frames(1301) [C5.1:2][0x7fea2d201de0] get output frames failed, state 8196
TIC Read Status [5:0x0]: 1:57
No you don't need to re-initialize the webview when changing the URL. I can't tell the issue with the information you provided. I would advise that you set the ViewController as navigationDelegate of the webview and implement these method to try to diagnose the issue :
func webView(WKWebView, didStartProvisionalNavigation: WKNavigation!)
func webView(WKWebView, didFail: WKNavigation!, withError: Error)
//Called when an error occurs during navigation.
func webView(WKWebView, didFailProvisionalNavigation: WKNavigation!, withError: Error)
Documentation here
I think I actually managed to fix this in the most random way. I thought maybe there's some reload function (except for .reload()) so using the auto completion of syntax, I came up with the following command:
areaNotasMes.reloadInputViews()
Apparently .reloadInputViews() is a void function inherited from UIView which updates the custom input and accessory views when the object is the first responder. You can check it here: reloadInputViews()
It does send some Error Domain=NSURLErrorDomain Code=-999 "(null)" although it actually works as supposed and refreshes the WebKitView information.
I hope this helps someone else.

How to run an Automator Workflow/Service from Cocoa app?

So I tried learning Swift well enough to recreate my programs in it, didn't work so well and I didn't get very far. Tried running my C++ functions from Obj-C++ source by calling the functions, didn't work and the project refused to open again after the first time I closed it. I don't find object oriented programming very intuitive in the first place so I'd like to avoid Obj-C.
I already have both an Automator standalone workflow and a Service (that do the same thing) which is get the programs I need, display confirmation, run the program in a terminal window with stdout, and display a notification before exiting. This is everything I need it to do when a specific button is pressed.
So how would I go about linking this button to an Automator func run() block in Swift? I know the command that needs to be used but like I said I don't find object oriented programming very intuitive so I need to know the context in which that function is used. Would following block be enough in practice or would it need more specification?
#IBOutlet weak var testButton(NSButton!)
#IBAction testButton(_ sender: AnyObject)
{
let guard Bundle.main.path(forName: "test",forType:"workflow")
else
{
print("Could not find 'test.workflow'")
return
}
let URL="//location of file"
class func run(at: URL, withInput: nil)
}
Am I missing something about how to do this or is the above enough? Secondarily can someone please give an example as to the format of a file URL where the file is located in the bundles "Resources" folder?
Also would the class remain the word class or should I be specifying a custom class? Can someone please give me a realworld example of this block/concept in practice?
Here's a testButton function that should work:
#IBAction func testButton(_ sender: AnyObject) {
guard let workflowPath = Bundle.main.path(forResource: "test", ofType: "workflow") else {
print("Workflow resource not found")
return
}
let workflowURL = URL(fileURLWithPath: workflowPath)
do {
try AMWorkflow.run(at:workflowURL, withInput: nil)
} catch {
print("Error running workflow: \(error)")
}
}
Notes:
You need to import Automator at the top of your source file in order for the Swift compiler to recognize the AMWorkflow class
You need to add test.workflow to your project

Combobox component for Swift (failed with DownPicker)

I need a component like comboBox when it dropes down and allow user to select value. PickerView looks terrible and one I found nice looking was DownPicker
I followed instructions, installed it and tried to use but I did not see the data I pass to this component in it.
let data = NSMutableArray()
data.addObject("1")
data.addObject("2")
data.addObject("3")
let a = DownPicker(textField: group!, withData: data)
Also nothing happens when I tap down arrow icon
Just advice me another components like this one which will work correctly with xcode 7 beta 6 or help me to fix problems with current try. Thanks)
Update:
I've just tried this one. Nothing good from it too :\
DownPicker is working fine with Swift. I just tried it and use it in my project. The problem I see is that you init DownPicker to local variable. There are two ways how you can use DownPicker in your project and it looks you choose Control Wrapper. So you should have in your controller these controls:
#IBOutlet weak var personTextField: UITextField!
var personDownPicker: DownPicker!
The first one is outlet connected to UITextField added in storyboard. DownPicker I Initialized in viewDidLoad:
override func viewDidLoad() {
super.viewDidLoad()
let persons: NSMutableArray = ["Architect", "Designer"]
self.personDownPicker = DownPicker(textField: self.personTextField, withData:persons)
}

NSDocument: how do you save & restore a document's window position?

I have a NSDocument-based app, and I'd like my window positions to be saved and restored when re-opening documents. Apple's documentation on this is pretty sparse, but what I've been able to piece together is that at some point, something in the app needs to call NSWindow.setFrameUsingName() and NSWindow.setFrameAutosaveName().
What I haven't quite figured out is what point this needs to happen at, and what things need to do this. For example, this doesn't work at all:
// In my NSDocument class
override func windowControllerDidLoadNib(aController: NSWindowController) {
super.windowControllerDidLoadNib(aController)
// Add any code here that needs to be executed once the windowController has loaded the document's window.
aController.window?.setFrameUsingName("MainWindow")
aController.window?.setFrameAutosaveName("MainWindow")
}
I've read various different pieces of documentation or forum answers that point to awakeFromNib() to be another area to do this, but I can't get that to work either.
I'm also confused / worried that this is somehow being affected by Auto Layout or something I've done wrong in Interface Builder - for example, this is how my window is set up in IB:
I don't particularly want my window centered, but the other options seem to lock it in place in fixed horizontal or fixed vertical positions, which I also don't really want. A side effect of having my window be centered is that my document windows no longer cascade, which I neither want nor can seem to stop from happening (note that windowController.shouldCascadeWindows = true isn't helping either).
So - what's going on here? I'm finding knowledge on this topic to be particularly unclear or misleading, and likely out of date for Cocoa development 2015, so a modern refresher on this would be great.
Step 1: In IB, give the window an autosave name.
Step 2: There is no step 2.
The easiest place to set the name of the autosave name for the window frame is probably in your implementation of NSDocument.
If you override makeWindowControllers() as part of the implementation, you are creating the window controller(s) manually, and thus can set the name there:
override func makeWindowControllers() {
let storyboard = NSStoryboard(name: NSStoryboard.Name("MyDocumentStoryboard"), bundle: nil)
let windowController = storyboard.instantiateController(withIdentifier: NSStoryboard.SceneIdentifier("MyDocument Window Controller")) as! MyDocumentWindowController
// this assumes you have a stored property or a computed property `someUniqueIdentifier` that is as unique as possible for your document
// and that you store on disk together with the rest of the document properties
windowController.windowFrameAutosaveName = NSWindow.FrameAutosaveName(rawValue: someUniqueIdentifier)
self.addWindowController(windowController)
}
If you instantiate your document window by overriding windowNibName, you should instead override the method windowControllerDidLoadNib(_:), to set the autosave name on the window. I have not tested this code, but I assume it will work:
func windowControllerDidLoadNib(_ windowController: NSWindowController) {
// this assumes you have a stored property or a computed property `someUniqueIdentifier` that is as unique as possible for your document
// and that you store on disk together with the rest of the document properties
windowController.windowFrameAutosaveName = NSWindow.FrameAutosaveName(rawValue: someUniqueIdentifier)
}

How to link actions to File->Save/File->Open in Swift app

I have a Swift application that has data I want to save using the methods described in this question. Now, I need to know what is the proper way to link these actions to the File -> Save/Save As menu item and the File -> Open menu item. This isn't a document-based application.
I'm running Xcode 6.4 on OS X 10.10.4.
Create an IBAction function and link it to the XIB via Interface Builder.
Create an open/save panel in that function and let the user select the file name and location, use the returned NSURL array for saving/loading path. (after converted to required object type, of course.)
There are lots of example codes almost everywhere, either Objective-C or Swift.
In Swift 3, it may seem odd because it's using 'First Responder' but all you have to do is add the following code to your NSViewController class that is set as the Custom Class on a storyboard. It does not have to be connected like other #IBAction functions.
class Test: NSViewController {
#IBAction func saveDocument(_ sender: Any?) {
// code to execute for save functionality
// following line prints in debug to show function is executing.
// delete print line below when testing is completed.
Print("save")
}
#IBAction func openDocument(_ sender: Any?) {
// code to execute for open functionality here
// following line prints in debug to show function is executing.
// delete print line below when testing is completed.
print("open")
}
}