NSPersistentDocument, Swift, macOS, and storyboards — how to get the managedObjectContext? - swift

It has been a while since I’ve tackled CoreData and macOS, back in the days of xib and nibs. With a xib, there’s the “File’s Owner” that can give you access to your document and the managedObjectContext. Easy.
With NSPersistentDocument and my storyboard, I’m having a bit of a chicken and egg problem. In my Document class, subclassed from NSPersistentDocument, I have the following:
override func makeWindowControllers() {
// Returns the Storyboard that contains your Document window.
let storyboard = NSStoryboard(name: "Main", bundle: nil)
let windowController = storyboard.instantiateControllerWithIdentifier("Document Window Controller") as! NSWindowController // <- when I need the moc
self.addWindowController(windowController)
windowController.contentViewController!.representedObject = self // <- when I set the representedObject
}
This seems to be what many folks, including Apple, are suggesting.
My problem is this: In the MainViewController, I want to have an Object Controller and it needs to be bound to the managedObjectContext but when it needs to have the managedObjectContext, I haven’t yet set the representedObject to self. So an exception is thrown. Setting the representedObject at the end of the makeWindowControllers method is too late but I don’t see anyway to get it in earlier.

Okay. So. I don’t know what was happening last night but there was no way I could get this working.
This morning, I re-read the documentation on representedObject:
The representedObject property is key-value coding and key-value observing compliant. When you use the represented object as the file's owner of a nib file, you can bind controls to the file's owner using key paths that start with the string representedObject.
The docs are clearly telling me that the magic is in representedObject. So I made sure that my makeWindowControllers method was as above and I made sure that my Object Controller in my storyboard was as the docs said it should be.
I wasn’t surprised that the path has a little (!) because representedObject is just an AnyObject.
I then dutifully launched the app, fully expecting it not to work.
But it worked. No idea why yesterday it didn’t but not much I can do about the hours already lost.
Follow up: As an experiment, I tried one of the twists from yesterday. To get rid of the (!) and to have a reference handy to the moc, I added this method to the MainViewController:
var moc:NSManagedObjectContext? {
if let doc = self.representedObject as? Document {
return doc.managedObjectContext
}
return nil
}
And then I used “self.moc” as the Model Key Path for my Object Controller. This didn’t work and the familiar exception was thrown. Restore the Model Key Path to “self.representedObject.managedObjectContext” and all works nicely. … like magic.

Related

Swift: Referencing variables in one class from another function and/or class

