Ways to get your managedObjectContext to the initial viewController OS X - macos

This is how I'm currently doing it but I'm wondering if this is how Apple suggests. I've read some debates about that.
let appDelegate : AppDelegate = NSApplication.sharedApplication().delegate as AppDelegate
if let moc = appDelegate.managedObjectContext {
// do stuff here
}
So that's just to get it from the AppDelegate to the first viewController. From there I'm guessing that using segues is the way to go to pass around the managedObjectContext?
Using the above code is pretty annoying because I'm typing that into every method in the viewController that needs the MOC. It's even more annoying when I have a function with a return statement and all my code that uses the MOC is inside the body of the if statement since that creates errors stating that there is/may not be a return. Is there a better way to do that, like make it more global?
EDIT:
My ViewController.swift file has this header:
import Cocoa
class ViewController: NSViewController, NSTableViewDelegate, NSTableViewDataSource {
and contains a Table View
My AppDelegate.swift file has:
import Cocoa
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {

Apple sample code stores and creates the Core Data stack in the app delegate, but that doesn't mean it's right. In fact, it's entirely wrong. The app delegate shouldn't own the data store. Apple does it, and most people follow along, because it's convenient. You should really have a custom class which owns and creates the Core Data stack.
This custom class needs to be instantiated somewhere, that could be in the app delegate and then passed to your root view controller, or it could be in the root view controller itself. This custom class could also be a singleton (because you don't want multiple instances of it).
Each view controller should have a public property for the MOC which is set by its creator (part of the segue code). In this way each controller isn't going and getting the MOC, the MOC dependency is being injected. This helps keep the relationships clean and aids in unit testing. You also don't need the let to check you got the MOC back - if it isn't there it's a development issue (and if it couldn't be created you should never have created / pushed the view controller...).

Related

IBOutlet is nil, but it is connected in storyboard, Swift

Using Swift 1.1 and Xcode 6.2.
I have a UIStoryboard containing a singular, custom UIViewController subclass. On it, I have an #IBOutlet connection of type UIView from that controller to a UIView subclass on the storyboard. I also have similar outlets for subviews of that view. See figure A.
But at run time, these properties are nil (Figure B). Even though I have assured I've connected the outlets in Interface Builder.
Thoughts:
Is it possible that because I am using a subclass of a subclass something messes up with the initialization? I am not overriding any initializers
awakeFromNib: is not getting called for some reason
Maybe it doesn't connecting to subviews on subviews
Things I have tried:
Matching #IBOutlet and storyboard item types exactly (instead of UIView)
Deleting property and outlet and re-added them
Figure A*
Figure B
*The obscured code in Figure A is:
#IBOutlet private var annotationOptionsView: UIView!
#IBOutlet private var arrivingLeavingSwitch: UISegmentedControl!
Thank you.
Typically this happens because your view controller hasn't loaded its view hierarchy yet. A view controller only loads its view hierarchy when something sends it the view message. The system does this when it is time to actually put the view hierarchy on the screen, which happens after things like prepareForSegue:sender: and viewWillAppear: have returned.
Since your VC hasn't loaded its view hierarchy yet, your outlets are still nil.
You could force the VC to load its view hierarchy by saying _ = self.view.
Did you instantiate your view controller from a Storyboard or NIB, or did you instantiate it directly via an initializer?
If you instantiated your class directly with the initializer, the outlets won't be connected. Interface Builder creates customized instances of your classes and encodes those instances into NIBs and Storyboards for repeated decoding, it doesn't define the classes themselves. If this was your problem, you just need to change the code where you create your controller to instead use the methods on UIStoryboard, or UINib.
Have you tried running Product > Clean. Solved a very similar problem for me.
The storyboard wasn't recognizing any further UI things I added to it. At run time all the references were nil. So I cleared my derived data folder and then those connections worked again.
This happened for me because I was accidentally instantiating my view controller directly instead of instantiating it through the storyboard. If you instantiate directly via MyViewController() then the outlets won't be connected.
This was happening to me with my custom collection view cell. Turns out I had to replace my registerClassforReuseIdentifier method with registerNib. That fixed it for me.
In my case, it happened because I overriden the loadView method in my ViewController subclass, but forgot to add [super loadView]
-(void)loadView {
// blank
}
When you override the loadView method, the it is your responsibility to init your subviews. Since you override it, the views from interface builder do not get the chance to convert to cocoa objects and thus outlets remain nil.
If you implement loadView in your view controller subclass, then it becomes your responsibility load the UI elements from from storyboard/xib into code.
Or just call
[super loadView];
So that the superclass gets the chance to load storyboard/xib into code.
If you instantiate view controller through programmatically. Then
try creating it like below
let initialVC = self.storyboard?.instantiateViewController(withIdentifier: "InitialVC") as! InitialVC
instead of directly
let initialVC = InitialVC()
This worked for me.
You can call controller.view to force to load the view to initialize the IBOutlets, then you will be able to assign the values.
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
if (segue.identifier == "identifier") {
let controller = segue.destinationViewController as! YourController
let _ = controller.view //force to load the view to initialize the IBOutlets
controller.your_IBOutlet_property = xxx
...
controller.delegate = self
}
}
I encounter this problem recently! Here is my thought.
The problem is not about you storyboard or any link issue. It is about how you initiate your ViewController. Especially when you are using Swift.(There is barely nothing in the editor when you create a class file)
By simply using the init() from super class can not initiate anything you worked with story board. So what you need to do is changing the initialisation of the ViewController. Replace
let XXViewController = XXViewController()
by
let XXViewController = UIStoryboard(name: "Main", bundle: NSBundle.mainBundle()).instantiateViewControllerWithIdentifier("XXViewController") as! XXViewController
This tells the program to go to the storyboard find XXViewController and initiates all IBOutlet in your storyboard.
Hope this help~ GL
For me, this occurred when I accidentally declared my view controller's class as
class XYZViewController: UINavigationController {
}
(ie as a UINavigationController not a UIViewController).
Xcode doesn't pick up on this mistake, the class seems to build fine, and override functions such as viewDidLoad, viewWillAppear, etc. all work correctly. But none of the IBOutlets get connected.
Changing the declaration to
class XYZViewController: UIViewController {
}
fixed it completely.
2019, ONE POSSIBILITY FOR THIS HORRIBLE PROBLEM:
Say you have perhaps a container view that shows some sort of clock. So you have
class Clock: UIViewController
You actually use it in a number of places in the app.
On the main screen, on the details screen, on the edit screen.
You have a complicated snapchat-like modern app.
In fact, Clock may actually be loaded more than once at the same time somewhere on the same screen. (Maybe it's hidden in some cases.)
You start working on one instance of Clock on one of your many storyboards.
On that storyboard you add a label, NewLabel.
Naturally you add the outlet in code. Everything should work. All the other outlets work perfectly.
You have definitely linked the outlet.
But the app crashes with NewLabel as nil.
Xcode clearly tells you "you forgot to connect the outlet".
The reason is this .......... you have "NewLabel" on only one of the storyboard uses of Clock!
The crash is actually from >>> an other place <<<< you are using Clock!!!!
Xcode does not tell you the crash is from another place altogether, not from where you are working!
The crash is actually not from the place you are working - it's from another storyboard, where there is no "NewLabel" item on that storyboard!!!
Frustrating.
For Swift 3.
func configureView() {
let _ = self.view
}
In my case, the app started crashing all of a sudden.
Debugging it revealed that all outlets were still nil at the time of viewDidLoad().
My app still uses nibs (not storyboards) for most view controllers. Everything was in place, all outlets wired properly. I double-checked.
We typically instantiate our view controllers as
let newVC = MYCustomViewController()
...which for some reason seems to work as long as the .xib is named the same as the view controller class (not sure how that works, though. We are not calling init(nibName:bundle:) with nil arguments, or overriding init() to do so on self like it is typically suggested...).
So I tried to explicitly call
let newVC = MYCustomViewController(nibName: "MYCustomViewController", bundle: .main)
...only to be greeted with the runtime exception error:
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Could not load NIB in bundle: 'NSBundle </Users/nicolasmiari/Library/Developer/CoreSimulator/Devices/3DA3CF21-108D-498F-9649-C4FC9E3C1A8D/data/Containers/Bundle/Application/C543DDC1-AE86-4D29-988C-9CCE89E23543/MyApp.app> (loaded)' with name 'MYCustomViewController''
And then, I saw it:
The "Target Membership" checkbox of the .xib file was unchecked.
Must have happened when resolving one of the frequent merge conflicts regarding the Xcode project file.
Apple definitely needs to come up with a project file format that is more SCM-friendly.
You need to load the view hierarchy first in order to instantiate the outlets in the storyboard. For this, you can manually call the loadView or loadViewIfNeeded methods.
100% Working solution for creating ViewControllers from XIB without StoryBoards
Create class CustomViewController : UIViewController
Create view CustomViewControllerView.xib
In CustomViewControllerView.xib in Interface Builder select Placeholders -> File's Owner
In "Attributes inspector" set Class to CustomViewController
In "Connections inspector" connect "view" to top-level view of xib (ensure top-level view's Class is not pointing to CustomViewController)
In "Connections inspector" connect other outlets (if needed/exist)
Create an instance of CustomViewController in parent view controller/App delegate
7.1.
// creating instance
let controller = CustomViewController()
7.2.
// connecting view/xib with controller instance
let bundle = Bundle(for: type(of: controller))
bundle.loadNibNamed("CustomViewControllerView", owner: controller, options: nil)
7.3.
// get/set outlets
controller.labelOutlet.text = "title"
controller.imageOutlet.image = UIImage(named: "image1")
Check your IBOutlet connection if it connected to the File owner or the view.
There could be mistakes.
Other case:
Your outlets won't get set until the view controller's view is actually instantiated, which in your case is probably happening shortly after initWithNibName:bundle:—at which point they'll still be nil. Any setup you do that involves those outlets should be happening in your view controller's -viewDidLoad method.
For me, I had same error on a localized storyboard, an element was added in some locale and not in the other, so I had null reference for that element when switched to the missing element locale, I had to remove (redundant) localization for that storyboard using https://stackoverflow.com/a/42256341/1356559.
For me, this was crashing because containerView was nil.
Here is my code with Crash.
#IBOutlet private var containerView: UIView! // Connected to Storyboard
override open func loadView() {
containerView.addSubview(anotherView)
}
The missing thing was calling the super.loadView(). So adding it solved problem for me.
Fixed Code:
#IBOutlet private var containerView: UIView!
override open func loadView() {
super.loadView()
containerView.addSubview(anotherView)
}
I had a similar issue when I had previously added register(_:forCellReuseIdentifier:) for the custom cell after I had already defined the identifier in the storyboard. Had this code in the viewDidLoad() function. Once I removed it, it worked fine.
Yet another case I just ran into. I changed the name of my class for the UIViewController, but I forgot to change the name of the .xib file where the interface was built.
Once I caught this and made the file names reflect the class name, it was all good!
I hope that helps someone.
Got one more ...
If you have a custom class for a UITableViewCell but forget to specify Custom in the Style of the cell.
Check to see if you have any missing or disconnected outlets.
You can validate if the is view is loaded.
if isViewLoaded && view.window != nil {
//self.annotationOptionsView.
}
select both .h and .m view controller files
remove the reference of those files
re-add the files to your project tree
open the storyboard, eventually re-build the project
Accidently I subclassed my view controller with AVPlayerViewController instead of UIViewController. By replaying it to UIViewController things back normal. This should help.
No build cleaning (normal&full), removing derived data folders and quitting Xcode worked for me.
I had the same problem after copying a class (linked to a xib) to reuse it with another viewcontroller class (linked to a storyboard).
I forgot to remove
override var nibName
and
override var nibBundle
methods.
After removing them, my outlets started to work.
I see you use ViewController!? in ViewController class you must use -viewDidLoad, not -awakeFromNib, -awakeFromNib use for UIView class
If you have two main.storyboards and you are making changes to the wrong one this can happen. This can happen anytime you connect an outlet from an uninstantiated storyboard.

How to reference controllers in Swift (MVC)

I have a single window application. Currently I have one xib file (MainMenu.xib) and I have one .swift file (AppDelegate).
If I want to add controls to the UI and assign specific controllers to some of those UI components to to handle additional functionality in a separate file e.g. a NSTextView with a TextViewController - how would I obtain a reference to TextViewController, from within my AppDelegate?
Most tutorials stop short from this and assume that everybody will want to use #IBOutlets to access a controls' properties from the AppDelegate.
I know you can use segues (prepareForSegue) - but my application does not make use of storyboards, and I would like to understand the MVC implementation within cocoa a little better.
Any object can can have its own controller. The AppDelegate is not a holy grail, its just an object which implements methods in the UIApplicationDelegate protocol. Its not a universal switchboard for everything you might wish to connect.
Some of the tutorials do a starting Cocoa dev great disservice by using the AppDelegate as a quick and nasty place to put things. For a storyboard app this all that needs to be contained in the class which conforms to NSApplicationDelegate
import Cocoa
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
}
i.e nothing.
A nominal pattern is one screen has one controller but even that isn't true when you use embedding patterns to control subviews within one window. However consider the simple Cocoa Application project template
The ViewController class is a subclass of NSViewController and is where you might be placing the IBOutlet to your NSTextView and some of the logic to do with interacting with it in that context.
import Cocoa
class ViewController: NSViewController,NSTextDelegate {
#IBOutlet var textView: NSTextView!
override func viewDidLoad() {
super.viewDidLoad()
configureTextController()
}
var textController:TextFieldInteractionManager!
func configureTextController() {
//textcontroller can be local var , alternatively inject it into this VC
textController = TextFieldInteractionManager(textView)
}
}
If you have specific interactions that you want to do across the entire project you might want to place these in another class say TextFieldInteractionManager and make that the delegate of the text field. You would pass the text field to the manager class when you construct the view controller.
The hypothetical TextFieldInteractionManager might be created locally or injected in during the setup of the ViewController instance i.e prepareForSegue(segue: NSStoryboardSegue, sender sender: AnyObject?). The possibilities are varied and will depend on the scope of your app and how you wish to manage your references.
My commonly used pattern is to have a Root view controller which passes references down the to the next controller in the chain. YMMV
Dependancy Injection is a good pattern to know about.
Massive View Controller is a good anti-pattern to know about and avoid.
This is what I meant when i said go and get some domain knowledge. Its an expanding tree of stuff to learn about and why your question as its stands is not a good fit for SO.

Swift class to interact with Interface Builder via IBOutlet

Good evening all,
I'm slowly working through my first OS X app. I have been having a hard time getting my Swift class to interact with an NSPopUpButton. Just to make sure I was doing it right, I created a new project and successfully erased all entries and entered text into the NSPopUpButton via AppDelegate. However, as soon as I try to move the same functionality to my own class, I can't even get the IBOutlet connection across to the new class.
Is a particular subclass type required of a new class to work properly with interface builder?
Here is a screenshot of the class I have created, as well as AppDelegate where I am trying to call the function belonging to this class.
Finally, here is the IB element in question, should I be able to select my own class under the 'Custom Class' inspector?
I am an iOS developer, but I would imagine the same principles would apply to your situation.
A ViewController class and an interface created in interface builder are two seperate things. While it may appear that they are connected via an iboutlet, they are actually independent and one can be instantiated without the other.
Currently, you are only creating an instance of your ViewController class in your App Delegate - and that's all. The system has no idea that your xib even exists! The outlets will only be connected once your app connects your xib to your ViewController class.
How do we do this? It's actually quite simple. Instead of instantiating our view controller like this:
let viewcontroller = ViewController()
We would connect our view controller to our xib in the instantiation:
let viewcontroller = ViewController(nibName: "MainWindow", bundle: NSBundle().mainBundle)
The nibName is telling the system the file name of your xib, and the NSBundle().mainBundle is telling the system where to look for the xib.
This will all only work if your xib has been assigned a custom class, like you mentioned. In your xib in interface builder, select the entire view controller. Then, in the custom class inspector type in the name of your ViewController class (in your case: ViewController - it should autocomplete). This will make sure your outlets are connected.
And you should be set up!! Let me know if you have any more problems come up.
Good luck!
EDIT:
This replaces the first part of my answer, however the part about hooking things up in Storyboard remains true. Upon reconsidering, I've believe I've realized that we are only creating the view controller, and not adding it to our view. Despite this, I believe we can take a short cut solution by adding one method to your view controller subclass (the one we set in the Storyboard). Start typing in viewDidLoad, and it should autocomplete. Type in super.viewDidLoad() at the beginning of the method. After that, type self.listUpdate(). This should work if the classes are hooked up correctly in Storyboard. This means you can delete the variables you created in the App Delegate.
Reference: You might also find Apple's documentation on creating a view controller handy (it's in Objective C online, but can be easily converted to Swift - it's the concept that counts): NSViewController Class Reference

Swift, storyboards and core data...a missing persistentstorecoordinator?

I am working on a new Cocoa project using Swift, Core Data and storyboards, and have come across a problem that makes no sense to me. After some fairly extensive hunting around, including on this site, I have come to the conclusion that I must be missing something obvious, but cannot figure out what. Here is what I have done so far:
1.Create a new project, Cocoa Application, using Swift, Storyboards, and Core Data.
2. Create an entity in the .xcdatamodeld. Let’s call it Dataset.
3. Create a subclass of NSSplitViewController (for what i want to do in the rest of the program).
4. Set the window content of the main window to and instance of myVC. I checked, and it loads up and displays fine.
5. In the viewController.swift, get the managedObjectContext like so:
#IBOutlet var moc:NSManagedObjectContext!
override func viewDidLoad() {
super.viewDidLoad()
// Do view setup here.
let appDelegate = NSApplication.sharedApplication().delegate as AppDelegate
moc = appDelegate.managedObjectContext
println("mainsplitviewcontroller moc:")
println(moc)
println("mainsplitviewcontroller psc:")
println(moc.persistentStoreCoordinator)
NSLog("Main split view loaded")
}
(yes, I have about dependency injection, but I want to solve this problem first).
In IB, put a managedObjectContext object in the View Controller instance.
In IB, connect the myVC outlet for the variable moc to the managedObjectContext.
In IB, create an array controller. Set it To Entity. Entity Name is Dataset. Turn on Prepares Content.
Either as an outlet or a binding, connect the array controller to the MOC. Using outlet, just dragging from managed object context in it's right-click popup to the icon for the MOC created in 6 above. For bindings, the old fashioned way, going to the bindings tab, and under Parameters, Bind to: (view controller), Model Key Path: moc. (moc is from 5 above)
Then, I build and run. and I get the error: "Cannot perform operation since managed object context has no persistent store coordinator."
This happens whichever way I try to do 9, above.
Now, the thing is, from my println statements, the objects referred to in both the app delegate and the viewcontroller are the same, both for the managed object context and for the persistent store controller, as below:
appdelegate moc:
appdelegate psc:
mainsplitviewcontroller moc:
mainsplitviewcontroller psc:
I wish I could show images, but I am new here and so cannot do that. Am I doing something clearly wrong? I thought I understood the process: make sure the VC can access the MOC, then put the MOC object into the VC's window in IB, make it an outlet, and connect it to an array controller. Why would the swift file for the view controller seem to show that the PSC is the same as the app delegate, but in IB, the array controller think the MOC has no PSC at all?
Thanks for reading!
I don't know if this is going to help, but I'm not surprised that your project shows that error. You have two managed object contexts - one created by your app delegate, and one created by the storyboard. Your interface code is connecting to that second MOC, which is not connected to your persistent store.

NSViewController in Swift - Can't unwrap Optional.None

On my main window I have a CustomView. Depending on what the user selects, this view will be changed. To have a clear source code, I created for each new view a NSViewController with a new xib file. Then I connected my IBOutlets to the new ViewControllers. This works perfect. But if I add an IBAction, Xcode says, that it cannot connect to the action. So I googled and I found out, that I should not connect all the IBOutlets and IBActions to the File's Owner of the ViewController. Instead I added a new NSObject to the new xib file and set the Class to my ViewController class. If I now want to access the IBOutlets, I'm getting the error fatal error: Can't unwrap Optional.None at the line, where I want to access the IBOutlet.
Any ideas? What's the right way to work with NSViewControllers. Do I have to add an NSObject? How many instances of my ViewController are then created?
"Can't unwrap Optional.None" is what happens when you have an implicitly unwrapped optional (one defined with an !) that is nil but you try to use it anyway. For example:
var aNumber : Int! = nil
aNumber + 5 //fatal error: Can't unwrap Optional.None
I believe outlets are declared as implicitly unwrapped by default in Swift, so you're probably getting this error by trying to do something with an outlet that isn't connected in interface builder (or your storyboard or whatever).
As to the rest, it's really hard to understand what you're doing in your app. I understand you have an NSViewController for each view, but what outlets are you connecting to them? Where are you adding the IBAction? Which file's owner do you mean, the owner of the window's nib? The owner of your NSViewControllers?
It sounds to me like you're making this way too complicated, and adding more NSObjects to the mix is almost certainly the wrong way to go. See if you can simplify things or upload your project someplace so we can take a look and better diagnose your issue.
I now have the solution. Here is the way to use NSViewController: (working in Beta 3)
Create a new app and add a CustomView to the main window. Connect this custom view to your AppDelegate.
Add this var to your AppDelegate: var mainViewController: NSViewController?
Create some new Swift Cocoa Classes as Subclasses of NSViewController and check "Also create XIB file for user interface" as much as you need.
For each ViewController add a new variable to AppDelegate like this: var firstVC = firstViewController(nibName: "firstViewController", bundle: nil)
Now edit your new xib files and connect everything to the correspondig swift file.
For changing the view and the view controller, choose this code:
mainViewController?.view.removeFromSuperview()
mainViewController = firstVC //or the vc you need
customView.addSubview(mainViewController?.view)
mainViewController?.view.setFrameSize(customView.bounds.size)
That's all. All IBActions and IBOutlets now work without any error. If you need a method for doing some things on loading the view, take override func loadView() { super.loadView() ...

Resources