NSView not being deallocated - swift

I've got a weird situation here that's almost certainly because I'm new to macOS development and I am missing some core knowledge.
I have a modal sheet that I'm displaying programmatically. (I'm not using a storyboard segue because it needs to be the result of a validation and so far I haven't seen a way to launch a segue programmatically - that's a sub-question here if anyone has advice)
Here's how I'm doing it:
searchVC = NSStoryboard(name: "Main", bundle: nil).instantiateController(withIdentifier: "SearchSceneIdentifier") as? SearchViewController
if searchVC != nil {
searchVC!.searchTerm = searchTextField.stringValue
self.presentAsSheet(searchVC!)
}
This presents the sheet nicely and lets me interact with it. In it, I'm using a class which has a delegate in order to return asynchronous search queries.
Where this gets weird is that when I call
self.view.window!.close()
from inside the view controller, I don't think the view controller is getting deallocated. This seems to be because the delegate is still connected to it, even though the object that has this delegate is within the scope of the view controller itself. This delegate appears to be holding the view controller in memory.
I've gotten around this by doing this before closing the window:
search.delegate = nil
But this is not a good solution for other view controllers that have the same problem because they are inside windows and I don't want to have to catch the window closing then send some kind of notification to each in order to nil-ify their delegates.
Another approach that seems wrong as well is that I keep a reference to these windows in the application delegate and nil-ify it from there.
All of these seem like nasty solutions to the deallocation problem and my hope is that there is a cleaner way of doing this. In Objective-C, reference counts were always a problem but there were patterns to handle them cleanly.
Any advice appreciated.

I updated all my delegates to weak var and that has solved all of my problems with deallocation.

Related

PushViewController shows Black Screen

i have a problem with my App.
I´m not an experienced programmer, so maybe it´s just a simple solution. I thought this problem exists because i´m just trying things and play with my app so i made a new App and there´s the same problem.
When i push to a ViewController with navigationController?.pushViewController(PinkViewController(), animated: true).
I´m getting just a black screen. If i have a code like
Label.text = "String in viewdidload of the PinkViewController(). I get the following error at this line of code:
fatal error: Unexpectedly found nil while implicitly unwrapping an
Optional
I searched the web and i didn´t find any solutions for this problem. I hope you can help me.
The reason why it crashes is that your label is probably defined as an #IBOutlet that is connected to a UILabel in your storyboard's PinkViewController. However, when you instantiate PinkViewController with an empty constructor, you're not "using the storyboard-way" and your label outlet (which is non-optional, because it's likely to have an exclamation mark there) could not have been connected to the storyboard instance of your view controller.
So what you should do is one of these options:
If you defined PinkViewController in the storyboard, you'd have to instantiate it in a nib kind of way, for example:
In your Storyboard, select the view controller and then on the right side in the Identity Inspector, provide a Storyboard ID. In code you can then call this to instantiate your view controller and then push it:
let pinkViewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "yourIdentifier")
Even better, you create a Segue in the storyboard by control-dragging from the current VC to PinkViewController. There's lots of tutorials for creating Segues online. Don't forget to provide an Identifier for the Segue in the Storyboard, if you want to trigger it programmatically. This can be done by calling
self.performSegue(withIdentifier: "yourIdentifier", sender: nil)
If you want to trigger that navigation upon a button click, you can even drag the Segue from the button to PinkViewController, that way you wouldn't even need to call it in code.
If you defined PinkViewController programmatically only (by creating a class named like that which conforms to UIViewController), you might wanna instantiate it with PinkViewController(nibName: nil, bundle: nil) (notice the arguments instead of an empty constructor) and then push it with your provided code.
Hope that helps, if not, please provide further code / project insight.

Programmatically switching to a Split View Controller in Swift

