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

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")
}
}

Related

How to drag and drop an external item for Xcode-ui-testing

In my application I allow the user to drag and drop items from Finder (or any other source of a file based URL) into my application. What I want to do is to add a mechanism that will allow me to test this in the Xcode UI testing.
I can see how to use XCUIElement.press(forDuration:thenDragTo:) to test the drag and drop of a source and destination within the application, but I have been unable to find a way to test when the source of the drag is outside of the application.
In a somewhat related test, I test the copy and paste portion of the application by setting the string I want to paste into NSPasteboard.general, then using XCUIElement.typeKey("v", modifierFlags: .command) to paste it into the desired element. That is a little less than ideal as it depends on Command-v actually being implemented as the paste command, but that is unlikely to change so it is acceptable for my needs. (In fact I've written an XCUIElement.paste(_ s: String) extension that makes it easy for me to add this in a test.)
I believe that drag and drop is also using an NSPasteboard for its communications, so with a little investigation into the underlying mechanism, I should be able to set my object into the correct pasteboard just like I do for the cut and paste. I'm reasonably certain I can figure that part out. But I haven't figured out how to perform the actual drop.
My goal would be to create an XCUIElement.drop(_ url) that would setup the proper "public.file-url" object into the correct pasteboard, and then simulate/perform the drop into the element.
Any ideas?
I should note that I have already tried the following two items:
First, I did use the Xcode record feature to attempt to record the drag and drop operation and see what events it would give me. Unfortunately, it records absolutely nothing.
Second, I do have a menu based alternative where the user selects a file via the file selector. So if I could simulate the file selection, that would be a suitable testing alternative for my purposes. Unfortunately, I didn't make any progress along that path either. When I used Xcode to record the events, it recorded the menu selection, nothing that was actually done in the dialog.
Based on your comments I would recommend you to read this article documentation piece
https://developer.apple.com/documentation/xctest/xcuiapplication
Notice the init(bundleIdentifier: String) and init(url: URL) methods. These allow you to interact with apps apart from the target application.
Then you can use XCUIElement.press(forDuration:thenDragTo:)
import XCTest
import XCTApps
import ScreenObject
let notes = XCTApps.notes.app
let photos = XCTApps.photos.app
class Tests: XCTestCase {
func testDragAndDrop() {
photos.launch()
notes.launch()
photos.images.lastMatch.press(forDuration: 1, thenDragTo: notes.textViews["Note Body Text View"])
}
}
P.S. In this example I use XCTApps because I don't want to remember or google bundle identifiers :D
https://github.com/rzakhar/XCTApps
Ok, so I haven't yet figured out the answer to my question (how to test a drag and drop), but I have come up with an acceptable workaround for my test.
Specifically, as I thought more about the pasteboard I realized that if I'm allowing the user to drag and drop a file into my application, then I should also be allowing them to cut and paste a file into the application.
Once I had that realization, then it was a reasonably simple process to test the necessary feature of my application by pasting a URL instead of dragging and dropping the URL. This has the added advantage that I can add the necessary test file to my testing package, keeping everything nicely self contained.
To this end I've added the following function to my XCUIElement extension:
extension XCUIElement {
func paste(url: URL) {
precondition(url.isFileURL, "This must be a file URL to match the pasteboard type.")
let pasteboard = NSPasteboard.general
pasteboard.clearContents()
pasteboard.setString(url.absoluteString, forType: .fileURL)
click()
typeKey("v", modifierFlags: .command)
}
}
Then in my test code I add the following to trigger the event:
let mainWindow = app.windows[/*...my main window name goes here...*/]
let testBundle = Bundle(for: type(of: self))
let fileURL = testBundle.url(forResource: "Resources/simple", withExtension: "json")
mainWindow.paste(url: fileURL!)
Granted, this doesn't actually test the drag and drop, but it does test the same portion of my code, since in my AppDelegate I have my onPaste action method calling the same underlying method as my performDrop method.
I will wait a couple of days to see if anyone comes up with an answer to the actual question (since I would still find that useful), but if no one does, I'll accept my own answer.

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.

Swift - Getting a SIGABRT when trying to update Firebase DB values

I have what I think should be a very simple operation. I just want to have an entry in a Firebase DB changed when I press a button. I already checked that there's nothing wrong with the button.
Here is my FireBase entry, by the way:
This code works fine in displaying text to the console (so, no problem with the button or connection, or variable, for that matter):
#IBAction func test(_ sender: Any) {
print("Username: \(requestUsername)")
}
My simple IBAction function gets a SIGABRT every time. I tried to look at other S.O. questions on the best way to update values, and to try to follow their suggestions, so I'm not sure if my firebase code is messed up, or something else. Right now, "ref" is declared inside the IBAction, but I've also tried declaring it globally, with the same result.
#IBAction func test(_ sender: Any) {
var ref = FIRDatabase.database().reference()
ref.child("Sell_Request").child(requestUsername).child("Request_Made").setValue("true")
}
Update:
For what it's worth, I'm able to get the correct Firebase entry with this code. I'm still not sure how to update specific fields. If anyone has any ideas, I would appreciate it.
ref.child("Sell_Request").queryOrdered(byChild: "name").queryEqual(toValue: requestUsername).observeSingleEvent(of: .value, with: { (snapshot) in
print(snapshot)
})
}

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