trying a few things out in Swift. I’m trying to get some things that seem muddled to me straightened out - mostly to do with how I deal with variables and referencing them in a project.
What I am trying to do is keep a variable (based on a struct) defined in ViewController accessed and updated from various other functions within an application.
So, a brief outline of the code I have is here. I actually wrote a smaller app to test my ideas out before applying them to something more complex.
I started in XCode with a Swift document based application for Mac OSX.
In ViewController.swift I have:
import Cocoa
class ViewController: NSViewController {
var myText = "Hello, some text"
#IBOutlet weak var textView1: NSTextField!
#IBAction func Button1(_ sender: Any) {
myText = "This is button 1 clicked"
myText = setText( thisText: &myText )
textView1.stringValue = myText
}
#IBAction func Button2(_ sender: Any) {
print("Button 2")
myText = "This is button 2 clicked"
textView1.stringValue = myText
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
textView1.stringValue = myText
}
override func viewDidAppear() {
let document = self.view.window?.windowController?.document as! Document
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
}
In SetText.swift, I have this:
import Foundation
func setText( thisText: inout String) -> String {
thisText = "Function"
return thisText
}
I liked the idea of sending variables to the Set Text function and then returning it, but thinking about it more makes me think that actually this could end up like a proverbial bowl of spaghetti with functions calling functions and who know what else. So I was thinking that something like this would probably make more sense:
import Foundation
func setText( thisText: inout String) {
let vc = ViewController()
// Read the variable from View Controller
var myTextHere = vc.myText
myTextHere = myTextHere + " More text"
// Set the variable in ViewController here
vc.myText = myTextHere
}
From my reading around on this subject, if I call ViewController(), it will create anew instance of the view (is that right, or am I misreading?). That’s already happened, so what I need is to reference the ViewController that called the function setText, or rather that owns that particular instance of code. As I’m thinking about a document based application, I’d obviously want to keep all instances of myText with each document’s ViewController.
My aim is to create something a bit more complex, using a variable based on a Struct to keep everything together. So:
myCard.image // holds a bitmap image
myCard.size // holds the size of the image
And so on. being able to access it in the form of ViewController().myCard to both read and write to is what I am thinking I need to do.
What I don’t want to do is use global variables.
Thanks.
I'm having a hard time seeing much correlation between the title of your question and the body of your question. In fact, I'm not even entirely sure that there's a question being asked. That said, I'll try to address the questions you appear to be asking (re-written how I think they're intended):
When you initialize a new view controller, does it create a new view?
Yes. There is a view property for every view controller, and it's not a shared component or a singleton or anything else like that. There is one main view for every view controller. It's almost certainly composed of dozens of other subviews, but there is one view that is the view for every view controller.
Is there a way to get metadata about the state of a view controller from outside, preferably in the form of a struct?
Absolutely. First, you'd need to define that Card struct. I'd recommend doing it in the same file as the view controller itself. You can define it outside of the view controller, or if you want stricter coupling and namespacing, you can do it inside the view controller. Just be aware that doing that latter would mean that the type name, when referenced outside the view controller, would be ViewController.Card rather than just Card.
Then you'd want to create either a computed property (var card: Card) or a method (func card() -> Card) on your view controller which builds and returns one of those based on the state. It sounds like you're already leaning toward the property approach.
Note: I would absolutely advise against having one that is a normal get/set property because then you'd have to constantly update it and modify it. The best thing to do is have a computed property which builds it on the fly. So when the property is called, it reaches into all your components to get the info you want (like image sizes, strings, etc) and then packages up and hands off the Card metadata struct. Doing it on-demand like this eliminates unnecessary complexity and consolidates the metadata logic into one place.
Some dangerous things in your code example:
I can't think of a good reason to implement viewDidAppear() but not call super.viewDidAppear(). Unless you have an enormously compelling reason to leave that out (I honestly can't think of a single one), do not do so.
I don't see any good reason for your first implementation of your setText(thisText:) method. The way you use it in your Button1(_ sender: Any) IBAction functionally does absolutely nothing. That method in general is screwy for several reasons: it's got an upper-case method name, sets the textView text by trying to assign to stringValue for some super strange reason, and does in three lines what could be done in one:
textView1.text = "This is button 1 clicked"
The second implementation of setText(thisText:) makes even less sense than the previous. The two biggest problems being 1) that you don't even use the thisText argument passed in, and 2) your method, which is called 'set text' is creating a whole new view controller every single time it gets called? That's a huge violation of "doing what it says on the tin." Methods should have a single responsibility and shouldn't do anything beyond that responsibility. I'd never in a million years look at a method called setText and think "I'll bet this initializes a view controller." Never.
I see this question has already been downvoted a bit (not by me), and I'd like to take a moment to coach you in using Stack Overflow: Ask clear, concise, specific questions about clear, concise, specific topics. As I said at the top of my answer, there doesn't appear to be a question anywhere in your post. I had to kind of make some up that I inferred from what you wrote.
Remember: coding isn't just wiggling your fingers while you think about an app. If you're doing the hard work of good engineering, you'll likely spend a ratio of about 10:1 (or more!) of staring at your screen to actually typing any code. Every time you write a line of code, you should be asking yourself, "Why am I writing this line of code? Is this necessary? Am I reinventing the wheel?)
Good luck!

How do set a relationship between managed objects within an MVVM pattern?