Here's what I'd like to do: use a SplitViewController but not have it be the first thing that shows up. I want an initialization view (which does some network stuff and then completes) which then punches over to the split view.
I've got this working. BUT not sure if what I'm doing makes sense and it has a weird behavior.
Here's what I think I know: to use a SplitViewController, it must be the rootViewController. Also, working with separate storyboards seemed the only way to get it to work. (I've tried having them all in one storyboard but couldn't get it to work).
My first storyboard's controller does it's init and then, to switch to the "SplitViewBoard" storyboard and launch the split view, does the following:
let mainStrbd = UIStoryboard(name: "SplitViewBoard", bundle: nil)
let splitController = mainStrbd.instantiateInitialViewController() as UIViewController
if splitController is UISplitViewController {
var mySplitController: UISplitViewController = splitController as UISplitViewController
mySplitController.delegate = appDel
appDel.window?.rootViewController = splitController
} else {
debugPrint("badness")
}
Now, this all works fine. With one exception. After setting the rootViewController to the splitController, there's an 8-10 second delay before the split view shows up on the screen! I'm imagining there's some way of telling the delegate (or whatever) to "refresh" or of just being more explicit but haven't found it yet. Is this way reasonable? Or it there a much better way?

iOS5 - manual Storyboard view selection in code

