Quickest way to dirty a FileReferenceDocument in SwiftUI for macOS - swiftui

I have a number of changes to a document-based application which are not undoable (conceptually, think quiz progress) but which should dirty the document so the state is saved when the user quits or closes the document.
Apple's documentation for ReferenceFileDocument states Changes to the document are observed and on change dirty the document state for resaving. but that does not happen for me. (I have tried both changes to a DocumentData class held by my document and to a variable directly within my ReferenceFileDocument subclass, but nothing happens; the document stays clean and does not autosave.)
NSDocument has an updateChangeCount function which is all I want at this point.
All references to dirtying documents I have found involve an undo manager, so in my document class I have implemented
var isDirty: Bool = false
func dirtyDocument(undoManager: UndoManager?){
undoManager?.registerUndo(withTarget: self) { isDirty in
undoManager?.removeAllActions(withTarget: self)
}
if isDirty == false {
isDirty = true
}
}
and any View that makes a noteworthy change contains
#Environment(\.undoManager) var undoManager
and calls document.dirtyDocument(undoManager: undoManager)
This works in that it dirties the document, but leaves me with an enabled 'Undo' action which does nothing BUT which marks the document as 'not dirty' if the user invokes it, which is why I added undoManager?.removeAllActions(withTarget: self)
I now have the functionality I want, apart from not finding a way to disable the undo menu item for this 'undo action' but it still feels hackish. Is there a better way to emulate updateChangeCount without invoking the UndoManager and and immediately discarding its changes?

Related

Detect UI changes to #Published property

I have a simple SwiftUI App that fetches App Settings using a REST API call and makes it available to the UI using an #ObservedObject with a #Published property. I have several Toggle views that bind to the #Published property.
Each time a Toggle is "toggled", I need to upload the change to the server. So I added a didSet observer to the #Published property and although this works, the didSet property observer also gets fired when I initially fetch the settings from the server.
How can I avoid firing the didSet observer when initially setting the #Published property with settings fetched from the server?
The behaviour you are describing is correct. When you load the setting with your REST API and then set it to the #Published the value gets set, and your function is getting called.
You can add a variable to your class, which stores if the fetch is completed. When you fetched all your results, you set it to true and only then allow didSet function to be executed.
//Set your fetched data previous
var fetchCompleted : Bool = false
//after your api call and fetch result success
fetchCompleted = true
.didSet()
{
if (fetchCompleted)
{
//allow override from variables and store new setting
}
}
Make sure you set your variables with the fetched data first, and then set fetchCompleted to true. Otherwise didSet will called after and fetchCompleted would already be true.
It's difficult without code shown, but you could add an if-condition in your didSet which prevents it from executing your upload, when the Published value is "nil" (initial).

How to trigger didReceiveAttrs in Ember component

Using version 2.17. I have an Ember component inside an /edit route with a controller:
// edit.hbs
{{ingredient-table recipe=model ingredients=model.ingredients}}
Inside my component, I am using a didRecieveAttrs hook to loop through ingredients on render, create proxy objects based off of each, and then build an ingredient table using those proxy objects.
// ingredient-table.js
didReceiveAttrs() {
let uniqueIngredients = {};
this.get('ingredients').forEach((ingredient) => {
// do some stuff
})
this.set('recipeIngredients', Object.values(uniqueIngredients));
}
I also have a delete action, which I invoke when a user wishes to delete a row in the ingredient table. My delete action looks like this:
// ingredient-table.js
deleteIngredient(ingredient) {
ingredient.deleteRecord();
ingredient.save().then(() => {
// yay! deleted!
})
}
Everything mentioned above is working fine. The problem is that the deleted ingredient row remains in the table until the page refreshes. It should disappear immediately after the user deletes it, without page refresh. I need to trigger the didReceiveAttrs hook again. If I manually call that hook, all my problems are solved. But I don't think I should be manually calling it.
Based on the docs, it is my understanding that this hook will fire again on page load, and on re-renders (not initiated internally). I'm having some trouble figuring out what this means, I guess. Here's what I've tried:
1) calling ingredients.reload() in the promise handler of my save in ingredient-table.js (I also tried recipe.reload() here).
2) creating a controller function that calls model.ingredients.reload(), and passing that through to my component, then calling it in the promise handler. (I also tried model.reload() here).
Neither worked. Am I even using the right hook?
I suppose recipeIngredients is the items listed in the table. If that is the case; please remove the code within didReceiveAttrs hook and make recipeIngredients a computed property within the component. Let the code talk:
// ingredient-table.js
recipeIngredients: Ember.computed('ingredients.[]', function() {
let uniqueIngredients = {};
this.get('ingredients').forEach((ingredient) => {
// do some stuff
})
return Object.values(uniqueIngredients)
})
My guess is didReceiveAttrs hook is not triggered again; because the array ingredients passed to the component is not changed; so attrs are not changed. By the way; do your best to rely on Ember's computed properties whenever possible; they are in the hearth of Ember design.

Swift - Unit testing functions that involve IBOutlets?

