I am using an NSOutlineView with drag and drop support. There is an NSOutlineViewDataSource function entitled outlineView(_:pasteboardWriterForItem:).
This call serializes the item passed into the function and allows the user to retrieve the serialized item in outlineView(_:acceptDrop:item:childIndex:)
#MainActor optional func outlineView(
_ outlineView: NSOutlineView,
acceptDrop info: NSDraggingInfo,
item: Any?,
childIndex index: Int
) -> Bool
That being said, I don't understand how I would write multiple rows of data using outlineView(_:pasteboardWriterForItem:) . It gets called for each item being moved, but the pasteboard only keeps a reference to the last item. Apple deprecated several multiple row write/move functions.
How should I implement this? I have tried several solutions but they're hacky and seem like they're not the canonical solution.
The information is in the info parameter.
There is an API enumerateDraggingItems(options:for:classes:searchOptions:using:) of NSDraggingInfo to enumerate and proceed the items.
In outlineView(_:pasteboardWriterForItem:) you usually just
return item as? NSPasteboardWriting
Related
I've been running into this every now and then I'm always questioning myself whether I'm using RxSwift (or reactive means altogether) the wrong way.
The challenge is converting value types to something representable on the UI.
Usually on the lower level I'm storing data with simple types but I need something more stateful on the UI level. Just to give you an example, consider I have list of following types:
struct Person {
let firstName: String
let lastName: String
}
On the UI, however, I'm binding view models created from these items into a UITableView instance. I could achieve this by simply mapping from one type to another:
let displayedPersons = listOfPersons.map { PersonViewModel($0) }
This would make all items to be recreated on each update which I'm trying to avoid. I'm using MVVM and would like to keep the view model instances due to their transient state. Reloading table view on each update would also mess up animations.
I'm thinking if a custom binding could help here, binding one observable to another with cached mapping. Another solution that I've ended up doing is simply looping the observable so that when mapping, I get the previous value which I'll use as a cache.
Effectively I would need to map only the new items and keep the existing ones. Any ideas how would I achieve this?
In my very biased opinion, MVVM is good only for very complex UI where elements need to update dynamically and independently from each other. For all other cases I use my own library https://github.com/maxvol/RaspSwift (NB: it is not only for UI, but for UI as well). The core idea stems from MVI, and boils down to having a new snapshot of the state on every mutating event. So in your case the state would contain a collection of cached PersonViewModel, which will be partially updated upon receiving mutating events. The whole thing would be bound to UITableView via RxDataSources library.
One simple solution that I'm currently using is simply to map and recycle the old items if they exist.
I've created an extension to make it work with sequences. In memoryLookup you will receive the previous values and can reuse any item from the previous round.
public extension ObservableType where E: Sequence {
public func mapWithMemory<R>(memoryLookup: #escaping (Self.E.Element, [R]) throws -> R?, transform: #escaping (Self.E.Element) throws -> R) -> RxSwift.Observable<[R]> {
return self.scan([]) { (acc, elements) -> [R] in
let mapped = try elements.map { e in
return try memoryLookup(e, acc) ?? transform(e)
}
return mapped
}
}
}
And here's an example usage where an array of Ints is mapped to array of Strings.
func testMapWithMemory() {
var creationCounts = [Int: Int]()
let items = Observable.from([[1, 2],[2, 3],[2, 3]])
let mapped = items.mapWithMemory(memoryLookup: { (item, previousItems) -> String? in
return previousItems.first { $0 == "\(item)" }
}) { (item) -> String in
creationCounts[item, default: 0] += 1
return "\(item)"
}
let xs = try! mapped.toBlocking().toArray().last!
XCTAssertEqual(xs, ["2", "3"])
XCTAssertEqual(creationCounts, [
1: 1,
2: 1,
3: 1
])
}
Use at your own risk. And feel free to improve and share.
Also note that this is only useful if you need to avoid creating new items. In my case I'm using classes and binding UI elements to these items, so I don't want to recreate those.
I'm currently working on creating a framework for IOS. My framework has to send request to my server, parse the response and then update the UITableView of the main page of the App.
I've already create a Singleton class "MyUpdater" which has a static func "addRow"
In my Application i have 2 objects: the tableView and a list of objects that i use for the tableView.
I want the "addRow" method (of my FrameWork) to add an object to the object list of the App asynchronously.
So i created the following method:
public static func addRow(tableView: UITableView, list: inout Array<Product>){
var my_object = Object()
list.insert(my_object, at: 1)
tableView.beginUpdates()
tableView.insertRows(at: [IndexPath(row: 1, section: 0)], with: .automatic)
tableView.reloadData()
tableView.enUpdates()}
And i get the following error: escaping closures can only capture inout.
How can i asynchronously update the list of the Application from the pod (framework) ?
Thank you.
Kind regards
why are you using inout ? I think this makes no sense here, since inout isn't a pass-by-reference.
Did you try something like: ?
public func addRow(tableView: UITableView, list: Array<Product>){
Dispatch.main.async {
//update the list + tableview
}
}
I've built an NSOutlineView that gets dynamically updated data from an NSTreeController and that all works fine. What I can't seem to do is work backwards from there based on a user selection in the NSOutlineView.
var deviceStore = [TreeNode]()
is my backing datastore that is updated in real-time it is an array of Device Objects, which may )or may not) contain an array of Service objects as children.
This all works. But when I select a row in the Outline View, I need to work my way back to the original object in the deviceStore -- or, at the very least, get the displayed data from the OutlineView so that I can walk the deviceStore to find the original item.
What I've got is func outlineViewSelectionDidChange(_ notification: Notification) {} which is called when a selection is made, and I can, from that, extract the NSTreeController TreeNode via treeController.selectedNodes but from there, I am in the weeds. The selectedNodes is the complete array of the selected Node, so if it's a child (leaf) node, it includes its parent node, and all its siblings.
Give then Table shown here:
The selectedNodes array looks like this:
<NSTreeControllerTreeNode: 0x6080000c4590>, child nodes {
0:<NSTreeControllerTreeNode: 0x6000000ca6b0>, child nodes {
0:<NSTreeControllerTreeNode: 0x6000000caf70>, child nodes {}
1:<NSTreeControllerTreeNode: 0x6000000cafe0>, child nodes {}
2:<NSTreeControllerTreeNode: 0x6000000cb050>, child nodes {}
}
1:<NSTreeControllerTreeNode: 0x6080000d1790>, child nodes {
0:<NSTreeControllerTreeNode: 0x6000000cce80>, child nodes {}
}
}
And the selectedIndex is 4.
I can't see how to get back to what, in my data model, would be deviceStore[0].serviceStore[2] from this information.
If I could retrieve the value in the Service ID column from the selected Row, I could simply walk the deviceStore tree to find it.
I'm sure there's a simply, elegant, easy way to do this that I just haven't found yet, but being new to NSTreeControllers and NSOutlineViews I'm lost.
You may try to access directly the associated object(s) like this:
let selectedService = treeController.selectedObjects.first as? Service
The docs are here.
also make sure your NSTreeController is correctly configured to use your class' objects:
Alternatively (if you want to work directly with your data source) you may want to get the index path of the selected object in the NSTreeController:
var selectionIndexPath: IndexPath? { get }
I use following with Core data,
func outlineViewSelectionDidChange(_ notification: Notification) {
let item = outlineView.item(atRow: outlineView.selectedRow) as! NSTreeNode
let fileItem = item.representedObject as! FileItemMO
// Do what you need to do with object fileItem
}
I was never able to actually get back to the TreeController backing-store data baed on where the user clicks in the TreeView. What I was able to do was to work backwards to the data though.
func outlineViewSelectionDidChange(_ notification: Notification) {
let selectedIndex = (notification.object as AnyObject).selectedRow!
let selCol1 = outlineView.view(atColumn: 0, row: selectedIndex, makeIfNecessary: false)?.subviews.last as! NSTextField
let selCol2 = outlineView.view(atColumn: 1, row: selectedIndex, makeIfNecessary: false)?.subviews.last as! NSTextField
let devName = selCol1.stringValue
let devID = selCol2.stringValue
...
}
I could then 'walk' the deviceStrore array until I found the devName and devID in it, and deal with it accordingly.
Probably not the most elegant solution, but at least it finally works.
I would like to implement a NSTokenField that will show Tokens that - when hovering over the token - show a removal icon. Subsequently when I click on the icon I want the token to be removed.
After a lot of searching it seems that this is not possible with the standard NSTokenField. If someone knows how please let me know.
I have taken a look at https://github.com/octiplex/OEXTokenField and based on that code I have made a CustomTokenField implementation in Swift. So far so good I have a working CustomTokenField and when I hover over the token it shows a removal icon.
The next phase turns out to be a problem that I cannot figure out myself. How can I get a click on the token trigger a callback.?
The token class is derived from the NSTextAttachmentCell and the CustomTokenField is derived from the NStokenField:
class CustomTokenAttachmentCell: NSTextAttachmentCell {
. . .
}
class CustomTokenField: NSTokenField {
. . .
}
I have tried to approach this using two different angles:
Through the CustomTokenAttachmentCell
The NSTextAttachmentCell implements the NSTextAttachmentCellProtocol.
public protocol NSTextAttachmentCellProtocol : NSObjectProtocol {
. . .
public func wantsToTrackMouse() -> Bool
public func highlight(flag: Bool, withFrame cellFrame: NSRect, inView controlView: NSView?)
public func trackMouse(theEvent: NSEvent, inRect cellFrame: NSRect, ofView controlView: NSView?, untilMouseUp flag: Bool) -> Bool
. . .
}
This is hopeful. So I implemented these methods in CustomTokenAttachmentCell and wantsToTrackMouse() is actually being called. I have implemented that to return ‘true’.
override func trackMouse(theEvent: NSEvent, inRect cellFrame: NSRect, ofView controlView: NSView?, untilMouseUp flag: Bool) -> Bool {
Swift.print(“trackMouse”)
return true
}
override func highlight(flag: Bool, withFrame cellFrame: NSRect, inView controlView: NSView?) {
Swift.print("highlight")
}
override func wantsToTrackMouse() -> Bool {
Swift.print(“trackMouse”)
return true
}
The other two methods are never called. Is there something else that needs to be done to make that they are being called?
Through the CustomTokenField
I also tried to approach this from the CustomTokenField. It is possible to get mouse events using MouseDown(), however I could not
find a way to actually access the Tokens from the cells.
I have seen many posts here on StackOverflow and I have seen tips but none of them seems to point in the right direction.
Somehow I have come to the conclusion that you can only get mouse events in the case there is a NSControl in the hierarchy. For tokens that is not the case. An NSControl is part of the hierarchy for views hence my attempt to achieve this through the CustomTokenField but I run in a dead-end there as well.
E.g. this question Clicking token in NSTokenField is exactly the same but setting the action or target will generate a fatal error because the setAction and setTarget are stubs for the base class.
I am a beginning programmer on Coacoa, so hopefully this is just a matter of lack of knowledge.
Any advise would be appreciated.
Have you tried adding an NSButton on top of all of the whole CustomTokenAttachmentCell view? Then add an #IBOutlet action to the button for the click and pass that via delegation to the TokenField where you can control the tokens that are displayed.
I am also trying to implement this in my app, so if you are able to share any of the code it would be greatly appreciated. Thanks!
Background:
I designed a TableViewDataSource class that provides an implementation for UITableViewDataSource and UITableViewDelegate. You instantiate TableViewSection objects, which are passed to the TableViewDataSource which are used to configure cells, section headers, handle selection, row insertion, etc.
The TableViewSection object has a property called dataSource: [AnyObject]?, which, when set, is used to calculate the number of rows in the section, and provide an object for the cell configuration block:
// get the section, dequeue a cell for that section, retrieve the item from the dataSource
// ...
tableSection.cellConfigurationBlock?(cell: AnyObject, item: AnyObject?, indexPath: NSIndexPath)
return cell
What I'd like to do is assign a reference to an array from my viewModel to my tableSection.dataSource, having my viewModel update the array, in turn updating the table view. In Swift, you cannot pass an array by reference. The workaround seems to be to use an NSMutableArray, but with that comes a loss of type safety, and greater cognitive load while translating objects back and forth from Swift to Foundation.
Working Example:
let kCellIdentifier = "SomeCellIdentifier"
class MyViewController: UITableViewController {
// Property declarations
#IBOutlet var tableDataSource: TableViewDataSource!
var viewModel: MyViewControllerViewModel = MyViewControllerViewModel()
override func viewDidLoad() {
super.viewDidLoad()
self.setupTableView()
self.refresh()
}
func setupTableView() {
var tableSection = TableViewSection(cellIdentifier: kCellIdentifier)
tableSection.dataSource = self.viewModel.collection
// tableSection configuration
// ...
self.tableDataSource.addSection(tableSection)
}
func refresh() {
self.viewModel
.refresh()
.subscribeNext({ result in
self.tableView.reloadData()
}, error: { error in
self.logger.error(error.localizedDescription)
})
}
}
The refresh() method on the viewModel hits my API service, updates it's collection property on response, and provides the result on the next event of an RACSignal (RACSignal is a class provided by Reactive Cocoa and really, besides the point).
I've found one workaround, which involves reassigning the data source each time a single update is made, or after a batch update.
func refresh() {
self.viewModel
.refresh()
.subscribeNext({ result in
self.updateDataSource()
self.tableView.reloadData()
}, error: { error in
self.logger.error(error.localizedDescription)
})
}
func updateDataSource() {
self.tableDataSource.tableSectionForIndex(0)?.dataSource = viewModel.collection
}
This approach works, but only temporarily as a workaround. As a TableViewDataSource grows and becomes more complex, this method becomes increasingly more complex with imperative, procedural code, the opposite of what I set out to achieve when writing the class.
Question
Is there any workaround to stick to native Swift Array's to achieve the equivalent of passing a Foundation NSArray or NSMutableArray by reference?
Bonus Question
Can someone provide me with some class/struct design tips to accomplish the desired goal in pure Swift?
The simple solution is to wrap the array in a class. The class instance is passed by reference so the problem is effectively solved: a change to the array through any reference to the class instance affects the array as seen through every reference to that class instance.
The class in question can be extremely lightweight - basically, it just serves as a thin wrapper that carries the array along with it, and a client accesses the array directly through the class instance - or, just the opposite, you can design the class to manage the array, i.e. the class deliberately presents an array-like API that shields clients from the underlying implementation. Either approach might be appropriate; I've certainly done both.
Here's an example of the first kind of situation. My model object is an array belonging to a UIDocument subclass. My view controller is a UITableViewController. The user is going to view, add, and edit model entities in the table. Thus, the UITableViewController needs access to the UIDocument's array (which happens to be called people).
In Objective-C, my UITableViewController simply held a reference to the array, self.people, which was an NSMutableArray. This was just a pointer, so changes to self.people were also changes to the UIDocument's people - they are one and the same object.
In Swift, my UITableViewController holds a reference to the UIDocument object, self.doc. The array, which is now a Swift array, is "inside" it, so I can refer to it as self.doc.people. However, that's too much rewriting! Instead, I've created a calculated variable property self.people which acts as a gateway to self.doc.people:
var doc : PeopleDocument!
var people : [Person] { // front end for the document's model object
get {
return self.doc.people
}
set (val) {
self.doc.people = val
}
}
Hey presto, problem solved. Whenever I say something like self.people.append(newPerson), I'm passed right through to the UIDocument's model object people and I'm actually appending to that. The code thus looks and works just like it did in Objective-C, with no fuss at all.