Build environment: Xcode 7 beta 7
OS X: 10.10.5
Swift 2
I have a project that is using the BSManagedDocument class from github to wrap core data into an NSDocument. I am using Cocoa bindings to get the data from core data to an NSOutlineView using an NSTree Controller.
The object model is fairly simple. The object (called SourceGroup_ tied to the tree controller has a one-to-many relationship with itself to provide the hierarchical data for the tree controller. It also has an optional one-to-one relationship with a Media object that provides a URL for associated media (image, video, etc.).
The outline view has three columns bound, two are bound to properties of the main object while the third is a thumbnail image that comes from the one-to-one media relationship.
Everything displays fine but someitmes when I close the document I get an EXC_BAD_ACCESS in _NSGetUsingKeyValueGetter that goes back though NSAutoUnbinder and [NSTableCellView release] back through [NSAutoreleasePool drain]. This doesn't happen every time but it does happen occasionally.
It I remove the column that is bound through the one-to-one relationship it never crashes. If I add in a non-core data property on SourceGroup that simply returns the image from the preferredMedia relationship and then bind to that property on SourceGroup, it never crashes.
It doesn't matter what the type of the field is. If I bind through the relationship, it (sometimes) crashes on close, whether it's String or NSImage.
I've even tried generating my core data objects as Objective C rather than Swift but that made no difference in behavior. No matter what I change the one constant is that if I bind the view in the table column through the relationship it sometimes crashes on close. Yet, on the detail side, if I bind to the same fields through the selectionIndex that works fine and never crashes. The crash only occurs if it's in the outline view.
I know the descriptions can be a bit vague so I prepared some screenshots unfortunately I appear to lack enough reputation to share them. I'll try to describe the setup.
The tree controller is in entity mode with the entity set to SourceGroup. It's source descriptors and managed object context are bound to my view controller.
The outline view's content is bound to the tree controller's arrangedObjects controller key. The selectionIndexPaths and sort descriptors are also bound to the tree controller.
The value for the NSImageView in the column is bound to the table cell view with the model key path set to objectValue.preferredMedia.thumbnail.
This all displays properly but causes intermittent problems on closing the document. If I put a thumbnail image property into SourceGroup and then bind that image view to objectValue.thumbnail I never see the issue
I don't see anything in the docs about this type of setup being a problem but I'm also new enough to OS X programming that I may have missed something.
Can anyone see a problem in what I'm trying to do?
I had a similar problem with Core Data, treecontroller and images. The managed object context is released before the outlineview and treecontroller. The context is turning managed objects into faults before the bindings remove their observers.
A workaround is setting the content of the treecontroller to nil when the window will close.
Related
I am trying to bind a NSPopUpButton in a view based NSTableView with NSArrayContollers using Xcode 8.1. I have an macOS app that had been using a cell based NSTableView and I would like to convert it to a view based table, however, I have been completely unsuccessful in doing this. I have looked at all the various wed postings, but nothing seems to work for me. I have been working on this for over two days.
Here is my design:
accountArrayController is bound to the larger table and this all seems to work just fine.
patientArrayController is aNSMutableArray of NSString's that contains the list of patients to be populated in the menu items of the NSPopUpButton.
Here are the actual bindings for the NSPopUpButton
I am guessing that the problem is in the Content Values bindings, I have tried many variations. When I compile this, I get
.../xxx.storyboard: Exception while running ibtool: *** -[__NSArrayM insertObject:atIndex:]: object cannot be nil
However, when I try other variations, Xcode (really ibtool) hangs for a long time and then exits with an error code of 255.
How can I resolve this? I'm happy to provide other binding information and code blocks, if needed.
The model attribute name you used in the NSTableColumn bind, you want to also use in the Table Cell View bind - 3rd level down from table column. It should have filled in already 'objectValue' or 'selection' or 'arrangedObjects', just add . and you should be good.
This project is to create an editor tool in Cocoa & Swift for Mac OS X that will edit a non-trivial data structure. A pared down schema looks like this:
Game
title : String
[ Room ]
Room
roomKey : String
roomName : String
roomDescription : String
[ Object ]
[ Exit ]
Object
objectDescription: String
Exit
destinationRoomKey : String
sourceRoomKey : String
The current implementation - the third go-around - has a single Document.xib file (the app is document based) and in that I'm hooking up a NSObjectController to the base game object loaded by the document, and an NSArrayController to the game objects array of rooms. There's an NSObjectController for the Room. I have not done the objects or exits yet.
The views are handled by a base root view controller, which swaps sub-views in and out as you go up and down the view hierarchy. On the view for the root game state, you click an "edit" button that slides in the table view for the list of rooms. Clicking a button in one of the room rows slides in a room detail view which has its own controller.
This is all working well enough. I have hooked up the object controller of the room so that it gets the selected room of the rooms array as its object, using Interface Builder bindings. I can do this because I have all the views, view controllers and data model controllers in the one XIB file.
However: now I am adding the game objects to this mix and the XIB file is getting very unwieldy. I really feel like I want to do this in separate XIB files, but when I tried that previously I was not able to hook up the controllers to each other. I tried manually writing code to load & save the data at the same time as the controllers had their view displayed and removed but this was flakey and error prone. So far the most elegant and robust result I have had is with this one XIB approach.
I looked at the programmatic API for binding but could not understand how to get it to work, or how to discover what the key path would look like. I suppose if it was possible to do the bindings programatically you could put the different parts in different XIB's and do the bindings at load time. But I could not find any examples of anyone doing that successfully and it seemed a road to madness.
At present I'm having no problems with Swift and its relations to Cocoa and Objective-C so if anyone has answers in Objective-C or Swift I'd be happy to hear them. I have not put Swift as a key word for this question as its not part of the problem.
I've seen the StackOverflow answer about hierarchical models, and its what I'm currently doing, so it doesn't help. The problem is that this approach gets unwieldy when there's several layers of master-detail.
I've also seen the StackOverflow answer about sharing controllers, and it was what I tried before and where I ran into the issue described there, that if you specify a controller object in a NIB it will get instantiated as an independent object. Hence why I have the huge-mega-NIB-of-death approach at present.
I could make the title of this question "cannot make programmatic bindings work" but I'm not sure that that is the right approach anyway.
Surely someone has done the job of making a non-trivial data model work with Cocoa before?
Your secondary NIBs should be view NIBs, their owners would be an instance of NSViewController or a custom subclass. That has a representedObject property. The NIB and its view controller class should be thought of as stand-alone, theoretically-reusable components. That is, in theory, that NIB could be used in multiple contexts to represent a particular kind of object. So, you should typically not want connections to other parts of your UI or their controllers, other than knowing what object this view is being loaded to represent.
Within the NIB, you can either bind to the File's Owner with a model key path that goes through representedObject or add an NSObjectController that binds to File's Owner's representedObject and then bind your views through that with controller key selection.
When you load such a secondary NIB, you would have to set its representedObject to the object it's supposed to represent, taken from the array controller's selection. This should be done in code, presumably the same code that decides it needs to load the NIB and does so.
If the design of your UI is such that a detail view needs to trigger a behavior that's best handled at a higher level — for example, a Room view needs to arrange for an Exit view be slid into the window, but not as a subview of its own view — the detail view controller should define a delegate protocol and implement a delegate property. For example, the Room view controller's delegate protocol might have a method -roomViewDidChangeSelectedExit:. The Room view controller would call that on its delegate, passing self. You would set some coordinating controller (perhaps the window controller) as the the detail view's delegate.
It's not clear to me if the "detail" views and the "master" views are visible simultaneously. That is, can the user change the object that the detail view is meant to show without backing up first? If so, there are a couple of approaches.
You could set up the bindings programmatically when the view is loaded. This would be the responsibility of the controller that loaded the detail view. It's not the responsibility of the detail view's controller. That doesn't have the higher-level perspective and knowledge to set up the binding. Anyway, you could do it like:
[detailViewController bind:#"representedObject" toObject:self.arrayController withKeyPath:#"selectedObjects.firstObject" options:#{ }];
Be sure to call -unbind: before the detail view controller is released.
The other way to do it is to simply observe the changed selection using a non-Bindings approach, and set the new representedObject in the code that gets triggered. For example, if your master view lets the user select an item in a table view, you would set up the table view's delegate (almost certainly already done) and implement -tableViewSelectionDidChange:. In that delegate method, query the newly-selected item and assign it to detailViewController.representedObject.
I am using MonoMac to build a desktop download manager for Mac in C#.
My XIB has a Table View, whose columns are bound to an NSArrayController. The array controller is connected to my Main Window Controller through an IBOutlet. The array holds a bunch of HttpDownload objects, which derive from NSObject. These HttpDownload objects contain properties such as TotalSize, TotalDownloaded, Bandwidth, etc. I have decorated these properties with an [Export] attribute.
In the controller I add some HttpDownload objects to the NSArrayController using the AddObject method. A background process, started with Task.Factory.StartNew() begins the download asynchronously and updates the bound properties such as TotalDownloaded and Bandwidth as data is received.
I can see these new values being reflected in the Table View, but only once I've "forced" a UI update, for instance by causing the window to lose focus, gain focus, or by clicking on a button within the window.
I have tried setting Continuously Updates Value in IB, but this makes no difference (and reading the docs, I didn't think it should).
Does anyone know to make the UI update the bound values in "real time", instead of only when a window event occurs?
I figured this out shortly after I posted this question.
It seems that we need to manually call WillChangeValue() and DidChangeValue() for at least one of the keys that are being updated, for instance, when I updated the total downloaded:
WillChangeValue("DownloadedBytes");
DownloadedBytes += bytesRead;
DidChangeValue("DownloadedBytes");
In my case, calling these methods for just one of the updated keys seems to be enough to force an update of all the bound values.
For reference, in Objective-C these selectors are called [self willChangeValueForKey:#"keyname"] and [self didChangeValueForKey:#"keyname"].
In my Cocoa app, I have a sheet with a one-column NSTableView that lists a bunch of files in a directory (the app makes back-ups of it's main database, provides this list to users so they can revert to a particular back-up). The content is loaded into and provided to the table view by an NSArrayController, each object is just an NSFileWrapper (I'm considering using NSURL instead, but I digress). The NSArrayController handles sorting, enabling the buttons when a row is selected via bindings, that's all great. I have an NSWindowController subclass object (BackupsSheetController) that hooks all this up and exists in the sheet's nib.
However, when a user edits one of the cells, I want to respond to that change from BackupsSheetController by appropriately re-naming the file represented by that cell, putting it in its new location. Since the table view is bound to the NSArrayController, I don't get sent the NSTableViewDataSource message – tableView:setObjectValue:forTableColumn:row:. If I set my BackupsSheetController as the datasource for the NSTableView object in the nib, I get sent that message sometimes, but not very often, to say nothing of every time.
Most questions and examples I see out there for this scenario handle this all by using a custom model class for items in their table view, and make some controller object an observer for changing properties that they wish to respond to. In other words, each item would be something like a BackupNode object, and BackupsSheetController would observe each for changes to the name property (or whatever I would call it). That seems totally overkill for my scenario, but I also don't want to ditch the bindings I've already got in use and I don't see another way to do this. Is there another way to do this, to make sure I reliably get the setObject:... message? Or should I drop the NSArrayController and make BackupsSheetController the delegate and datasource for the table?
In the "BackupNode" scenario, I don't see why BackupsSheetController would observe each for changes in its name. That's a very roundabout way of doing things. I would think that the hypothetical BackupNode object would simply do the necessary work in its setter for the name property.
Anyway, I recommend using proper model objects. When you try to build a model with only Cocoa-provided objects like NSFileWrapper, NSURL, or NSMutableDictionary, you end up doing more work in the long run than if you just make a proper model object.
On a tangential topic, why is your window controller in the NIB? It should be the thing which loads (and owns) the NIB, which of course requires that it exist prior to the NIB being loaded, which means it can't be instantiated in the NIB.
Fairly new to Mac and XCode 3.2.6 and iPad. I have a fairly simple business data model to implement on the iPad, but can't seem to find the combination of views and controllers to use.
It's a simple data collection program, but there are too many data fields to fit on a single screen so I've resorted to the Split View approach.
I've got eight rows of table data on the left; RootViewController is the default UITableViewController. I'm trying to write the program such that when any of those table rows are selected, the view on the right (Detail View) gets redrawn and populated with a bunch of different controls (labels, buttons, text fields, etc.) that correspond to the selection on the left. Currently all the Details views are plain UIViewControllers.
For instance, the first "tab" might be titled "Personal", and I need to display fields to collect info such as Name, DOB, Height, Weight, etc. The next one might be "Address" where I need to collect Street, City, ZIP Code, etc. The next one might be "Employer", where I'd like to collect the employer's name, address, phone number, etc. These are mostly static and unchanging, but there are enough fields for about 8 screens worth.
I've got all this laid out in Interface Builder, but when I run it on the iPad simulator and click the very first button on the very first view, the connected delegate isn't called. I've put a breakpoint there and it's not being hit.
Is there some special magic to using simple UIViewControllers on the right side of a UISplitViewController, as far as letting them contain other simple Label, TextBox, and Button controls?
Also is there any special magic in that the framework built the original start-up code with a RootViewController.h/.m and DetailViewController.h/.m pair? Am I correct in thinking that a single detail view represents the entire iPad screen to the right of the Table View, and that to add more "pages" we need to write more files and .XIBs like DetailViewController?
Final question is how to implement the data model for this app. I noticed when the project was first auto-created, there weren't any source files for the model. (Maybe most of the predefined app types don't auto-generate anything for the model, but I understand that's a big part of model-view-controller paradigm.) Anyway these data fields on all the various views will eventually be part of a SQLite record that I'd like to save and load from storage. Where would I begin to wire all that stuff in to all the auto-generated "view centric" code?
If anyone has seen a "how to write real business apps for the iPad" website along the lines of anything like this, please post the link. I'm not trying to display jpegs of various fruits in a single (reused) view on the right side; I'm trying to display several real controls for real-world data collection for a real business app.
Thanks!
Basic things if you haven't already done them:
Have you connected the interface object the user is pressing from it's INSIDE TOUCH UP event to an IBOUTLET that then refreshes the right side?
If you have, and you've mucked about a bit (ie renaming and moving stuff around), try deleting it from the right-click event panel and then reconnecting it to the same IBOUTLET again (you don't have to delete any code).
Also, if you are counting on some event to fire that isn't firing, you should search on that (ie VIEWDIDLOAD NOT FIRING) because that can both occasionally be "broken" (ie no connection to the Files Owner) but more often one might misunderstand when that actually gets fired (ie VIEWDIDLOAD vs VIEWDIDAPPEAR vs VIEWWILLAPPEAR etc.)
For your data model, CoreData is your friend. It provides a modell-oriented approach and easily allows to persist data.
For your navigation needs, I think a tabBarController better suits your needs.
Changing the detail view from the master view can be troublesome, especially when using a NavigationController as detail view. The best way I've found is using the NSNotificationCenter.
When using UISplitview I strongly recommend to support the iPad only when it's a business requirement - there are some changes in iOs 8 that make easier to use.