I am setting up unit testing for my Swift project and am having trouble testing a class function that involves updating IBOutlets.
I have a function, validateUrl, which expects a string to be passed, then validates it. If it is valid, it enables a UIButton, if it is invalid, it disables a UIButton. When I run a test that calls this function, the app crashes on the line of code that enables or disables the UIButton.
The storyboard and controllers both has the proper Test target set.
This line of code:
self.submitButton.enabled = true// Enable Submit Button
Spits out this error:
fatal error: unexpectedly found nil while unwrapping an Optional value
Try this code to initialize the IbOutlets of your view controller:
let yourStoryboard = UIStoryboard(name: "Your_storyboard", bundle: nil)
yourViewController = yourStoryboard.instantiateViewController(withIdentifier: "YourViewController") as! YourViewController
yourViewController.loadView() // This line is the key
You have to initiate the view controller using the storyboard. See the documentation here: https://developer.apple.com/library/ios/documentation/uikit/reference/UIStoryboard_Class/index.html#//apple_ref/occ/instm/UIStoryboard/instantiateViewControllerWithIdentifier:
If you initialize the view controller directly, it will not have any connections because the VC itself does not know of the storyboard in this case.
You may need to add the controllers view to a hierarchy prior to testing to force the controller to load the XIB
let localContainer = UIView(frame:someFrame)
let controllerUnderTest = //instantiate your controller
localContainer.addSubview(controllerUnderTest.view)
//at this point you can test outlets
Otherwise your outlets will be nil as they haven't been connected yet.
Testing IBOutlet's is not the best approach, because:
outlets should be considered implementation details, and should be declared as private
outlets are most of the time connected to pure UI components, and unit tests deal with testing the business logic
testing the value injected into the outlet by another function can be considered somewhat as integration testing. You'd also double the unit tests you have to write by having to test the connected/unconnected outlet scenarios.
In your particular case, I'd recommend instead to test the validator function, but first making it independent of the controller class (if it's not already). Having that function as an input->output one also bring other benefits, like increased reusability.
Once you have tested all the possible scenarios for the validator, validating that the outlet correctly behaves it's just a matter of a quick manual testing: just check if the outlet behaves like the validator returned. UI stuff are better candidates for manual testing, as manual testing can catch other details too (like positioning, colors, etc).
However, if you really want to test the outlet behaviour, one technique that falls into the unit testing philosophy is snapshot testing. There are some libraries available for this, I'd recommend the one from https://github.com/uber/ios-snapshot-test-case/.
A solution I'm using to test classes in Swift is to create the outlets in the setUp() method of the test. For example, I have a UIViewController subclass that has a UIImageView as an outlet. I declare an instance of my View controller subclass a property of my test class, & configure it's outlet in the setUp() method by initializing a new UIImageView.
var viewController = ViewController() // property on the test class
override func setUp() {
super.setUp()
viewController.imageView = UIImageView(image: UIImage(named: "Logo")) // create a new UIImageView and set it as the outlet object
}
Any other outlets can similarly be set in the setUp method, and this way you don't even have to instantiate a storyboard (which, for some reason, despite the fact that I was able to instantiate a UIStoryboard object in my tests, the view controller outlets were still nil).
#talzag In your case iboutlets are nil because they are weak variables, They are supposed to deallocate immediately after instantiation.

Ember.js: How do I prevent this property observer from being hit more than once?

In my controller I have a property and a function observing that property. When the property changes once, the observer is being hit three times - twice with the old data and once with the new data. You can see it happen in the console window of the jsbin I just created:
jsbin
Usage: click one of the books (not the first one), and look in the console window at the results.
In my actual app, the work to be performed requires an asynchronous download. This observer is downloading the wrong content twice and the correct content once because of the three hits. Making this problem more obvious is that the asynchronous responses do not come back in sequence. :)
An interim solution has been to schedule the download code to run later.
I'm not sure why this is happening, but the guides give a way to fix it. They suggest something like this:
bookObserver: function() {
Ember.run.once(this, 'bookWasChanged');
}.observes('book'),
bookWasChanged: function() {
// This will only run once per run loop
}
Personally, I always make the assumption that observers could fire even when I don't want them to. For instance, with this code, I would do the following:
bookObserver: function() {
var book = this.get('book');
var lastBook = this.get('lastBook');
if (book !== lastBook) {
this.set('lastBook', book);
doSomethingWithBook(book);
}
}.observes('book')
This way, even if Ember calls your observer 1000 times, you're only going to do the work you want when the book actually changes.

Observing controller property from view only works if get('controller') called from didInsertElement

Note the below Ember view definition. If I remove the didInsertElement call or comment out the get('controller') call, the setupMultiselect observer never gets called. Is this a feature or a bug? Confused...
Discourse.KbRelatedObjView = Discourse.View.extend({
...
didInsertElement: function() { var self = this;
// for some reason this needs to be here else the observer below never fires
self.get('controller');
},
setupMultiselect: function() { var self = this;
...
}.observes('controller.objPage')
});
I wouldn't say it's a feature or a bug, more like a quirk. It is the expected behavior though. It's noted here.
UNCONSUMED COMPUTED PROPERTIES DO NOT TRIGGER OBSERVERS
If you never get a computed property, its observers will not fire even if its dependent keys change. You can think of the value changing from one unknown value to another.
This doesn't usually affect application code because computed properties are almost always observed at the same time as they are fetched. For example, you get the value of a computed property, put it in DOM (or draw it with D3), and then observe it so you can update the DOM once the property changes.
If you need to observe a computed property but aren't currently retrieving it, just get it in your init method.