Using shouldPerformSegueWithIdentifier( ) method in Swift

I am trying to use swift's shouldPerformSegueWithIdentifier() method, but it accepts 2 arguments. These are (identifier: String!, sender:AnyObject)
My main goal is to execute the code when pressing a login button in my storyboard, and depending of a series of checks return TRUE or FALSE, depending if whether the correct username and password were provided. So here are my questions:
What am I supposed to use as the identifier? Apple's documentation it explains that the identifier is a string that identifies the triggered segue. So suppose that my segue had the name of loginSegueProcess. How could I use it in my ViewController tat is assigned to my UIView? The thing is that I declare the method in my code and it requires me to specify both arguments (identifier & sender). How could I provide the arguments?
Will this method actually fulfill my needs? By that I mean if it will indeed stop the segue transition whenever my Login button is clicked and depending on whether the correct credentials were provided it is going to take you to the next View or it will show, say for example, an AlertView.
Finally, I was thinking that the performSegueWithIdentifier(args) method would help me as well. Does anybody know the difference between them?
Thanks a lot in advance!
isn't it what you want to do?
override func shouldPerformSegueWithIdentifier(identifier: String!, sender: AnyObject!) -> Bool {
if identifier == "LoginSuccessSegue" { // you define it in the storyboard (click on the segue, then Attributes' inspector > Identifier
var segueShouldOccur = /** do whatever you need to set this var to true or false */
if !segueShouldOccur {
println("*** NOPE, segue wont occur")
return false
}
else {
println("*** YEP, segue will occur")
}
}
// by default, transition
return true
}
You may not invoke shouldPerformSegueWithIdentifier() method by yourself. It will be automatically called just before transition to the next view giving a chance to determine wether the transition should take place or. You may conditionally return YES/NO from this method. If your condition does't involve any sever call,a simple logical checking this method will be enough for you.
performSegueWithIdentifier() is used to invoke a segue programmatically. Consider the above case with a network call, you may return NO from shouldPerformSegueWithIdentifier() initially since authentication is going on. After getting the response from server if it success you can call the segue to execute with performSegueWithIdentifier (Here the identifier is the ID you have given in the storyboard). Before make sure you are supposed to return YES from shouldPerformSegueWithIdentifier().
Now a third case if your segue is connecting from the login button(You have to connect it from the controller itself). The checking of shouldPerformSegueWithIdentifier is no more required. You can just call the segue with performSegueWithIdentifier() after getting the success response from server