Best Practice or method to see if the form changed from its initial values or not - eureka-forms

I apologize if this is covered elsewhere, but I haven't been able to find a reference to this.
Is there a built-in method or Best Practice to identifying which rows had changed from their initial values?

So here is what I ended up doing - and this has been working well. This is a partial class implementation just for this purpose:
class YourFormViewController : FormViewController {
var formValuesChanged = [String : (Any?, Any?)]()
override func valueHasBeenChanged(for row: BaseRow, oldValue: Any?, newValue: Any?) {
super.valueHasBeenChanged(for: row, oldValue: oldValue, newValue: newValue)
guard let rowIdentifier = row.tag else { return }
formValuesChanged[rowIdentifier] = (oldValue, newValue)
}
}
I use this as a base class for all my forms and then I can just use:
guard formValuesChanged.count > 0 else {
log.info("Did not save editor because no values were changed")
dismiss(animated: true, completion: nil)
return
}

Related

Error with NSTreeController - this class is not key value coding-compliant for the key

I am new to Swift and trying to learn how to implement NSTreeController with NSOutlineView. I've been following several guides which shows such examples, but I keep getting an error. I followed step by step and/or try to run their source codes if available, but I was getting same error. I come to think there is some change in Swift 4 which makes these Swift 3 examples to produce error. As there are not many examples done in Swift 4, I decided I'd give a try by asking the question here.
The error I'm getting is:
this class is not key value coding-compliant for the key isLeaf.
I believe that error is coming from the key path set up for NSTreeController:
However I am not sure what needs to be done to fix the error.
I have simple model class called Year.
class Year: NSObject {
var name: String
init(name: String) {
self.name = name
}
func isLeaf() -> Bool {
return true
}
}
My view controller looks like this.
class ViewController: NSViewController, NSOutlineViewDataSource, NSOutlineViewDelegate {
#IBOutlet weak var outlineView: NSOutlineView!
#IBOutlet var treeController: NSTreeController!
override func viewDidLoad() {
super.viewDidLoad()
addData()
outlineView.delegate = self
outlineView.dataSource = self
}
func addData() {
let root = ["name": "Year", "isLeaf": false] as [String : Any]
let dict: NSMutableDictionary = NSMutableDictionary(dictionary: root)
dict.setObject([Year(name: "1999"), Year(name: "2000")], forKey: "children" as NSCopying)
treeController.addObject(dict)
}
func isHeader(item: Any) -> Bool {
if let item = item as? NSTreeNode {
return !(item.representedObject is Year)
} else {
return !(item is Year)
}
}
func outlineView(_ outlineView: NSOutlineView, viewFor tableColumn: NSTableColumn?, item: Any) -> NSView? {
if isHeader(item: item) {
return outlineView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "HeaderCell"), owner: self)!
} else {
return outlineView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "DataCell"), owner: self)!
}
}
}
When I run the program, it causes no issue, but when I expand the node to show the two children of the root, it is giving the error I mentioned above.
Because is isLeaf is used in KVO by NSOutlineView, you have to add #objc in front of isLeaf function:
#objc func isLeaf() -> Bool {
return true
}
The class to which you are binding needs to be KVO compliant.
So, it needs to be a subclass of NSObject.
And the objc runtime needs access.
One way to do this:
#objcMembers
class FileSystemItem: NSObject {
Or, you can annotate each field/function with #objc
Full Example

Enable NSArrayController predicate based on checkbox

I've got a static filter to turn on/off for an NSArrayController based on whether or not a checkbox is checked. Right now I've bound the checkbox value to this:
private dynamic var filterPending: NSNumber! {
willSet {
willChangeValueForKey("filterPredicate")
}
didSet {
didChangeValueForKey("filterPredicate")
}
}
and then I bound the filter of the NSArrayController to this:
private dynamic var filterPredicate: NSPredicate? {
guard let filter = filterPending?.boolValue where filter == true else { return nil }
return NSPredicate(format: "pending > 0")
}
That seems to work properly, but feels like maybe I'm missing some easier way of doing this?
In your set-up the value of filterPredicate depends on the value of filterPending. As Gerd K points out, the Key-Value Observing API allows you to specify this type of relationship by registering filterPending as a dependent key of filterPredicate:
// MyFile.swift
class func keyPathsForValuesAffectingFilterPredicate() -> Set<NSObject> {
return Set<NSObject>(arrayLiteral: "filterPending")
}
private dynamic var filterPending: NSNumber!
private dynamic var filterPredicate: NSPredicate? {
guard let filter = filterPending?.boolValue where filter == true else { return nil }
return NSPredicate(format: "pending > 0")
}

Accessing PFObject Subclassed Properties from a Query

I am using Parse and have created a subclass of PFObject. When creating objects it makes things much easier. Once objects are created, I am experimenting with querying the database and accessing the custom properties I created. What I am finding is that I cannot use dot notation to access the properties when I am working the the PFObjects returned from the query. Is this normal?
Here is subclass I created.
import Foundation
import UIKit
import Parse
class MessagePFObject: PFObject
{
#NSManaged var messageSender : String
#NSManaged var messageReceiver : String
#NSManaged var messageMessage : String
#NSManaged var messageSeen : Bool
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// Custom Query method.
override class func query() -> PFQuery?
{
let query = PFQuery(className: MessagePFObject.parseClassName())
query.includeKey("user")
query.orderByDescending("createdAt")
return query
}
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
init(messageSenderInput: String?, messageReceiverInput: String?, messageMessageInput: String?)
{
super.init()
self.messageSender = messageSenderInput!
self.messageReceiver = messageReceiverInput!
self.messageMessage = messageMessageInput!
self.messageSeen = false
}
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
override init()
{
super.init()
}
}
//++++++++++++++++++++++++++++++++++++ EXTENSION +++++++++++++++++++++++++++++++++++++++++++++++++++
extension MessagePFObject : PFSubclassing
{
class func parseClassName() -> String
{
return "MessagePFObject"
}
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
override class func initialize()
{
var onceToken: dispatch_once_t = 0
dispatch_once(&onceToken) {
self.registerSubclass()
}
}
}
Here is my query and what I am required to do to access the properties. createdAt, updatedAt, etc are all available with dot notation but none of my custom properties are. You can see I access messageSeen with element.objectForKey("messageSeen").
let predicate = NSPredicate(format: "messageSender == %# OR messageReceiver == %#", self.currentUser!.username!, self.currentUser!.username!)
let query = messagePFObject.queryWithPredicate(predicate)
query!.findObjectsInBackgroundWithBlock ({ (objects, error) -> Void in
if error == nil && objects!.count > 0
{
for element in objects!
{
print(element)
print(element.parseClassName)
print(element.objectId)
print(element.createdAt)
print(element.updatedAt)
print(element.objectForKey("messageSeen"))
}
}
else if error != nil
{
print(error)
}
})
If this is normal then that is fine. I just want to make sure I am not missing something.
Take care,
Jon
Your object subclass has to implement the PFSubclassing protocol and you need to call MessagePFObject.registerSubclass() in your app delegate.
The parse documentation is very good : https://parse.com/docs/ios/guide#objects-subclasses

Dropping a token in NSTokenField

I am implementing an app where rows from an NSTableView can be dragged and dropped into an NSTokenField, but I am struggling to implement the drop-side of the interaction. I have subclassed NSTokenField (as shown below in the debugging code below). But I am only seeing calls to draggingEntered: and updateDraggingItemsForDrag: method. Even though I return a valid NSDragOperation (Copy), none of the other methods in NSDraggingDestination are called. The cursor briefly flashes to the copy icon when moving over the token field, but then returns to the normal cursor.
I tried implementing all the methods associated with NSDraggingDestination for debugging purposes, shown in the code below. Is there another class or part of the NSTokenField that is handling the drop? Is it possible to override that?
I have confirmed that the pasteboard does have data with the valid pasteboard type.
let kPasteboardType = "SamplePasteboardType"
class MyTokenField : NSTokenField
{
override func draggingEntered(sender: NSDraggingInfo) -> NSDragOperation {
// entered
NSLog("ENTERED")
// must come from same window
guard self.window == sender.draggingDestinationWindow() else {
return super.draggingEntered(sender)
}
// has valid pasteboard data?
let pb = sender.draggingPasteboard()
if let _ = pb.dataForType(kPasteboardType) {
NSLog("MATCHED")
return NSDragOperation.Copy
}
return super.draggingEntered(sender)
}
override func draggingUpdated(sender: NSDraggingInfo) -> NSDragOperation {
NSLog("UPDATED")
// must come from same window
guard self.window == sender.draggingDestinationWindow() else {
return super.draggingUpdated(sender)
}
// has valid pasteboard data?
let pb = sender.draggingPasteboard()
if let _ = pb.dataForType(kPasteboardType) {
return NSDragOperation.Copy
}
return super.draggingUpdated(sender)
}
override func draggingExited(sender: NSDraggingInfo?) {
NSLog("EXITED")
super.draggingExited(sender)
}
override func prepareForDragOperation(sender: NSDraggingInfo) -> Bool {
NSLog("PREPARE")
return super.prepareForDragOperation(sender)
}
override func performDragOperation(sender: NSDraggingInfo) -> Bool {
NSLog("PERFORM")
return super.performDragOperation(sender)
}
override func concludeDragOperation(sender: NSDraggingInfo?) {
NSLog("CONCLUDE")
super.concludeDragOperation(sender)
}
override func draggingEnded(sender: NSDraggingInfo?) {
NSLog("ENDED")
super.draggingEnded(sender)
}
override func updateDraggingItemsForDrag(sender: NSDraggingInfo?) {
// super.updateDraggingItemsForDrag(sender)
guard let drag = sender else {
return
}
let classes: [AnyClass] = [NSPasteboardItem.self]
let options: [String: AnyObject] = [NSPasteboardURLReadingContentsConformToTypesKey: [kPasteboardType]]
drag.enumerateDraggingItemsWithOptions(NSDraggingItemEnumerationOptions.ClearNonenumeratedImages, forView: self, classes: classes, searchOptions: options) {
(item, idx, stop) in
NSLog("\(item)")
}
}
}
Thanks to the comment from #stevesliva, I was able to solve the problem. There are some key caveats that I discovered (they may be partly due to my ignorance of pasteboard and drag/drop interactions).
Subclassing the NSTokenField class is not necessary.
I had to implement the delegate function tokenField(tokenField: NSTokenField, readFromPasteboard pboard: NSPasteboard) -> [AnyObject]? for the token field.
I had to change start of the drag to store a string value to the pasteboard. It seems like if the pasteboard does not have a string value, then the above delegate function is never called.

How to get the correct callback value for NSUndoManager in Swift?

I have an NSDocument subclass, hooked up to an NSArrayController. For reference, I'm trying to translate the example from chapter 9 of Cocoa Programming for Mac OS X Fourth Edition.
It seems that from this question that I asked before, I need to use object-based undo with NSUndoManager. In order to pass two values to the method being invoked, I'm packaging them into an NSObject subclass with two instance variables.
When the KVO methods for inserting and deleting from the employees array are called by clicking on the buttons in my application, they work as expected.
However, when removeObjectFromEmployeesAtIndex is called during an undo operation, the index passed in is wildly out of bounds (for the first row, it always seems to be 55, then after that the index increases into the thousands for the next few rows).
How can I get the correct index to perform the undo action?
class Document: NSDocument {
var employee_list: Array<Person> = []
var employees: Array<Person> {
get {
return self.employee_list
}
set {
if newValue == self.employee_list {
return
}
self.employee_list = newValue
}
}
func insertObject(person: Person, inEmployeesAtIndex index: Int) {
self.undoManager.registerUndoWithTarget(self, selector: Selector("removeObjectFromEmployeesAtIndex:"), object: index)
if (!self.undoManager.undoing) {
self.undoManager.setActionName("Add Person")
}
employees.insert(person, atIndex: index)
}
func removeObjectFromEmployeesAtIndex(index: Int) {
let person = self.employees[index]
let pair = PersonIndexPair(person: person, index: index)
self.undoManager.registerUndoWithTarget(self, selector: Selector("insertPersonIndexPair:"), object: pair)
if (!self.undoManager.undoing) {
self.undoManager.setActionName("Remove Person")
}
employees.removeAtIndex(index)
}
func insertPersonIndexPair(pair: PersonIndexPair) {
insertObject(pair.person, inEmployeesAtIndex: pair.index)
}
}
Edit: I've worked around the issue by passing a string, but this seems pretty obtuse:
self.undoManager.registerUndoWithTarget(self, selector: Selector("removeObjectFromEmployeesAtStringIndex:"), object: String(index))
//...
func removeObjectFromEmployeesAtStringIndex(index: String) {
if let i = index.toInt() {
removeObjectFromEmployeesAtIndex(i)
}
}
Use NSNumber instead of Int.
self.undoManager.registerUndoWithTarget(self, selector:Selector("removeObjectFromEmployeesAtStringIndex:"), object: NSNumber(integer: index))
//...
func removeObjectFromEmployeesAtStringIndex(index: NSNumber) {
removeObjectFromEmployeesAtIndex(index.integerValue)
}
I am reading Cocoa Programming for Mac OS X now. I translate the sample Object-C code as below, only need to append it to class Document:
func insertObject(p: Person, inEmployeesAtIndex index: Int) {
//NSLog("adding %# to %#", p, employees)
let undo = self.undoManager
undo!.prepareWithInvocationTarget(self).removeObjectFromEmployeesAtIndex(index)
if !undo!.undoing {
undo!.setActionName("Add Person")
}
employees.insertObject(p, atIndex: index)
}
func removeObjectFromEmployeesAtIndex(index: Int) {
let p = employees.objectAtIndex(index) as! Person
//NSLog("Removing %# from %#", p, index)
let undo = self.undoManager
undo!.prepareWithInvocationTarget(self).insertObject(p, inEmployeesAtIndex: index)
if !undo!.undoing {
undo!.setActionName("Remove Person")
}
employees.removeObjectAtIndex(index)
}
I think it's cleanest to replace the old-fashioned Objective-C NSUndoManager method entirely:
private class SwiftUndoPerformer: NSObject {
let closure: Void -> Void
init(closure: Void -> Void) {
self.closure = closure
}
#objc func performWithSelf(retainedSelf: SwiftUndoPerformer) {
closure()
}
}
extension NSUndoManager {
func registerUndo(closure: Void -> Void) {
let performer = SwiftUndoPerformer(closure: closure)
registerUndoWithTarget(performer, selector: Selector("performWithSelf:"), object: performer)
//(Passes unnecessary object to get undo manager to retain SwiftUndoPerformer)
}
}
Then you can Swift-ly register any closure, and not worry about wrapping things up to get around an outdated interface:
undoManager.registerUndo {
self.insertObject(person, inEmployeesAtIndex: index)
}

Resources