I am new to iOS and using storyboards for the first time. When my app starts it checks back with the a server app I have written to see if the saved credentials are authenticated and I then in my AppDelegate class I then attempt to show the appropriate scene in the app's storyboard - MainMenu if authenticated or a Login Screen if not authenticated.
I have tried using instantiateViewControllerWithIdentifier on the storyboard and also the performSegueWithIdentifier on the initial NavigationController which is set to be the "Initial View Controller" to display the appropriate view..
However with both methods only the blank navigation bar shows and I am unsure where to go from here.
If there was some example code on how others manually manipulate storyboard scenes and viewcontrollers that would be great. Am I maybe putting the code in the wrong place (ie should it go into the first View Controller) or should that not matter? No exceptions are raised and I seem to have access to instantiated objects as required.
I am thinking I need to understand the operation of the app delegate's window more, or maybe should I focus on manually loading the storyboard by removing it's reference from the InfoPlist settings?
Any thoughts would be greatly appreciated.
From my (admittedly haphazard) understanding of storyboards (at the moment), you should have two named segues going from a first viewcontroller, and then you can simply trigger one or the other as need be (I presume there's some sort of "loading/authenticating" screen, however brief?)
if (success) {
[self performSegueWithIdentifier: #"MainMenuSegue" sender: self];
} else {
[self performSegueWithIdentifier: #"LoginSegue" sender: self];
}
To debug, I'd set up buttons on the initial viewcontroller just to be sure the segue linkings/etc are proper.
You really shouldn't need to instantiateViewControllerWithIdentifier unless you're working around segue/storyboard limitations. I think.
I've put the performSegueWithIdentifier in my app's first viewcontroller's viewDidAppear (not the best idea, I think; but that's sort of the soonest it should happen? and I would hedge towards saying it should be triggered somewhere in the viewcontroller stack, not from the appdelegate, but I haven't tested that).

How to share a ManagedObjectContext when using UITabBarController with inner UINavigationControllers

I have an architectural question. My App uses a TabBarController right in the application window. The ApplicationDelegate creates the managedObjectContext, although it actually doesn't need it.
Each ViewController in the TabBarController is a NavigationViewController. The first view controller for each NavigationController are my custom views. All is createde an linked via Interface Builder.
Now, how do I pass the managedObjectContext around the right way? Actually I need my views to load the data as soon as possible so that when the user chooses a tab or navigates through the NavigationControllers, the data is already there.
So my questions are:
How to I pass the context properly?
When should I fetch my data, i.e. in which method? "viewDidLoad" or "viewDidAppear"?
Thanks for all ideas!
You should generally stay away from getting shared objects from the app delegate. It makes it behave too much like a global variable, and that has a whole mess of problems associated with it. And singletons are just fancy global variables, so they should be avoided unless really necessary, too.
I would add a managedObjectContext property to each of your view controllers and assign that when you're creating them. That way, your view controllers don't have a tight linkage with the app delegate.
As for when to fetch the data, you should do it lazily. Core Data is really fast, so I would wait until viewWillAppear: to do your fetching. If you wait until viewDidAppear:, the view is already on the screen and there will be a flicker when the data loads. Do be aware, though, that viewWillAppear: is called every time your view will become visible (e.g. when the user taps the back button on the navigation bar, or a modal view controller is dismissed) so you might want to track whether you've already loaded the data and skip the loading on subsequent calls.
I've ran into this same problem, i'll share my solution.
First you need a reference to the Nav Controller in the Tab Bar in the nib file, make sure you connect it up.
IBOutlet UINavigationController *navigationController;
Then, get the Controller as recommended in the support docs and send it the managedObjectContext:
SavedTableViewController *saved = (SavedTableViewController *)[navigationController topViewController];
saved.managedObjectContext = self.managedObjectContext;
Alex is right, "You should generally stay away from getting shared objects from the app delegate. It makes it behave too much like a global variable, and that has a whole mess of problems associated with it."
You can get it from the app delegate at any time like this:
myApp *d = [[UIApplication sharedApplication] delegate];
NSManagedObjectContext *managedObjectContext = d.managedObjectContext;
Or variations of the above.
Other than that you can add a property to all your viewcontrollers and pass it around or you can create a singleton and reference that globally.
Swift
You should not share a NSManagedObjectContext, but you can share the NSPersistentStoreCoordinator.
Thus, you can create a new managed object context for each view, each sharing the same store. It is the preferred method, and allows concurrent, multithreaded access. In the example below, I am assuming that your AppDelegate, *if created with a recent version of Xcode with Use Core Data checked*, has a property named persistentStoreCoordinator:
lazy var managedObjectContext:NSManagedObjectContext? = {
// This property is optional since there are legitimate error conditions that could cause the creation of the context to fail.
if let appDelegate = UIApplication.sharedApplication().delegate as? AppDelegate {
let coordinator = appDelegate.persistentStoreCoordinator
var managedObjectContext = NSManagedObjectContext(concurrencyType: .PrivateQueueConcurrencyType)
managedObjectContext.persistentStoreCoordinator = coordinator
return managedObjectContext
}
}()

Why won't this view controller init? (NEVER MIND)

In trying to debug why a view controller is initing empty, I've ended up a sort of weird place. Check this out:
OffersSearchController *searchController = [[OffersSearchController alloc]
initWithNibName:#"This is a completely bogus nib name."
bundle:nil];
Not a single complaint. I've seen that sort of construct crash out with complaints about being unable to find a nib named "This is a completely bogus...", but not this time. Instead, my searchController pushes onto the navigation controller as if it had loaded successfully. It's empty, though--I can see the full screen of another view that's (accidentally!) "underneath" my UINavigationController stack.
What's happening here? Is [OffersSearchController alloc] coming back nil for some reason?
EDIT: Never mind. Here's the lesson: don't implement loadView when you mean to implement viewDidLoad. Oy. Long week.
Here's the answer (thanks #Eric Petroelje for suggesting I post and accept the answer).
In a burst of late-Friday-afternoon productivity, moving far faster than is recommended, I set up my property initializers and picker-wheel-data-source arrays in -(void)loadView rather than in -(void)viewDidLoad.
Rather than the initWithNibName: call's call to loadView being allowed to propagate up to UIViewController, it happily initialized my fields and that's all.
The documentation for UIViewController initWithNibName:bundle doesn't say anything about what happens if the specified nib name is invalid. Presumably then, an invalid nib name is treated the same as a nil one. However, it does say that the return value is always an initialized UIViewController.
So, what that code is doing is to allocate/initialize a new OffersSearchController with no nib. The view appears empty because it is. You've probably never actually seen that crash before, because it's not supposed to; what's happening is perfectly normal.