I'm building an app and trying to use the MVVM pattern. While there's a plethora of information about how to wire up things up for data to flow form the model to the view model to the controller to the view, I'm having a very hard time learning how to do other things while sticking to MVVM principles. One thing I'm really struggling with is setting a relationships between two managed objects after segueing to different view controllers. Let me explain...
When my app starts, it presents the first view controller which is backed by a view model which talks to a newly created NSManagedObject of type LiftEvent. From here, the user can segue to a Settings view controller which also has a view model, and from there they can segue to a table view, which also has a view model, and make a selection that needs to change an NSManaged var property of the model object. This property is a relationship to another NSManagedObject. Here's the flow:
I'm passing the managedObjectContext from the first view controller to the Settings view controller:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let nav = segue.destinationViewController as? UINavigationController {
let vc = nav.topViewController as! SettingsViewController
vc.dismissalDelegate = self
let moc = viewModel.model.context
vc.moc = moc
}
and then pass it again to the last view controller where the user makes a selection and I want to set the relationship. It works, but I don't like it because I now have to import CoreData in the view controllers I'm passing it to.
The view controller on the far right is a UITableViewController. When the user selects one of the rows, I want to use that selection to grab the corresponding managed object and set it as one of the properties on the object that was created when the app launched. Here's the didSelectRowAtIndexPath method:
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
tableView.deselectRowAtIndexPath(indexPath, animated: true)
let defaultFormula = NSUserDefaults.formula()
guard indexPath.row != defaultFormula.rawValue else { return }
if let newDefaultFormula = FormulaName(rawValue: indexPath.row) {
NSUserDefaults.setDefaultFormulaName(newDefaultFormula)
}
if let selectedFormula = formulasArray[indexPath.row] {
// now what?
}
}
FormulaName is a enum of the possible names. The data source is formulasArray, an array of Formula managed objects. I initialize the array in viewDidLoad:
override func viewDidLoad() {
super.viewDidLoad()
formulasArray = dataManager.fetchSelectableFormulas(moc)
}
I see a few possible solutions in my head but I have no idea which of them, if any, are good choices:
pass the Formula object I picked out of the array back to the first view controller in unwind segues and set the relationship there
somehow get a hold of the LiftEvent from here, set the relationship, then tell the view model of the first view controller that the object has changed.
pass a reference to the LiftEvent object around instead of passing just the managedObjectContext property
I've spent countless hours trying to find examples, lessons, git repositories, SO threads, etc to figure this out that I don't know where to go from here. I'm not asking anyone to write the code for me, just steer me in a good direction and I'd be forever grateful.
I too have had issues finding concrete examples of MVVM in iOS, so i've kind of developed my own approach.
I have a couple recommendations (this is opinion to be clear).
Keep the NSManagedObject context in a CD access singleton for access on the main thread is really helpful. Assuming you're not using multiple databases or doing a bunch of things in the background, (which you can handle as well with the accessor if necessary) you will have 1 context with no context passing.
When using Segues, dependency injection is awesome. Rather than giving your new VC the minimum information it needs to find the data and set things up, simply set it's ViewModel in the Segue and build the VC to work only off of that. This is what makes ViewModels work so well. your View/ViewController doesn't need to to know anything about your model.
When dealing with unwinds, my approach is to simply make a "selectedItem" property in the desired VC and leave the responsibility of what to do to the VC to which it is unwinding. If necessary it can grab that value when the unwind occurs. This allows your choosing VC to operate the same way regardless of the behavior around it.

How to make a Cocoa binding to a property of optional type in Swift 3?

