message sent to deallocated instance 0x600000147e80 error by clicking sub view event in container view OS X Swift - swift

I am new in os x programming. I am using swift to developing my mac app. In my app home screen contains a Container view and some NSButtons. By clicking on the buttons the NSViewControllers (created in storyboard) appear on the ContainerView. This is done by the code below:
self.ContainerView.subviews.removeAll()
let myViewController = iR_DELEGATE.GoToPage("Questions",container: ContainerView)
self.ContainerView.addSubview(myViewController.view)
the GoToPage function code is:
func GoToPage(identify: String,container: NSView) -> NSViewController
{
let storyBoard = NSStoryboard(name: "Main", bundle: nil) as NSStoryboard
let myViewController = storyBoard.instantiateControllerWithIdentifier(identify) as! NSViewController
myViewController.view .setFrameSize(CGSize.init(width: container.frame.width, height: container.frame.height))
return myViewController
}
The problem is when I click on the button or select the table's view in the sub view controller that displayed in container view the app crash with error:
message sent to deallocated instance 0x600000147e80
I got this error by enable zombie objects.

Your crash is likely caused by the fact that the view controller is deallocated almost immediately after you created it.
A view controller holds a reference to its view, but not the other way around. When you add myViewController.view as a subview, it is retained and displayed. In the meantime, myViewController itself is not retained by anything, so it is removed from memory after the variable is last used.
You probably have your view controller as target to button's action. When you tap the button, it wants to send the action to the view controller, but it has been already deallocated, and it crashes.
To solve it, you need to keep the reference to myViewController to keep it from being deallocated earlier than needed. Either just store it in a variable, or consider using child view controllers for that purpose: https://developer.apple.com/library/mac/documentation/Cocoa/Reference/NSViewController_Class/#//apple_ref/doc/uid/TP40005253-CH1-SW34

Related

Black screen with viewDidLoad after if else statement

I want my app to check at start conditionaly if a variable is correct or not. Based on that it should either go to an intro screen (where he can select a variable in my case select a team) or it should start the main view. After searching i found this code and edited it. But there still seems to be problems. First of all I dont have two identifier. The Intro has one but not the main view. My main View is called WeatherViewController and the Intro screen is called FirstScreenViewController. I also added a picture of my Main.storyboard.
I also googled a lot about conditional UINavigationController but I can only understand with a video and did not found a video about it.
I tried to use the code from here.
var id = hello ? "goToIntro" : "???"
self.window = UIWindow(frame: UIScreen.main.bounds)
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let WeatherViewController: UIViewController = mainStoryboard.FirstScreenViewController(withIdentifier: WVC has no identifier??) as UIViewController
self.window?.rootViewController = WeatherViewController
self.window?.makeKeyAndVisible()
if hello {
self.performSegue(withIdentifier: "goToIntro", sender: self)
} else {
/here nothing should happen. It should open the Main View
self.performSegue(withIdentifier: "???", sender: self)
}
Note: This answer was referring to the original question (before any edits) that was attempting a segue inside loadView().
You're supposed to manually create your controller's view in loadView. You're currently doing nothing, hence the black screen. Furthermore, if you are using a Storyboard or a xib for managing this controller you shouldn't be overriding loadView at all.
Having said that it might be a better idea to move this branching logic a step back, to "something" (a container controller like a UINavigationController or a custom one, or even directly setting the root controller of your window if it makes sense) that will present (or set) A or B based on some condition and thus avoid to load this controller altogether (have in mind that in your code, the controller is initialized, it will be part of the hierarchy and all the lifecycle methods will be called even if you segued directly to another one)
Finally, if you decide for some reason to override loadView you don't have to call viewDidLoad yourself, the system will call this method.

Crashing while assigning nil to UIViewController after removing view and popping

I have a sequence in which I need to pop a UIViewcontroller from outside the class. From the server I will get an log out event, and I need to pop the view controller if it is open. So I have done this to find the top view controller and pop it:
UIViewController *top_view_ctrlr = [self.navigationController topViewController];
if (top_view_ctrlr.view != nil) {
[top_view_ctrlr.view removeFromSuperview];
}
// Popping only the top view controller.
[top_view_ctrlr.navigationController popToViewController:dash animated:NO];
Then I am setting nil for all the view controllers created, e.g.:
if (history != nil) {
history = nil; // Here history is an view controller. Because it is not on top, I am setting only nil here to release all its memory.
}
As I am using ARC, I guess I don't need to worry much about memory release inside history. However, it's crashing with:
[history_class tableView:cellForRowAtIndexPath:]: message sent to deallocated instance.
What could be the problem? Why it is crashing when I set the object to nil?
Why do you remove the topViewController's view from its superview? You should never need to do that in a navigation controller, popToViewController: does that automatically, and removing a view "behind the navigation controller's back" is probably causing the crash.
I think I know what you plan to achieve, but I fear you lack some fundamental understanding of how to use UIViewControllers and UINavigationControllers, that's why I recommend reading this first:
View Controller Programming Guide for iOS
I'm sure it will help you with your current problem and in the future.

