I'm pretty new to Cocoa, and I have an NSComboBox which I'm populating with an NSComboBoxDataSource. So far so good, but the one thing I haven't been able to figure out is what type of objects can be stored. I've had success with strings, but I'd like to be able to store more info than what is displayed. I've tried storing a custom object which represents what is being listed (clients), but of course nothing is displayed.
You can save any object, in your model.
But for display purpose string will be good. And based on the selection of the string value, you can fetch the entire object ( it may be in arrays or dictionaries). Then anywhere in your gui you can display all the propeties.
As, when you nslog dictionary or array having objects, you see only its memory locations not the values inside the object. You have to again break them to display. So is the case here.
Related
I'm struggling to understand how to use UICollectionViewDiffableDataSource and NSDiffableDataSourceSnapshot to model change of items.
Let's say I have a simple item which looks like this:
struct Item {
var id: Int
var name: String
}
Based on the names of the generic parameters, UICollectionViewDiffableDataSource and NSDiffableDataSourceSnapshot should operate not with Item itself, but only with identifier, which Int in this example.
On the other hand, again based on names of generic parameters, UICollectionView.CellRegistration should operate on complete Item's. So my guess is that UICollectionViewDiffableDataSource.CellProvider is responsible for finding complete Item's by id. Which is unfortunate, because then aside from snapshots, I need to maintain a separate storage of items. And there is a risk that this storage may go out of sync with snapshots.
But it is still not clear to me how do I inform UICollectionViewDiffableDataSource that some item changed its name without changing its id. I want UICollectionView to update the relevant cell and animate change in content size, but I don't want insertion or removal animation.
There are two approaches that would work solve your problem in this scenario.
The first is to conform your Item model to the hashable protocol. This would allow you to use the entire model as an identifier, and the cell provider closure would pass you an object of type Item. UICollectionViewDiffableDataSource would use the hash value for each instance of your model (which would consider both the id and name properties, thereby solving your name changing issue) to identify the data for a cell. This is better than trying to trick the collection view data source into considering only the id as the identifier because, as you stated, other aspects of the model might change. The whole point of structs is to act as a value-type, where the composition of all the model's properties determine its 'value'...no need to trick the collection view data source into looking only at Item.id.
Do as you said, and create a separate dictionary in which you can retrieve the Items based on their id's. While it is slightly more work to maintain a dictionary, it is a fairly trivial difference in terms of lines of code. All you should do is dump and recalculate the dictionary every time you apply a new snapshot. In this case, to update a cell when the model changes, make sure to swap out the model in your dictionary and call reloadItem on your snapshot.
While the second option is generally my preferred choice because the point of diffable data source is to allow for the handling of massive data sets by only concerning the data source with a simple identifier for each item, in this case your model is so simple that there's really no concern about wasted compute time calculating hash values, etc. If you think your model is likely to grow over time, I would probably go with the dictionary approach.
I have a class which as its properties has a number of NSSet values. The content of these sets is not stored in Core Data, but comes from another source. They are basically ID strings which are unique. No point in filling up Core Data with them as separate entities.
However, as far as I can see it's not possible to store an NSSet as an attribute of an NSManagedObject. I guess I would need to serialise it into binary data by hand, whenever the object gets stored, and deserialise it when it gets retrieved from the persistent store? The same would also apply to storing other collection classes.
Has anybody else ever come across this issue and is able to give some advice?
You can set the attribute type to 'transformable' and then directly store the set into the attribute. Be sure that all of the contents of the set conform to < NSCoding >.
In Xcode, in the Project Navigator open up the project_name.xcdatamodeld source for the model editor and select the entity in question. Choose Editor in the Xcode menu bar and there is a Create NSManagedObject subclass... item. Make the subclass and use it for extra non-Core Data properties. The subclass becomes part of the model in place of the entity. It retains all of the entity's behaviors.
I'm working on a Document-based, Core Data-based MacOS Cocoa app, and am using an NSArrayController who's selectedIndexes I would like to persist (in the document), so that when a user saves the document and reopens it elsewhere the same rows in the table (/ object the detail view is on) are selected. Is there any way to do this?
I can generally figure out what I think the way to save an NSIndexSet would be, but I can't figure out how to save just one specific object and get it back, particularly with bindings and interface builder.
Thanks!
In theory, there's nothing difficult here. Storing selectionIndexes is no different from storing any other NSIndexSet. You just need to make an entity with a Transformable property called, maybe selectedRows, and then put the value of arrayController.selectionIndexes in there right before you save the file, and read it out right after reading the document in. You don't need to worry about anything else, CoreData will just handle it. If you don't already have a logical "root" element to store this on, just make a new entity, perhaps called UIState with such a property on it and store it there.
The way I see it, you probably don't actually want to bind this value to that entity, for a couple reasons. The first is that you probably don't want changing the selection alone to dirty the document, right? I mean, maybe you do, but that sounds like more of an annoyance than a feature, and saving the selection, in general, sounds more like a convenience than a charter feature. But I don't know your application, so it's hard to say.
The other reason is it looks like if you bind arrayController.selectionIndexes and arrayController.contentArray both in IB, when it binds the contentArray binding, it looks like it will write out an initial value for selectionIndexes right after contentArray is bound when the doc opens, clobbering your real value. (I can imagine it being something like, 'You just set the content, so any previous value for the selection is invalid now, let's overwrite it with nothing!') So, at the very least, if you bound the value, you'd have to figure out some clever way to avoid having your real value clobbered by this initial write.
It seems to me like you'd be better off just not binding this directly to the model, but rather binding it to a transient property on the document which you can read from at save time, and write to at doc open time. That's where I'd go with this.
I'd like to have an NSArray or NSMutableArray that always shows a filtered view of a data set. That is, if the filter is set to "show me things with the letter a", and an item "blah" is added, that item would automatically show up in the list. However, if "bluh" were added, it would not show up in the filtered list (but would still really be there in the underlying dataset).
I see that there are filter methods on NSArray and NSMutableArray, but these are one shot methods. That is, the filter occurs when you call the method and never again (unless of course you call the filter method again).
I'm coming from the Java world, were I used Glazed Lists extensively for this kind of thing. I was hoping for a similar solution baked into Cocoa.
You'll most likely want to use NSArrayController as suggested by Ole.
You can use setFilterPredicate: on it as suggested, and then you'll want to access the objects by calling arrangedObjects on the controller. You don't need setAutomaticallyRearrangesObjects: unless you're intending to have your data sorted (using sort descriptors — NSSortDescriptor instances).
NSArrayController is really set up to handle displaying things in a table view very easily, so if that's your end goal, then this is exactly what you want. It integrates nicely with NSSearchField to handle predicates in the UI.
If you're using this as some back end object that's getting passed around, then you might want to write something yourself that's a little less heavy-weight than NSArrayController.
Coming from the iPhone, I'm not very familiar with NSArrayController but you might want to take a look at it. It seems to me that setFilterPredicate: in combination with setAutomaticallyRearrangesObjects:YES might do the trick.
Other than that, it should be quite easy to roll your own solution using Key-Value Observing. Start with a mutable copy of the array you want to filter and filter it with filterUsingPredicate: as you noted above, then register yourself as an observer for insertions and deletions in the original array and when your observer method gets called, call evaluateWithObject: on the newly inserted objects to decide whether to insert them into your filtered array.
I have an object that implements the indexed accessor methods for a key called contents. In those accessors, I call willChange:valuesAtIndexes:forKey: and didChange:valuesAtIndexes:forKey: when I modify the underlying array.
I also have a custom view object that is bound to contents via an NSArrayController. In observeValueForKeyPath:ofObject:change:context: the only value in the change dictionary for the NSKeyValueChangeKindKey I ever see is NSKeyValueChangeSetting. When I'm adding objects to the array, I expect to see NSKeyValueChangeInsertion.
Recreating my view's internal representation of the objects it observes every time I insert a single item -- particularly when I'm bulk loading hundreds of items -- presents quite a performance problem, as you'd imagine. What am I doing wrong that Cocoa seems to think I'm setting a completely new array each time I add or remove a single item?
(Note to all readers: I hate using answers for this, too, but this discussion is too long for comments. The downside, of course, is that it ends up not sorted chronologically. If you don't like it, I suggest you complain to the Stack Overflow admins about comments being length-limited and plain-text-only.)
I don't understand what you mean by implementing array accessors in the view.
Implement accessors, including indexed accessors, for the mutable array property that you've exposed as a binding.
Bindings is built on top of KVO.
And KVC.
All bindings are implemented using observeValueForKeyPath:
Overriding that is one way, sure. The other way is to implement accessors in the object with the bindable property (the view).
My custom view provides a binding that the app binds to an array -- or in this case, an array controller. Accessor methods apply to KVC, not KVO.
Cocoa Bindings will call your view's accessors for you (presumably using KVC). You don't need to implement the KVO observe method (unless, of course, you're using KVO directly).
I know this because I've done it that way. See PRHGradientView in CPU Usage.
Curiously, the documentation doesn't mention this. I'm going to file a documentation bug about it—either I'm doing something fragile or they forgot to mention this very nice feature in the docs.
It absolutely matters that I'm getting a set message on every array update. I wouldn't have posted it as a question if it didn't matter.
There are quite a large number of people who engage in something called “premature optimization”. I have no way of knowing who is one of them without asking.
I have an object that implements the indexed accessor methods for a key called contents. In those accessors, I call willChange:valuesAtIndexes:forKey: and didChange:valuesAtIndexes:forKey: when I modify the underlying array.
Don't do that. KVO posts the notifications for you when you receive a message to one of those accessors.
I also have a custom view object that is bound to contents via an NSArrayController. In observeValueForKeyPath:ofObject:change:context: the only value in the change dictionary for the NSKeyValueChangeKindKey I ever see is NSKeyValueChangeSetting. When I'm adding objects to the array, I expect to see NSKeyValueChangeInsertion.
For one thing, why are you using KVO directly? Use bind:toObject:withKeyPath:options: to bind the view's property to the array controller's arrangedObjects (I assume) property, and implement array accessors (including indexed accessors, if you like) in the view.
For another, remember that arrangedObjects is a derived property. The array controller will filter and sort its content array; the result is arrangedObjects. You could argue that permuting the indexes from the original insertion into a new insertion would be a more accurate translation of the first change into the second, but setting the entire arrangedObjects array was probably simpler to implement (something like [self _setArrangedObjects:[[newArray filteredArrayUsingPredicate:self.filterPredicate] sortedArrayUsingDescriptors:self.sortDescriptors]]).
Does it really matter? Have you profiled and found that your app is slow with wholesale array replacement?
If so, you may need to bind the view directly to the array's content property or to the original array on the underlying object, and suffer the loss of free filtering and sorting.
I call the KVO methods manually for reasons outside the scope of this issue. I have disabled automatic observing for this property. I know what I'm doing there.
I don't understand what you mean by implementing array accessors in the view. Bindings is built on top of KVO. All bindings are implemented using observeValueForKeyPath: My custom view provides a binding that the app binds to an array -- or in this case, an array controller. Accessor methods apply to KVC, not KVO.
It absolutely matters that I'm getting a set message on every array update. I wouldn't have posted it as a question if it didn't matter. I call something like
[[modelObject mutableArrayValueForKey:#"contents"] addObjectsFromArray:hundredsOfObjects];
On every insertion, my view observes a whole new array. Since I'm potentially adding hundreds of objects, that's O(N^2) when it should to be O(N). That is completely unacceptable, performance issues aside. But, since you mention it, the view does have to do a fair amount of work to observe a whole new array, which significantly slows down the program.
I can't give up using an array controller because I need the filtering and sorting, and because there's an NSTableView bound to the same controller. I rely on it to keep the sorting and selections in sync.
I solved my problem with a complete hack. I wrote a separate method that calls the KVO methods manually so that only one KVO message is sent. It's a hack, I don't like it, and it still makes my view reread the entire array -- although only once, now -- but it works for now until I figure out a better solution.