I am exploring the use of Swift for a Mac Cocoa application - using Xcode 8.2.1, and it seems I hit a roadblock regarding Cocoa bindings.
In this toy Core Data document-based project, I added an NSArrayController in the StoryBoard. I need to bind it to a NSManagedObjectContext so that my user interface works by itself. I followed the solution outlined in Technical Q&A QA1871 (https://developer.apple.com/library/content/qa/qa1871/_index.html).
So I want to add a property of type ManagedObjectContext to my ViewController class.
I naturally declared it as an optional:
var moc: ManagedObjectContext?
But when I enter the property name in the bindings inspector of InterfaceBuilder, it complains: there is a red exclamation mark, and hovering over it pops up this error message:
“The Managed Object Context binding expects to be bound to an object of type NSObject, but moc is of type ManagedObjectContext?”
And it fails at run time too.
changing the type to ManagedObjectContext! doesn’t help: IB complains in exactly the same way.
changing the type to a non optional ManagedObjectContext silences the IB error, but now my ViewController class doesn’t compile anymore. The error I get is:
class ViewController has no initialiser
I completely understand this error message. And I can add an initialiser. But I get this new error message:
property ‘self.moc’ not initialised at super.init call.
I understand that one too, but what can I do? At initialiser-time, the managedObjectContext is not yet known. In Objective-C I would set it to nil, which I cannot do since the property is not an optional any more.
Do I really need to allocate a dummy sentinel ManagedObjectContext, just to make the compiler happy?
This would be ugly as hell, far worse than the nil value we use in Objective-C. Swift in that case, would not be safer, but less safe than Obj-C.
I find this idea repulsive. Or did I miss something?
Or is Swift fundamentally incompatible with this Apple-recommended Cocoa bindings pattern? That would be a pity too.
Note that an equivalent Objective-C implementation works just fine. In Objective-C, the property is declared as:
#property (nonatomic, strong) NSManagedObjectContext * _Nullable moc;
Edit: a zip archive containing two toy sample projects, one in Objective-C, the other in Swift 3, can be downloaded from http://dl.free.fr/pWkoKQLOc
Edit: here is where I set the viewController's managed object context:
class Document: NSPersistentDocument {
override func makeWindowControllers() {
let myMoc = self.managedObjectContext
// Returns the Storyboard that contains your Document window.
let storyboard = NSStoryboard(name: "Main", bundle: nil)
let windowController = storyboard.instantiateController(withIdentifier: "Document Window Controller") as! NSWindowController
let myViewController = windowController.contentViewController as! CarViewController
myViewController.moc = myMoc
self.addWindowController(windowController)
}
}
myViewController is created by storyboard, and I can only set its moc after it has been initialised. This in turns requires that the property be an optional or to have a dummy actual value to use as a sentinel value in replacement for nil

IBOutlets in NSCollectionViewItem are nil

I am having a problem where it seems I am able to add items to a collection view, and have the prototype cells I created from a subclass of NSCollectionViewItem, but when I try to change the labels in order to display the data from the is get errors because they are nil, even though I connected them from the interface builder.
I also checked out the representedObject property by printing by attacked a mouseup function to the cell, and it was working fine. It only falls apart when I attempt to assign to the stringValue of the apparently nonexistent outlets.
Down below you can see my storyboard and the controller for the item views.
The Label objects are represented down below as the two #IBOutlets.
representedObject is being aliased as var morpheme : Morpheme?
Here in the main window controller I am testing out just adding the Morpheme objects to the collection with test values that I'm wanting to populate the Labels with.
The display shows the cells in a list, but when I click them it crashes due to a nil value from the apparently uninstantiated outlets. If you look in the console Morpheme 3 was printed successfully, so the data model made it in. Is there anything more I need to do? There is a lot of stuff about using the binding menu floating around, but I haven't played around much with it.
That is very interesting problem which already gained a lot of traction. The problem seems to be a bug in Cocoa so when items created from prototype, their outlets dont get hooked.
Look what happens when prototype is created:
Then when actual instance is created the outlets are nil:
So my recommendation would be to use one of 2 solutions:
Use nibs and NSCollectionViewDataSource to create collection view items based on Views. Look for the last answer here:
Cocoa - Where is the link between a NSCollectionView and a NSCollectionViewItem? Xcode 6 Bug?
Or just see examples with keyword: "makeItemWithIdentifier"
Dont use outlets and use data binding as I did in my example. I bound my text edit to represented object:
And here is my code in controller:
class ViewController: NSViewController {
dynamic var collectionViewData = [String]()
#IBOutlet weak var collectionView: NSCollectionView!
override func viewDidLoad() {
super.viewDidLoad()
guard let proto = self.storyboard?.instantiateControllerWithIdentifier("collectionViewItem") as? CustomCollectionViewItem else { return }
collectionView.itemPrototype = proto
collectionViewData.append("One")
collectionViewData.append("Two")
}
My final picture is (you can see I bound label to the actaul string in the array):

Deinit never called

I'm creating a ViewController object an pushing it to a navigation controller.
When the object is being popped from the stack - it is not being release and Deinit is not being called. What can be the reason for that?
Here's the code that pushes:
self.navigationController?.pushViewController(CustomViewController(), animated: true)
And here's the code that pops:
self.navigationController?.popViewControllerAnimated(true)
I had similar problem. I added empty deinit method to my class and added breakpoint:
deinit {
}
As result it's never called.
As soon as I add some code to the body it started working as expected:
deinit {
print("deinit called")
}
So be sure that your deinit method isn't empty.
PS. I am using Swift 2, Xcode 7.1
Do any of your classes, or properties they contain make a reference to the view controller you've popped?
If your UIViewController has created an instance of an object, which in turn makes a 'strong' reference to that view controller (e.g. a reference that's not explicitly declared 'weak' or 'unowned'), and your view controller keeps a strong reference to that object as well, neither will be deallocated. That's called a strong reference cycle, documented here (a must read for serious Swift developers):
The Swift Programming Language (Swift 3.0.1): Automatic Reference Counting
Closures are a more insidious case where you can get into trouble.
One thing you might try as an experiment is pushing the controller and popping it before you do anything in viewDidLoad or in the initialization, and see if the deinit method is being called. If it is, then you should be able to incrementally discover what you're doing that's leading to the symptom you're seeing.
Another thing that can thwart diagnosis (as other answers have pointed out), which I learned the hard way, is that the debugger breakpoint won't be taken for deinit() if the deinit method contains no executable statements, because the OS or compiler optimizes the deinit invocation away if it's empty, so at least put a print statement there if you want to verify deinit() is getting called.
I was using this tutorial Custom TabBar with a custom seque on the given page. The memory problem was due a subview which had a strong reference the the parent viewcontroller.
class WBMNewsFeedV: UIView
{
var parentVC:WBMHomeTabVC!
}
WBMNewsFeedV - subclass
parentVC:WBMHomeTabVC - parent class ViewController
I changed it to :
class WBMNewsFeedV: UIView
{
weak var parentVC:WBMHomeTabVC!
}
So, the strong reference was nested inside a subviews and not visible at first. Hope this helps anyone. After the change deinit was always called in
WBMHomeTabVC
I had the same issue when the NotificationCenter was holding a strong reference to the presented view controlled and thus it was never getting released. So I had to add [weak self] to the block like this:
(in the viewDidLoad)
NotificationCenter.default.addObserver(forName: .showFoo, object: nil,
queue: OperationQueue.main) { [weak self] (notification) in
if let foo = notification.userInfo?["foo"] as? Foo {
self?.afooButton!.setImage(UIImage(named: "foo"), for: .normal)
}
}
add some line code of in deinit. If You put breakpoint at Empty deinit ,compiler will not stop you there
put this:
deinit {
print("any thing")
}
It will work ;)
I had a timer in my view controller, running every minute to update a label. I put a call in deinit to invalidate the timer, which I'm pretty sure is what I've always done in Objective-C (in dealloc) and it's worked. But it seems a little different in Swift so I moved the time creation/invalidation code to viewWillAppear/viewWillDisappear (which makes more sense really) and everything seems fine now!
I just ran into a similar issue. My problem appears to have been caused by a strong reference to a control that had delegate assignment not specified as 'weak' with a class type protocol.
I had same issue what I found is I didn't make weak reference for my other class delegate
protocol SomeClassDelegate : AnyObject {
func someClassDelegateMethod(<param>)
}
class SomeClass: NSObject {
// Make delegate weak reference
weak var delegate:InfractionDataManagerDelegate? = nil
< some code >
}
now deinit is being called on my implementation class.
Just to add an edge case which I found very difficult to detect:
If you allocate any UnsafeMutablePointers and provide self (the UIViewController) as pointee inside of your view controller, make sure to call pointer.deinitialize(count:), not just pointer.deallocate().
Otherwise, the reference to self will remain and the UIViewController will not deinitialize.
I faced the same Problem. In my case a never ending cycle of UIView.animateWithDuration... holds the ViewController in Memory and blocks the call of deinit. If you use something like this you have to stop it first bevor you remove the ViewController. Hope that helps.
I had a similar issue: I had some UIViews as arranged subviews of a *stackview" contained in a scrollview in a detail view controller. It worked when I removed the UIViews from the stackview on back button tap (willMove(toParent parent: UIViewController?)). Another thing to check is when in your view controller you have something like: let session = URLSession(configuration: .default, delegate: self, delegateQueue: OperationQueue()) (that self could in some cases prevent a detail view controller to be deallocated when you click Back and move to the master)
First of all make sure to define deinit
deinit {
print("OverlayView deinit")
}
Use Xcode's object graph to check number of instances being created and if they are not getting deallocated then they will keep growing.
I was creating property of another ViewController on top of the file so i move it and place it in the scope it was being used that solved my problem. And its deinit started calling.
Moreover i was using a uiview property to show a overlay that need to be accessed from my viewcontroller at some places i make it optional and set it nil when after calling removefromsuperview on it.
var overlay: OverlayView?
overlay = nil
If still dealloc not calling then there could be a retain cycle issue as described above in that case you will have to check another viewcontroller(B) too if its calling back this controller(A) then set one of them as a weak variable.
the most helping material that I could find in this problem is link
But for me the problem was typealias