I have narrowed down an ugly bug, but since it seems like something internal to the nib/Interface Builder, I'm at a loss of what to do next.
I've got a UIView created in IB which functions as a custom dialog box. It shows a message and two buttons. (Proceed or Cancel.) Both buttons have a Background image set in Interface Builder.
Something is wrong with the way the background image for the cancel button is being handled. With NSZombieEnabled, I've been running the program. Most often, the method below logs this:
-[ModalDialog showInView:title:message:cancelText:proceedText:]
dialogProceedButton <UIButton: 0x7031f10; frame = (286 192; 90 31) // (etc.)
dialogProceedButtonBackground <UIImage: 0x3b36120>
dialogCancelButton <UIButton: 0x3b39cd0; frame = (104 192; 90 31) // (etc.)
dialogCancelButtonBackground <UIImage: 0x3b3a920>
That's completely normal. However, sometimes it does this (I can get this to repeat somewhat reliably if I "rush" the UI by rapid tapping some interface buttons):
-[ModalDialog showInView:title:message:cancelText:proceedText:]
dialogProceedButton <UIButton: 0x7031f10; frame = (286 192; 90 31) // (etc.)
dialogProceedButtonBackground <UIImage: 0x3b36120>
dialogCancelButton <UIButton: 0x3b39cd0; frame = (104 192; 90 31) // (etc.)
*** -[UIImage retain]: message sent to deallocated instance 0x3b3a920
As you can see, NSZombieEnabled found that the background image for the Cancel button has been deallocated, but is being sent a retain message. (Not by me, though... that image is only used for this one button, and only accessed in Interface Builder. I don't have any IBOutlets or any variables linked to that image.)
So, um, what now?
EDIT:
Sometimes, it's not a retain message that gets caught as a zombie, sometimes it's isKindOfClass:
//(the object address is always dialogCancelButton.currentBackgroundImage)
-[UIImage isKindOfClass:]: message sent to deallocated instance 0x1661f0
//occasionally, these come along, too:
*** NSInvocation: warning: object 0x7e0d0b0 of class '_NSZombie_UIImage' does not implement methodSignatureForSelector: -- trouble ahead
*** NSInvocation: warning: object 0x7e0d0b0 of class '_NSZombie_UIImage' does not implement doesNotRecognizeSelector: -- abort
This is my custom UIViews "showInView" method:
- (void)showInView:superView
title:(NSString*)title
message:(NSString*)message
cancelText:(NSString*)cancelText
proceedText:(NSString*)proceedText {
NSLog(#"%s",__PRETTY_FUNCTION__);
NSLog(#"dialogProceedButton %#", dialogProceedButton);
NSLog(#"dialogProceedButtonBackground %#", dialogProceedButton.currentBackgroundImage);
NSLog(#"dialogCancelButton %#", dialogCancelButton);
NSLog(#"dialogCancelButtonBackground %#", dialogCancelButton.currentBackgroundImage);
CGRect rect;
dialogTitle.text = title;
dialogMessage.text = message;
[dialogProceedButton setTitle:proceedText forState:UIControlStateNormal];
if (cancelText == #"") { // SINGLE BUTTON DIALOG
dialogCancelButton.hidden = YES;
rect = [dialogProceedButton frame];
rect.origin.x = 195; //center the button
[dialogProceedButton setFrame:rect];
} else {
[dialogCancelButton setTitle:cancelText forState:UIControlStateNormal];
dialogCancelButton.hidden = NO;
rect = [dialogProceedButton frame];
rect.origin.x = 286; //button on right of dialog box
[dialogProceedButton setFrame:rect];
}
[UIView beginAnimations:#"modalAppears" context:nil];
[UIView setAnimationDuration:0.5];
[superView addSubview:self];
self.alpha = 1.0;
[UIView commitAnimations];
}
Thanks!
Ok, this one's a what-the-heck. I decided to try reversing the images for the Proceed and Cancel buttons. The result was now that the Proceed button image would cause the crash (just as intermittently). I completely deleted the image from my project and from Interface Builder. I then added a fresh copy with a new name and hooked it up.
With the previous setup, I had been able to reproduce the crash about 40% of the time. I've tried about 20 times to reproduce the crash after these changes, and I cannot reproduce it at all now.
If the image or the nib was corrupted, why, why, why would it cause a random/intermittent symptom?
Whattathing. Hope it's well and truly fixed.
Update:
And... so there is a bit more to it. Turns out I discovered that I had coincidentally been using the same image as a placeholder in my (incomplete) instructions view. For temporary convenience there, I was using [UIImage imageNamed:] to grab the image. It was being properly allocated and released, but it seems that IB's cooperation with the imageNamed: method and/or cache is not perfect.
The fact that when I went and grabbed a fresh copy of the image, I also gave it a new name, meant that now the IB button image and the temporary placeholder image were no longer the same image at all.
I went back to a backup of my project from a couple days ago to test my theory. All I did was tell the instructions view to use a different placeholder image. Crashes stopped.
This is probably an SDK bug, then. There shouldn't be any reason not to use an image in IB and also use the same image elsewhere using imageNamed:. If I feel wily or bored one of these days, maybe I'll distill this down into an example project to send to Apple radar.
How is your XIB file wired to your view? What IBOutlets do you have defined? I really doubt you have solved your problem in the way you describe.
Related
I am currently working on an application that has some ViewController with a button on it that pushes to a UITableViewController with Search Bar and Search Display controller. I have some data in the cells and that gets populated. I have added the following code to hide the search bar and also make it clearcolor when you do see it:
[[self.searchBar.subviews objectAtIndex:0] removeFromSuperView];
[self.searchBar setBackgroundColor:[UIColor clearColor]];
CGRect newBounds = [[self tableView]bounds];
newBounds.origin.y = newBounds.origin.y + searchBar.bounds.size.height;
[[self tableView] setBounds:newBounds];
Now this works when I am using the iOS simulator, but when I run it on my device and begin to scroll, either up or down, it crashes and gives me the following error:
EXC_BREAKPOINT (code = EXC_ARM_BREAKPOINT, subcode = 0xdefe)
I then enabled Zombie Objects to further debug and got this:
-[UIView frame]: message sent to deallocated instance 0x156b1430
When I take off:
[[self.searchBar.subviews objectAtIndex:0] removeFromSuperView];
[self.searchBar setBackgroundColor:[UIColor clearColor]];
and run my application on my device again, it does not crash and works fine.
Anyone have any ideas whats going on and how this is happening? Thanks in advance!
[[self.searchBar.subviews objectAtIndex:0] removeFromSuperView];
//This Line remove search bar from view ,which means it is deallocated at that moment so when you again try to remove it ,,the app crash ..
// So,instead of this try to hide searchbar ,,
Self. searchBar.hidden =YES;
I think UISearchBar has unsafeunretained reference of [self.searchBar.subviews objectAtIndex:0];
And You set the Bounds of UITableView then change frame of UITableView.
It would fire auto resizing and layoutSubviews.
So, UISearchBar's layoutSubviews method access [self.searchBar.subviews objectAtIndex:0].frame
for layout or auto resizing.
It's not recommended that change view in UIControls from SDK.
There is an idea that Setting hidden=YES instead removeFromSuperview.
I was having this same problem. Got to this page, read the words 'zombie objects' in the question and remembered that I'd left zombie objects enabled. After disabling them, the problem went away. Now when I scroll my tableView, there's no crash.
This may be weird, but perhaps it will help someone...
On my iPhone app, I simply want to set a particular background image, which depends on whether it's an iPhone 5 or not.
So, I tried two approaches:
A) Using
self.view.backgroundColor = [UIColor colorWithPatternImage:backGroundimage];
B) Creating an UIImageView and setting up the image there. Code:
UIImageView *backgroundImageView = [[UIImageView alloc]initWithFrame:screenBounds];
[backgroundImageView setImage:[UIImage imageNamed:backGroundImage]];
[self.view addSubview:backgroundImageView];
But I am having issues with both of them:
Issues with Step A:
When I set the image through that way, I have to deal with the image scaling issues for different sizes of the screen. I use the following code to do the scalling:
UIGraphicsBeginImageContext(screenBounds.size);
[[UIImage imageNamed:backGroundImage] drawInRect:screenBounds];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
self.view.backgroundColor = [UIColor colorWithPatternImage:image];
Another issue from Step A is that the image appears quite blurry. It doesn't have the same sharpness to it.
Issues with Step B:
With this, the image looks really crisp and sharp - just the way it should look.
But when I switch to another view using the following code, strangely enough the UIImageView backgroundImageView still appears on the second one. The code I use to switch views is:
[self presentViewController:secondViewController animated:YES completion:nil];
I even tried [backgroundImageView removeFromSuperview], but that doesn't solve anything either.
So what am I doing wrong? And how can I set up a picture as my background which is dependent on the size of the iphone?
Plan B is a good plan. Presenting another view controller should and will definitely hide your image view. If it isn't happening, then it's a problem with the creation of secondViewController, unrelated to the background image on the presenting VC.
Before presenting secondViewController, log it:
NSLog(#"presenting %#", secondViewController);
I'll bet a dollar that it's nil. If I'm right, let's have a look at how you initialize secondViewController. That's the problem, unrelated to the background image view.
Okay, I finally fixed this issue, although the cause of this issue is still puzzling to me.
To fix this, I had to create an IBOutlet property for UIImageView and hook it up on the XIB file.
The reason I was programmatically creating the UIImageView is because the size of the UIImageView depends on what size iPhone they are using. But for the IBOutlet (let's call it as UIImageViewOutlet, I simply used [self.UIImageViewOutlet setFrame:] to get the size and location that I wanted.
I also discovered that one of the buttons that I was programmatically creating, was still visible in the secondViewController. I ended up creating an Outlet on the XIB file for that one as well and used setFrame on it to position it properly.
If anyone who knows the reason of this problem, I will be very grateful.
The application which runs smoothly on 4.0.1 when was tried on run on 4.2.1 produced distorted screen i.e., screen somewhat moved to left by 20%. Phones on which iOS 4.0.1 and 4.2.1 are installed are 2 different phones. What could be the problem?
We observed that wherever we have added as subview this problem is occurring.
Thanks,
Satish
From your description, I think it is either that you are not setting the correct view.autoresizingMask properly or there is a subtle change in how views are being laid out.
Try setting the frame of the view that moved explicitly and see what happens.
[view setFrame:CGRectMake(0, 0, 320, 460)]
Some related piece of code and/or screenshots would definitely help. Also, is it a UIKit application or a Cocos2D game?
EDIT: Since you can't provide code (or a stripped-down example version) I'll just post some code that I've been using.
Usually when I add subviews to "fill" a parent view, I had do the following:
UIView *parent = nil; // find parent view
UIView *child = nil; // child view to add to parent
[parent addSubview:child];
CGRect frame = [parent frame];
frame.origin.x = 0;
frame.origin.y = 0;
[child setFrame:frame];
If you have done that and it still does not work I believe the problem might lies somewhere else (i.e. have you overridden layoutSubviews by any chance? or was the phone jailbroken?)
I highly doubt it'll be a UIKit bug though but it is totally possible.
I'm sure I'm missing something basic here. I'm trying out the CALayers 'hello world' code from:
http://www.raywenderlich.com/2502/introduction-to-calayers-tutorial
Doing the very first example. New single view project in xcode 4.2. No change to the nib/storyboard. Import QuartzCore. Add the following code to ViewDidLoad in the ViewController.m:
- (void)viewDidLoad
{
[super viewDidLoad];
self.view.layer.backgroundColor = [UIColor blueColor].CGColor;
self.view.layer.cornerRadius = 30.0;
self.view.layer.frame = CGRectMake(20, 20, 20, 20);
}
I run this (ipad 2 or ipad simulator) and get a full screen blue rectangle with rounded corners. What I hoped to get was a 20x20 blue rectangle offset by 20/20.
I'm clearly getting control over the views layer (as shown by the color and rounded corners). However, adjusting the frame seems to have no impact. I've NSLog'ed the frame before/after setting it, and it has changed. Is the frame of the root layer locked to the uiview frame?
I don't have a strong reason to change the views layers frame, I'm just trying to reason through what is going on. Hopefully this is an easy question...
Thanks!
Paul
Actually, the previous answer (you can't set uiview.layer.frame as it always fills the uiview) is close, but not quite complete. After reading the answer, I registered for the original site and to comment that the tutorial had issues. In doing so, I found that there were already comments that I hadn't seen in my first pass that addressed this. Using those, I started doing some testing.
The bottom line, if you move the self.view.layer.frame setting code from viewDidLoad to viewWillAppear, it works fine. That means that you can change the frame of the root layer of a view. However, if you do it in viewDidLoad it will be undone later.
However, the previous answer is still pretty close. I NSLog'ed the frame of the root layer and the frame of the view. Changing the root layer frame changes the view frame. So, the answer that the view.layer.frame always fills the view.frame is correct. However, setting the layer frame resets the view frame to match. (I'm guessing that uiview.frame property simply returns uiview.layer.frame...)
So, at some point in time between 2010 and today, something in the environment changed. Specifically, after viewDidLoad and before viewWillAppear the uiview/layer frame appears to be reset to the nib specified value. This overrides any changes in viewDidLoad. Changes made in viewWillAppear appear to stick.
Robin's answer got me on the right track, but I wanted to spell out the full answer.
The tutorial is wrong. Setting the frame of the view's main layer has no effect. The main layer is 'special' and will always fill the view's bounds. What you need to do is create a sublayer of the main layer like this:
- (void)viewDidLoad
{
[super viewDidLoad];
CALayer *newLayer = [[CALayer alloc] init];
newLayer.backgroundColor = [UIColor orangeColor].CGColor;
newLayer.cornerRadius = 20.0;
newLayer.frame = CGRectMake(100.0f, 100.0f, 200.0f, 200.0f);
[self.view.layer addSublayer:newLayer];
[newLayer release]; // Assuming you're not using ARC
}
Also, in your code a layer with width 20pt and height 20pt is too small to have rounded corners of 30pt anyway.
basically im running my apps with instruments and found out that by just setting a background image to the UIButton, it takes up 6mb of data(which i do not want in case low-memory warnings). i read around and found out that since the button has been assigned the image, it retains it(and the memory).
How should i code it then?My current codes are as below. Btw im new to iPhone development so please tell me what to do.
btw this button would just bring me to another view. is there anyway to release the memory that was allocated to this image?
.m file
-(void)viewDidLoad{
UIColor *background = [[UIColor alloc] initWithPatternImage:[UIImage imageNamed:#"MainScreen.png"]];
selectionScreen.backgroundColor = background;
[background release]
}
You mentioned in comment above that your .png is under 300k. That's perhaps a touch big, but you're actually not looking at the right thing. A png gets expanded to a native CGImage object. I usually figure a 32-bit image with alpha takes up width * height * 4 bytes of memory. That's pretty much guaranteed to be bigger than the PNG it gets expanded from, and in your case could be quite big indeed. Enough so that the docs recommend not instantiating UIImages bigger than 1024 x 1024.
Now, one solution could be that -initWithPatternImage can take a small piece of your background, and will tile it when it's drawn. So your first shot at solving this would be to provide that method as small an image as possible, and let it tile to bigger sizes.
Second thing, the retention. You're correctly releasing your UIColor object after setting it on the background. You WANT that object you set it on to retain it! In a world of infinite memory, you'd want that button to retain its background color until the viewcontroller it's on gets dealloc'ed. If it's still huge and you really have to get rid of it before backing out of the view controller (say when you push to a new UINavigationController view or something), you could try setting background to nil (or a system default color maybe) in -viewDidDisappear and re-building your background in -viewWillAppear.
Wheb viewWillDisappear, you can set backgroundColor as another color, and release the background color you made.
-(void)viewWillDisappear:(BOOL)animated
{
// release original backgroundColor
// default backgroundColor is nil by UIView class reference.
selectionScreen.backgroundColor = nil;
}
Hope this can help you.
Did you try using something like:
button = [UIButton buttonWithType:UIButtonTypeCustom];
[button setFrame:/frameOfChoice/];
[button setBackgroundImage:[UIImage imageNamed:#"MainScreen.png"] forControlState:UIControlStateNormal];
I'm not sure how this effects the memory usage tho.