I have the next code:
- (void)func
{
MyViewController *ctrl = [MyViewController new];
[ctrl doSmth];
[self presentViewController:ctrl animated:NO];
}
//------------
- (void)doSmth
{
CGRect *rect = self.view.frame;
// Do something with rect
self.view.frame = rect;
}
Ok, I know, that when -[UIViewController view] is equal to nil then it's being created. And this code did work before my changes (only buttons and labels visibility, nothing extraordinary) and now it throws SIGABRT on self.view, it looks like it cannot be created. Suddenly. If I revert my changes, all work like a magic. And even if I won't call this function it'll crash on presenting and in Summary it'll show that view = 0x00000000.
I've got only one question: WAT?
I've found the error.
After XCode's “refactoring” (renaming some IBOutlets) old outlets had been marked with the ! sign but not removed and nothing XCode told me about it.
What I mean by !-s, is Interface Builder showing not valid outlets with an icon. Not valid outlet means that you have a connection between a view and a property / instance variable that does not exist. So when the NIB will be loaded in runtime, the application will crash, because there are no such property / instance variable.
So if you run in such error, you should either remove the outlet, or add the property / instance variable with the same name to class which you have linked the XIB to (usually a File's Owner).
Related
I have a static view that is used to display as a loading indicator in the UI.
static UIView *loadingView;
in my class Loading.m I add loadingView into my container when show() is called
+ (void)show {
if (loadingView == nil) {
UIWindow *containerView = UIApplication.sharedApplication.keyWindow;
loadingView = [UIView alloc] init];
[containerView addSubview:loadingView];
}
}
and when dismiss() is called I remove it from superview:
+ (void)dismiss {
if (loadingView != nil) {
[loadingView removeFromSuperview];
loadingView = nil;
}
}
I found loadingView will always be nil after being added into containerView, so I will keep adding more loadingView into my containerView and it will not be removed when dismiss() is called. When I print UIApplication.sharedApplication.keyWindow it shows an UIView has been added into the stack. It seems like loadingView has lost its reference into containerView after show() is completed.
What gives?
Indeed, this question can have deeper consequences:
You use static UIView. If your application is under ARC, system automatically decides if objects can live when they are not visible. So, you should think twice before using static declaration for visible objects.
If your object does respond to messages like removeFromSuperview and .superview is not nil - it means that the object is not nil for sure. As said above, it is bug of debugger (happens when running the app on device under XCode).
The goal of this post - to pay attention to the UIView objects ierarchy, where parent object has .subviews non-null NSArray property, indicating all objects, added as subviews. They will be autoreleased by ARC next to removed from the stack VC. So, in case of static UIView * declaration, ARC will keep loaded all its parent elements until it will be manually removed from superview. In other words, static UIView * is potentially dangerous construction and can lead to memory leaks or other conflicts. Better way to control loading indicator, for instance - is to check last subview element of current top VC:
if (self.view.subviews && [self.view.subviews.lastObject isKindOfClass: [loadview class]]) {
[self.view.subviews.lastObject removeFromSuperview];
}
In this case you have no risk to of calling nil object methods (app crashes as well) and headache of manual lifecycle control.
How you detected that's "loadingView" is nil?
Please verify it via code or using "po" in Debugger.
If you just saw "nil" value in variables list, sometimes it's Xcode debugger bug.
When variable "nil" in variable list, but it's not "nil".
I'm currently writing an app and I'm experiencing a problem that seems to be caused by code that I did not write (i.e., Apple's classes). I create an instance of a subclass of UIViewController that I wrote. I add it as a child view controller of another custom view controller. Then, when I try to add this new view controller's view as a subview of the parent view controller's view I get a crash with this error.
Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSArrayM objectAtIndex:]: index 0 beyond bounds for empty array'
I have tested and determined that the problem is specifically cause by trying to add the view as a subview. I reference the view in an NSLog just to make sure that it isn't simply the act of referencing it that's causing the error. I've tried adding the view as a subview of different views and that also crashed, so the problem is not with the parent view. Finally, I have tried to add a different view as a subview to the parent view and that did work, further proving that the parent view is fine, and that the prospective subview is at fault. The code where I allocate it is this:
ScheduleSelectorViewController* selector = [[ScheduleSelectorViewController alloc] initWithNibName:#"ScheduleSelectorViewController" bundle:nil];
This has always worked for me. I don't know what it is I've changed. I don't know enough about the inner workings of subview hierarchy to know which array is empty and is causing this crash, so if anyone can help me out I would be extremely grateful.
If there's any other information I could supply that would help let me know.
UPDATE:
Here is the code where it crashes. I have placed NSLogs to indicate the line at which it breaks.
- (void) addViewControllerToStack:(UIViewController *)controller withKey:(NSString *)key
{
if ( !self.stack ) {
self.stack = [[NSMutableDictionary alloc] init];
}
NSLog(#"subviews %#", [controller.view subviews]);
[[controller view] setFrame:offScreenFrame];
[self addChildViewController:controller];
NSLog(#"code gets to here");
[self.view addSubview:controller.view];
NSLog(#"but not to here");
[self.view bringSubviewToFront:controller.view];
[self.stack setObject:controller forKey:key];
[self.stackKeys addObject:key];
}
For the record, the subviews array is not nil.
Check if you have set view's.hidden = YES; somewhere
I was pulling my hair for more than hour to find out I was hiding my pager control and try to set the pageIndicatorTintColor property later which also throw array out of bounds issue.
Make sure you did not override init/ initWithNibName methods in ScheduleSelectorViewController without calling the super methods.
And of course you can always print out [self.view subviews] for parent view in console to understand if subview array is whether nil. If it is nil you should initiate it before adding any views.
This is used to work fine for my pre-ARC code, but since refactoring all the project to be ARC compatible, I start getting this crash:
[CustomViewController respondsToSelector:]: message sent to deallocated instance
My project is an iPad app with a split view, but contrary to apple documentation, previous developer has put another view controller to be visible on app launch before split view. So I know this is not the right way to do, but as I said it used to work before ARC integration so I need to get a workaround with this.
The root view controller which contain a menu of items, each item display a detail form to be filled, then a click on next button move to the next detail screen, etc.
The issue starts when I click on home button put on root view to get back to the home view, here is the relevant code that move the user to the home screen:
//this method is in the appdelegate, and it gets called when clikc on home button located on the root view
- (void) showHome
{
homeController.delegate = self;
self.window.rootViewController = homeController;
[self.window makeKeyAndVisible];
}
Then when I click on a button to get back to the split view (where are the root/details view), the app crashes with the above description. I profiled the app with instruments and the line of code responsible of that is located in the RootViewController, in the didSelectRowAtIndexPath method, here is the relevant code:
if(indexPath.row == MenuCustomViewController){
self.customVC=[[CustomViewController alloc] initWithNibName:#"CustomVC"
bundle:nil];
[viewControllerArray addObject:self.customVC];
self.appDelegate.splitViewController.delegate = self.customVC;
}
customVC is a strong property, I tried to allocate directly and assign to the instance variable but that didn't help to fix the crash. Any thoughts ?
EDIT:
Here is the stack trace given by instruments:
[self.appDelegate.splitViewController setViewControllers:viewControllerArray];//this line caused the crash
[viewControllerArray addObject:self.appDescVC];//this statement is called before the above one
self.custinfoVC=[[CustInfoViewController alloc] initWithNibName:#"CustInfo" bundle:nil];//this statement is called before the above one
self.appDelegate.splitViewController.delegate = self.appDescVC;//this statement is called before the above one
custinfoVC and appDescVC are strong properties.
I solved this problem by setting my delegates and datasources to nil in the dealloc method. Not sure if it'll help you but its worth a try.
- (void)dealloc
{
homeController.delegate = nil;
//if you have any table views these would also need to be set to nil
self.tableView.delegate = nil;
self.tableView.dataSource = nil;
}
You may want to setup the CustomViewController during app launch, and display the other views modally on top if necessary. The error message you're getting is because something is getting released by ARC prematurely. It might have not manifested before because pre-arc stuff wasn't always deallocated immediately. ARC is pretty serious about releasing stuff when it leaves scope
Hard to tell without seeing a lot more of the code involved, and what line it breaks on, etc.
This:
- (void) showHome {
//THIS: where is homeController allocated?
homeController.delegate = self;
self.window.rootViewController = homeController;
[self.window makeKeyAndVisible];
}
EDIT:
Add this line right above the line that causes your crash
for (id object in viewControllerArray) {
NSLog(#"Object: %#",object);
}
I had the same Problem.If you are not using "dealloc" method then use "viewWillDisappear" to set nil.
It was difficult to find which delegate cause issue, because it does not indicate any line or code statement for my App So I have try some way to identify delegate, Maybe it becomes helpful to you.
1.Open xib file and from file's owner, Select "show the connections inspector" right hand side menu. Delegates are listed, set them to nil which are suspected.
(Same as my case)Property Object like Textfield can create issue, So set its delegate to nil.
-(void) viewWillDisappear:(BOOL) animated{
[super viewWillDisappear:animated];
if ([self isMovingFromParentViewController]){
self.countryTextField.delegate = nil;
self.stateTextField.delegate = nil;
}
}
I need to create a number of UIScrollViews dynamically and fill them with content. This is all good except when i set the delegate to self and pan the list i get this exception:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSCFString scrollViewDidScroll:]: unrecognized selector sent to instance 0x7581230'
NSCFString obviously isn't my view controller (which implements the protocol UIScrollViewDelegate) so from what i gather somehow the memory gets messed up and it doesn't keep the reference correctly. Occasionally this can be something else too which strongly points to something being wrong with the memory
Here's the code to create the list:
for (NSUInteger i = 0; i < self.stories.currentStory.selectableWordCount; i++) {
UIScrollView *list = [[UIScrollView alloc] init];
list.alwaysBounceVertical = YES;
list.showsVerticalScrollIndicator = NO;
list.clipsToBounds = NO;
list.delegate = self;
list.pagingEnabled = YES;
[self.view addSubview:list];
.. // add UILabels to the list, set the frame, contentSize etc
[self.wordLists addObject:list]; // this is a #property (nonatomic, strong) NSMutableArray, declared in a private interface()
}
If i NSLog the delegate it's correct. respondsToSelector also matches fine. Interestingly if i comment out the scrollViewDidScroll: respondsToSelector: doesn't match any more and (probably because of this) the UIScrollView won't attempt to call this method any more. This then means that it can reach the delegate correctly to check for the method availability but when it gets called something goes wrong.
I'm targeting iOS5 with ARC. If this wasn't the case i would assume that i messed something up with the memory myself but now i don't have the same control.
I'm having a hard time debugging this issue, any help on how to proceed would be appreciated
D'uh. I was obviously looking in the wrong place. The view controller was added through a .xib and the view was pointing to a subview on the stage. However i needed to create an IBOutlet to the view controller in the main view controller to make sure it stays in memory. Hopefully this can help somebody else with a similar problem :)
I just witnessed a very strange issue where my view would ignore all of the delegate calls coming from a custom view because I called alloc/init on the item at the load. I'm curious as to why.
#synthesize customTextField;
-(void)viewDidLoad {
// by calling this alloc/init, none of the changes here actually update to the view
// everything is ignored from here on in.
// if I comment out the alloc/init line, everything works fine
self.customTextField = [[UITextField alloc] init];
self.customTextField.text = #"Some text";
// setting font and size as well
}
While I would still get calls to the text field delegate methods, none were linked to my specific text fields. I could not respond to just customTextField.
I do realize that calling alloc/init will give me a completely new instance of customTextField... but why wouldn't that new instance be linked to IB and my view?
Because IB linking != binding.
When you link a variable in IB, it's a simply sets the variable once on first load, that's it. It does no other special code to track any changes to it, for good reason.
For example:
You are designing a UITableViewCell, and if you have a cell that is selected, you must rearrange all of the content inside the cell. In this case, you determined it would be easier if you just recreated all of the subviews and re-added them into the view, so you do the following:
-(void) layoutSubviews {
if (cellIsSelected)
{
// custom button is an IBOutlet property, which is by default a subview of self
self.customButton = [UIButton buttonWithType:UIButtonTypeCustom];
[[self someSubView] addSubview:customButton];
}
else {
// where is customButton located now? is it a subview of self or `someSubView`?
self.customButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
// [self addSubview:customButton];
}
}
Thus, it is much easier for IB to say let's set this once, and let the programmer figure the rest out than for IB to try and track all changes made to an object and report them the to the UI.
viewDidLoad is called after your nib is loaded, and creating a new UITextField instance at this point will not be associated with your nib. If you're setting up new instances manually you also need to manually setup the delegates, and add them as subviews of your view.
The XIB file has no way of knowing that you are changing the reference. Consider the following piece of code
NSObject *myObjA = [[NSObject alloc]init]; //create object
NSObject *myObjB = myObjA; //assign reference <- this is the your case after xib load
myObjB = [[NSObject alloc]init]; //create object, myObjA still lives on.
It's basically the same that happens when you load your XIB file; You get the reference to the instantiated object (equals myObjB in above example). You can do with the reference what ever you please but you do not change the interface instance just by creating a new object.