Accessing parentviewcontroller

I have a cancel button, when i press cancel button then i pop my current viewcontroller. Before popping my controller, i want to access one member (which is a class Student) of previous view controller. So i am doing this way:
StudentProfileViewController *controller = (StudentProfileViewController*)self.parentViewController;
NSLog(#"%#", controller.student);
My app crashes on line NSLog, error is this:
[UINavigationController student]: unrecognized selector sent to instance 0x6865180
Strange part is its says "[UINavigationController student]" but my controller is UIViewController.
Can anyone shed a light on this. I know some silly mistake is being done.
Thanks
The parentViewController would return the controller you are looking for only if you had presented modally from that view in the first place. It looks to be that you are trying to reference the previous controller in the stack, not the presenting view.
In your case, the parentViewController is the navigationController if that is how you presented. You are casting it to the controller class you wish it to be but that doesn't make it so.
More appropriate method would be to have passed the object you wish to reference in the init method or, most preferably, make a delegate method to tell the former view when this controller is complete, then let the former view react as intended.

On iOS, if a view controller has no view yet, why does NSLog(#"self.view is %p", self.view) crash?

If a new iOS project is created with an Empty App template in Xcode 4.3.2, and in AppDelegate.m:
self.window.rootViewController = [[FooViewController alloc] init];
and in FooViewController's viewDidLoad, the following:
NSLog(#"self.view is %p", self.view);
NSLog(#"self.view is %#", self.view);
will print out the view, so it looks like the default loadView will instantiate a view and assign it to self.view.
So if I override loadView with an all empty method, and comment out the second NSLog statement above, I expect the first NSLog statement to print out 0x0, but instead the app crashed due to bad memory access right at that NSLog line. Why would that be?
Okay, after a knee-jerk and obviously wrong answer, I tried this. The Empty App template would not have a rootViewController, so I used a single screen template. After running, I see that you are getting a stack overflow. In trying to access self.view, you are calling the view property on the superclass, which is then trying to load the view in order to return it, which is calling viewDidLoad, etc., as far as I can see. The other NSLog statement does the same.
The documentation for the view property in UIViewController states:
Because accessing this property can cause the view to be loaded automatically, you can use the isViewLoaded method to determine if the view is currently in memory.
It also has a link to The View Controller Life Cycle, which states:
The steps that occur during the load cycle are as follows:
The load cycle is triggered when the view controller's view property is accessed and the view is not currently in memory.
The view controller calls its loadView method. The default implementation of the loadView method does one of two things:
If the view controller is associated with a storyboard, it loads the views from the storyboard.
If the view controller is not associated with a storyboard, an empty UIView object is created and assigned to the view property.
The view controller calls its viewDidLoad method to allow your subclass to perform any additional load-time tasks.
So when you say:
So if I override loadView with an all empty method
You're deliberately breaking the life cycle, because when your overridden version of loadView finishes, it should have loaded a view. Because it didn't, you get a crash.

How to load more than one ViewController when using TabBarController?

I have a TabBarController with 7 Custom ViewControllers and what i am trying to do is have the TabBarController on startup load the first ViewController in its array as well as a ViewController in one of the other tabs.
As far as i understand it the viewDidLoad method for the ViewController's is only called for that tab when it is first selected. Is there a way to force the TabBarController to call a ViewController viewDidLoad method even if its not active?
thx
Just reference the ViewController view:
[myViewController view]
If myViewController's view is nil, then it will be loaded.
Note that even if this approach is working, your app will be affected by the view loading/unloading mechanism which is controlled by the internal run loop logic and not by your app, while the view controller "internal logic" should be initialized by the initWithNib: method which is completely under your control. But this is just a suggestion to avoid strange bugs, anyway the solution proposed is working.
This technique seems to work pretty good. I have an app with a UITabBarController at the bottom with 5 buttons in it. When the user clicks the 3rd button, the viewDidLoad for that view took 5 seconds to do stuff so I used this technique to cause its viewDidLoad to get called when the app starts. I don't want the 3rd view to be displayed; just to be initialized so it shows up instantly when clicked.
I had to subclass the UITabBarController to something like FoohBarController, then in it's viewDidLoad I made a background thread do this:
{
// get a pointer to the 3rd item in the tab bar (0 based)
UINavigationController *navcon = [self.viewControllers objectAtIndex:2];
// get a pointer to the viewController I want to init
CalendarViewController *calendar = [navcon.viewControllers objectAtIndex:0];
// Just ask for the view. This will force the view to load and to initialize
UIView *v = calendar.view;
v = nil; // to remove a compiler warning
}