I'm new to this and couldn't find an answer.
On my old 4.x app, I added files in which I implemented my own UIView, where I ran some "drawRect:(CGRect)rect" to paint some lines. For example...
/// DrawView.h
#import <UIKit/UIKit.h>
#interface DrawView : UIView <UINavigationControllerDelegate> {
...
}
-(void)DoSomething;
#end
Then, in my view controller .m file, after importing DrawView.h, I could do this:
self.view = (DrawView *)self.view;
[(DrawView *)self.view DoSomething];
Worked like a charm in my IOS4 stuff. Now that I try an IOS5 app using storyboard, this trick no longer works.
The first line...
self.view = (DrawView *)self.view;
doesn't crash. But I don't see any painting on the screen. Was expecting drawRect to show some stuff. Nothing. So I doubt the assignment was successful, or something is hidden somewhere.
The second line...
[(DrawView *)self.view DoSomething];
returns the following error:
-[UIView DoSomething]: unrecognized selector sent to instance 0x87631b0
sharedlibrary apply-load-rules all
Program received signal SIGABRT
I've read somewhere that drawRect (for painting) can't run directly on the viewcontroller, and has to run on a separate instantiated implementation of UIView. That's why I do this. But apparently my old trick for assigning a custom view to the controller's self.view no longer works.
Do you know how to do this? I mean, when using storyboards and all?
Thanks
I didn't see any other options. So I'll post this as the answer. Feel free to comment or post better alternatives if you know any...
DrawView *myView = [[[DrawView alloc] initWithFrame:CGRectMake(0, 0, 320, 480)] autorelease];
[self.view addSubview:myView];
[myView DoSomething];
In my case I actually declared *myView in the .h file, then synthesized it, removed the "autorelease", and released it dealloc.
Related
I have got a single view in my storyboard, which I add to my current view by doing the following :
MainViewController *mvc = [self.storyboard instantiateViewControllerWithIdentifier:#"MainController"];
[self.view addSubview:mvc.view];
The view appears, but everything I do after it appears, leads to a crash. What am I doing wrong ?
Here is an example when it crashes:
-(IBAction)showUsername:(id)sender{
[testLabel setText:#"username"];
}
Everything is hooked up in storyboard as well, so falsely linked connections should not cause the problem.
You instantiate a new view controller:
MainViewController *mvc = [self.storyboard instantiateViewControllerWithIdentifier:#"MainController"];
But you do not retain it. Your view hierarchy is, as soon you added it to another view.
[self.view addSubview:mvc.view];
So when a button is clicked, a message is sent to you IBAction, but your view controller has been released already. To prevent this from happening, retain your mvc variable, for example somewhere in a property.
#property(nonatomic, strong) MainViewController *controller;
self.controller = mvc;
I can think all reason before you show log...
Turn NSZombie on in the Product>>Edit Scheme you should get more descriptive Error showing then. Then you can add it.
Make sure your method is declared and implemented correctly. Also make sure you have IBOutlet UILabel * testLabel in your .h. The only other problem I can think of other than that is how you hooked it up. Does it only crash when you press the button?
This line is wrong this will be why you are getting the error.
MainViewController *mvc = [self.storyboard instantiateViewControllerWithIdentifier:#"MainController"];
[self.view addSubview:mvc.view];
replace it with this
MainViewController *mvc = [self.storyboard instantiateViewControllerWithIdentifier:#"MainController"];
[self presentModalViewController:mvc animated:YES];
In storyboards you are not adding a subview you are doing one of three things presenting a modal, pushing it on to the navigation controller stack or making a custom one of these.
I am pretty new to iPhone programming, and was playing around with an app yesterday trying different scenarios with view controllers and nib files. So, I started a new app with a FirstViewController (FVC for short) and an FVC.xib.
I layed out a quick view in FVC.xib and ran the app - view displays, great.
I now wanted to have a second view I could add on top of the main view. So I went ahead and created SecondViewController.xib (SVC) but did not create the .m and .h files. I went about trying to load both these views from the same view controller, and here is where my question lies:
I created a button in FVC.xib and created an IBAction like this:
- (IBAction)loadSVC {
FirstViewController *viewController = [[FirstViewController alloc] initWithNibName:#"SecondViewController" bundle:[NSBundle mainBundle]];
secondView = viewcontroller.view;
[viewController release];
[self.view addSubView:secondView];
}
So this works great and adds the contents of SVC.xib, but when I try and remove that view from the superview, the app crashes:
[secondView removeFromSuperview];
If I actually create a view controller for SVC, use that to instantiate my view in FVC, and move the remove code to the SVC:
[self.view removeFromSuperview];
Everything works. My question - I kind of get why my first method crashes, but I was hoping someone could explain why and what goes on behind the scenes. I'm still a noob with object oriented programming, so what is actually happening in my first case where I create a new instance of FirstViewController and add its view to self.view? Why can't I release it (I assume because the original view is associated with FirstViewController, and when I create a new instance with the second xib it messes everything up) - I'd love a more technical explanation as to what is happening...
Thanks much!!
EDIT to add more info in response to Nick's reply below
Nick - so your answer did clear my thinking a bit in regards to the retain count, etc... I did another test app trying to get this working from a single view controller - think, for example, that I wanted to display an Alert or Welcome message to the user (I know in a real app there are different methods to accomplish this, but this is more of a learning experience) -- so I have my main view # MainViewController and layout my alert message in a xib called alert.xib -- so there is no logic behind the alert message, no reason for it to have a view controller that I can see, my end goal being loading/unloading this on top of my main view from the main view's view controller (or understanding why it is impossible)
I tried this using instance variables as you recommended:
In MainViewController.h:
#import <UIKit/UIKit.h>
UIViewController *secondController;
UIView *secondView;
#interface MainViewController : UIViewController {
}
#property(nonatomic, retain) UIViewController *secondController;
#property(nonatomic, retain) UIView *secondView;
- (IBAction)loadSecond;
- (IBAction)removeSecond;
#end
In MainViewController.m:
#import "MainViewController.h"
#implementation MainViewController
#synthesize secondController, secondView;
- (IBAction)loadSecond {
secondController = [[MainViewController alloc] initWithNibName:#"alert" bundle:[NSBundle mainBundle]];
secondView = secondController.view;
[self.view addSubview:secondView];
}
- (IBAction)removeSecond {
//I've tried a number of things here, like [secondView removeFromSuperview];, [self.secondView removeFromSuperview];, [secondController.view removeFromSuperview];
}
- (void)dealloc {
[secondController release];
[secondView release];
[super dealloc];
}
So - this works to load the alert view, but the removeSecond button does nothing (I did use NSLog to verify the removeSecond method is fired) - why?
Second, and most importantly - is this even possible, or is it horrible practice? Should every nib/view I am manipulating have their own view controller? Am I wrong to think I could just make a new instance of MainViewController and use it to display and remove this no-functionality, very temporary view? (And yes, I realize I could easily create this view programatically or accomplish the end goal in many different ways which would be easier, but I'm trying to really learn this stuff and I think figuring this out will help...
Thanks for the help!
You created a view controller
You accessed its view which caused controller to create the view and call the delegates (i.e. viewDidLoad)
Controller returns the view that you asked for
Now you add the view as a subview which increases its retain count
Controller is released and it releases the view, BUT since view's retain count was increased the view is still there
You try to remove the view, it is unloaded and delegates are to be called (e.g. viewDidUnload), however that messes up since the controller who created the view is released and that piece of memory is... smth else :)
That's why the first method doesn't work.
The second method is NOT correct either but it works because:
You remove controller's view from superview but since controller itself is not released (you didn't call [self release] or anything like that, not saying that you should :), just an example), then the view didn't reach 0 (zero) retain count and is still there - which means its subviews aren't removed
The proper way to do it is to save the reference to the controller as an instance variable (usually declare a synthesized property), and release it only when you are done with the view, making sure that the view is removed from superview before hand. The default templete for a View Based App shows how view controller should be managed
Hope this helps to understand why both methods behave differently
Based on your clarifications, you don't need secondView property or iVar. Also in your loadSecond instead of secontController = bla you need self.secondController = bla, otherwise you simply assign reference to the iVar instead of going through the setter.
Yes, it's possible to load subviews/other resources from a nib without having a dedicated controller
This is how you do it (one of the approaches):
UIView *result = nil;
NSArray *bundle = [[NSBundle mainBundle] loadNibNamed:#"MyNibName" owner:owner options:nil];
for ( id o in bundle ) {
if ( [o isKindOfClass:[UIView class]] ) {
result = (UIView *)o;
break;
}
}
Here the result will contain the first UIView in MyNibName. You can use other criteria to find out whether you got the view you wanted (tags, types...)
Okay I just typed this whole question out and then managed to delete it. Sigh. Anyway, standard disclosure that I have searched everywhere and banged my head on the keyboard and can't figure this out.
I'm building a basic app based on the utility application template (with a MainViewController and FlipsideViewController). Side note: I still don't understand the purpose of MainView and FlipsideView in this scheme so if someone can explain it that wouldn't be too terrible :D
Anyway, at the bottom of both of these views I want to have a toolbar. It was easy enough to add that to a given view with IB, but I want the toolbar to have its own controller and model because I want to keep its state consistent across the two views. Accordingly I'd like to load that view from a nib but it seems I'm doing something wrong. I followed the advice here: NSViewController and multiple subviews from a Nib but obviously borked it up so more insight would be appreciated.
I did the following things:
I created ToolBarViewController which is basically empty but is the file owner of ToolBar.xib. Inside ToolBar.xib I have a view with a frame the size of the toolbar and inside that a toolbar. In MainView.xib I've got a view element of the same size which is wired up to toolBarView found in the code below...
In MainViewController.h:
#import "ToolBarViewController.h"
#interface MainViewController : UIViewController <FlipsideViewControllerDelegate, MKMapViewDelegate> {
...
ToolBarViewController *toolBarViewController;
IBOutlet UIView *toolBarView;
}
...
#property(nonatomic, retain) ToolBarViewController *toolBarViewController;
#property(nonatomic, retain) IBOutlet UIView *toolBarView;
In MainViewController.m:
#synthesize toolBarViewController;
#synthesize toolBarView;
- (void)loadView
{
[super loadView];
toolBarViewController = [[ToolBarViewController alloc] initWithNibName:#"ToolBar" bundle:nil];
[[toolBarViewController view] setFrame:[toolBarView frame]];
[[self view] replaceSubview:toolBarView with:[toolBarViewController view]];
}
When I build and run I get the warning for the last line above that 'UIView' may not respond to 'replaceSubview:with' and on running it the following exception is thrown:
*** -[MainView replaceSubview:with:]: unrecognized selector sent to instance 0x450c540
Can anyone explain what I'm doing wrong? Thanks!å
There is no such method as [UIView replaceSubview:with:]. That's an NSView method. You should have gotten a warning about this when you compiled.
To do the same thing, you'd need to use [[self view] insertSubview:aboveSubview:] and then [toolbarView removeFromSuperview].
That said, I'm not certain if this is how you want to mess with the toolbar. I'd probably try something more like:
self.toolbarItems = [toolbarViewController toolbarItems];
That's because there's no such UIView's replaceSubview:with: (there's a method like that for NSView on the Mac, though).
Remember that Objective-C warnings are usually errors ;) in general I tend to never leave them without treatment, and I even turn them into errors:
http://akosma.com/2009/07/16/objective-c-compiler-warnings/
Being new to Cocoa, I'm having a few issues with Interface Builder, UIViewController and friends.
I have a UIViewController subclass with a UIView defined in a xib, and with the controller's view outlet connected to the view. The xib's "file's owner" is set as myViewcontroller subclass.
In this one instance, the following code to load the controller/view (from the main view controller) doesn't work as expected:
if ( self.myViewController == nil )
{
self.myViewController = [[MyViewController alloc]
initWithNibName:#"MyViewController" bundle:nil];
}
[self.navigationController
pushViewController:self.myViewController animated:YES];
In MyViewController's methods, I have placed breakpoints and log messages to see what is going on:
-(id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]) {
NSLog(#"initWithNibName\n");
}
return self;
}
-(void)viewDidLoad {
[super viewDidLoad];
NSLog(#"viewDidLoad\n");
}
Expected result
Both -initWithNibName and -viewDidLoad methods are called, and myViewController's view is displayed.
Observed result
Only -initWithNibName is called, the view is not displayed.
Have I missed something? Can anyone recommend anything to check? (Particularly in the wondrously opaque Interface Builder tool).
RE: SOLUTION FOUND!!!!!
Indeed that seems to be a working solution, however the real trick is not in setting the view.hidden property to NO, what makes the view load from the nib file is the calling of the UIViewController's view method, the view only actually gets loaded from the nib when the view method is called for the first time.
In that sense, a simple [viewController view] message would force the view to load from the nib file.
Ok, I have a partial answer - maybe the gurus can explain some more. The problem is:
[self.navigationController pushViewController:myViewController animated:YES];
Looking more closely, in this case self.navigationController is nil - so the push message is going no-where.
Instead, if I send:
[self.view addSubview:self.myViewController.view];
Then the view appears and -viewDidLoad is called.
I'm not entirely sure why self.navigationController is not set in this instance - the only thing I can think of is that self is a subclass of UIViewController rather than UITableViewController (where the pushViewController code came from).
Also, silently allowing messages to go to nil seems like a bad idea, although these answers say otherwise. See also my question here.
Final edit:
Answers in comments below, I've realised the display function that I was actually after (given myViewController is modal) is:
[self presentModalViewController:myViewController animated:YES];
Thanks everyone for their helpful responses.
SOLUTION FOUND!!!!!
Even something as innocuous as this makes the viewDidLoad method call happen.
Insert this right after alloc initWithNibName
viewController.view.hidden = NO; //calls viewDidLoad
make sure that the view outlet in File's Owner (your viewController subclass) is connected to the actual view (i.e. the 480X320 canvas you see on your screen that you use to build your UI)
Chances are that you might not have linked the supposed ViewController in main.storyboard from the Identity Inspector to the custom class you created. You might be able to navigate to that controller from other view controllers via segues but any of viewDidLoad(), viewWillAppear() etc. won't be executed.
Simply use
- (void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
//Your Code here
}
instead of the viewDidLoad method.
Another reason, somewhat obvious in retrospect: if viewController.view is set in code, then the viewDidLoad event will not trigger.
It looks like a capitalization problem to me. You're referencing the class MyViewController instead of the property myViewController in the call to pushViewController.
Check your run log for errors. Almost certainly, the NIB is not loading, and there should be an error to that effect. The most likely cause for that is failure to put it in the bundle. Look in your "Copy Resources" build phase and make sure that the XIB is actually being copied. Build for the simulator, and go down into the build directory and make sure that the NIB is in the .app bundle.
Apart from other answers here,
It often happens when the identifier with which you instantiate your ViewController from the storyboard is incorrect. For e.g.
[[self getStoryboard] instantiateViewControllerWithIdentifier:MyVC];
If MyVC is the identifier of some other ViewController, this might happen.
OP is using nib instead of storyboard here. But the answer applies.
The page has been presented but not visible in Debug view hierarchy & in device(simulator also), issue happens based on and
i found the fix:
func viewWillLayoutSubviews{
if day == true{
self.view.backgroundColor = .clear
}else{
self.view.backgroundColor = .blue
}
}
Don't try to implement the self.view (viewcontrollers view) in function of layoutsubviews. So better use self.view in viewwillappear or viewdidload. This issue happens starts from v-14 devices.
Hope it works for you too.
Here's a common practice I see often (including from a very popular iPhone developer book)
In the .h file:
#interface SomeViewController : UIViewController
{
UIImageView *imgView;
}
Somewhere in the .m file:
imgView = [[UIImageView alloc] initWithFrame:[[UIScreen mainScreen]
applicationFrame]];
[imgView setImage:[UIImage imageNamed:#"someimage.png"]];
[self addSubview:imgView];
[imgView release];
And later, we see this...
- (void) dealloc
{
[imgView release];
[super dealloc];
}
Since imgView has a matching alloc and release, is the release of imgView in dealloc necessary?
Where is the imgView retained by the addSubview call accounted for?
The code is incorrect. You'll end up releasing imgView after it's been deallocated.
In your .m file, you:
alloc it --> you own it
add it as a subview --> you and the UIView owns it
release it --> you don't own it
Then in dealloc, you release imgView even though, as we established at step 3 above, you don't own it. When you call [super dealloc], the view will release all of its subviews, and I imagine you'll get an exception.
If you want to keep an ivar of imgView, I suggest not calling release after you add it as a subview, and keep your dealloc the same. That way, even if imgView is at some point removed from the view hierarchy, you'll still have a valid reference to it.
The code is incorrect, you shouldn't be releasing it in the init method, just when dealloc is called (that's if you want to keep it as an ivar, you don't need to unless you need a pointer to it elsewhere since addSubview: will retain the view for you).
I believe the reason it's not actually crashing is because it's still being retained by the superclass (from the call to addSubview:), so when it's released in dealloc that's actually balanced out. The view probably removes itself from the superview when it's deallocated immediately afterwards, so when [super dealloc] is called it's not being over-released. That's my hunch, at lease.
Release in init is incorrect.
You mentioned "common practice" and an un-named book. I suggest looking at the canonical examples from Apple: ViewTransitions is a good example for this case (and 2 views to boot ;)
http://developer.apple.com/iphone/library/samplecode/ViewTransitions/index.html
(I don't have enough reputation to add comment yet.)
#bentford: Correct me if I'm wrong, but I believe that in order to use the imgView property's synthesized setter, you must use "self.imgView":
self.imgView = [[UIImageView alloc] initWithFrame:[[UIScreen mainScreen]
If you don't have self., it's just using the ivar, and it's not getting the additional retain.
The basic answer is, there should only be one [imgView release] in the example code (whether it's after addSubview or in dealloc). However, I would remove [imgView release] from dealloc and leave it after addSubview.
There is a catch on the iPhone; with didReceiveMemoryWarning, you could have objects (including an entire view) released out from under you. If you have an application-wide retain set and you don't respect memory then you could find the application simply being killed.
A good example is:
if you think of a nested set of 3 views, View 1-> View 2-> View 3.
Next, consider the 'viewDidLoad' and 'viewDidUnload' calls. If the user is currently in 'View 3', it's possible that View1 is unloaded, and this is where it gets nasty.
If you allocated an object inside viewDidLoad and didn't release it after adding it to the subview, then your object isn't released when view1 is unloaded, but, view1 is still unloaded.
viewDidLoad will run again and your code will run again, but now you've got two instantiations of your object instead of one; one object will be in nowhereland with the previously-unloaded view and the new object will be for the currently visible view. Rinse, lather, and repeat and you find your application crashing from memory leaks.
In this example, if the given block of code is volatile and has a chance to be executed again (whether because of memory or an unloaded view), I would remove [imgView release]; from dealloc and leave it after addSubView.
Here is a link on basic retain/release concepts:
http://www.otierney.net/objective-c.html#retain
Yes, that code has problems. It releases the imgView too early which could potentially cause crashes in rare circumstances stores an object in an instance variable without retaining it, and it's just generally going about memory management the wrong way.
One correct way to do this would be:
#interface SomeViewController : UIViewController
{
UIImageView *imgView;
}
#property (nonatomic, retain) UIImageView *imgView;
And in the implementation;
#synthesize imgView;
Somewhere in the module:
//Create a new image view object and store it in a local variable (retain count 1)
UIImageView *newImgView = [[UIImageView alloc] initWithFrame:self.view.bounds];
newImgView.image = [UIImage imageNamed:#"someimage.png"];
//Use our property to store our new image view as an instance variable,
//if an old value of imgView exists, it will be released by generated method,
//and our newImgView gets retained (retain count 2)
self.imgView = newImgView;
//Release local variable, since the new UIImageView is safely stored in the
//imgView instance variable. (retain count 1)
[newImgView release];
//Add the new imgView to main view, it's retain count will be incremented,
//and the UIImageView will remain in memory until it is released by both the
//main view and this controller. (retain count 2)
[self.view addSubview:self.imgView];
And the dealloc remains the same:
- (void) dealloc
{
[imgView release];
[super dealloc];
}