UIViewController *viewController = [[UIViewController alloc] init];
UIView *view1 = [[UIView alloc] init];
[viewController.view addSubview:view1];
[view1 release];
if I want release viewController;
[viewController release];
do I need manual release view1 before release viewController?
UIView *view = (UIView *)[[viewController.view subviews] objectAtIndex:0];
[view release];
[viewController release];
should I do this? or just release viewController?
No, you dont have to do that. Just release viewController and it will release all of its subviews internally. Rest will be taken care by framework.
If you are not using ARC, your code will look like this,
UIViewController *viewController = [[UIViewController alloc] init];
UIView *view1 = [[UIView alloc] init];
[viewController.view addSubview:view1];
[view1 release];
[viewController release];
Since you have allocated both viewController and view1 once, you have to release it once as shown above. You dont have to do a release again since you are not doing any retain on this after that.
If you do this,
UIView *view = (UIView *)[[viewController.view subviews] objectAtIndex:0];
[view release];
It will mostly result in a crash when viewController is released since you are releasing it twice and viewController's subviews are also getting released internally.
Here once thing you have to note is that, addSubview retains view1 as mentioned in Apple documentation.
The view to be added. This view is retained by the receiver.
After being added, this view appears on top of any other subviews.
This will be released once viewController is released and you dont have to manually release it since you dont own it.
This is the correct way.
UIViewController *viewController = [[UIViewController alloc] init];
UIView *view1 = [[UIView alloc] init];
[viewController.view addSubview:view1];
[view1 release];
[viewController release];
When you add a view as subview it'll be retained by the viewcontroller.
addSubview:
Adds a view to the end of the receiver’s list of subviews.
- (void)addSubview:(UIView *)view
Parameters
view
The view to be added. This view is retained by the receiver. After being added, this view appears on top of any other subviews.
Discussion
This method retains view and sets its next responder to the receiver,
which is its new superview.
Views can have only one superview. If view already has a superview and
that view is not the receiver, this method removes the previous
superview before making the receiver its new superview.
Reference UIView
Important: A view controller is the sole owner of its view and any
subviews it creates. It is responsible for creating those views and
for relinquishing ownership of them at the appropriate times such as
when the view controller itself is released
Reference : UIViewController Class
UIView *view = (UIView *)[[viewController.view subviews] objectAtIndex:0];
[view release];
It'll surely crash when you call release on viewController.
Related
I've been struggling with a bug, and I found a work-around, but I'd like to understand what is exactly going on. it has something to do with UIButton target actions misfiring depending on different subview hierarchies, inside a subclass.
Brief summary: I have a subclass of NSObject with a UIView property object, a UIButton attached to it, and a target added to the button calling a function inside the subclass. Inside the main ViewController, I init the subclass and add its view to the view stack, click the button, and it throws me to main.mm with the error - EXC_BAD_ACCESS, gives me little feedback. so the hierarchy looks like this:
-CustomClass
--UIView <-this is added as a subview to the View Controller
---UIButton (onRelease calling a function)
so I fixed it by changing the custom class to be a subclass of UIView instead of NSObject, then add its #property UIView to be a subview of the custom class (and the button is still attached to the subview), and then in the main View Controller, I add the custom class itself as a subview, not the class's subview property object. then the button successfully calls the function. so the new arrangement looks like this:
-CustomClass (now UIView) <-this is added as a subview to the View Controller
--UIView <-this is added as a subview to CustomClass
---UIButton (onRelease calling a function)
then, i realized i can just keep the CustomClass a subclass of UIView for both instances, the problem persists with the original setup if everything else is unchanged.
okay, more detail, here's code:
CustomClass:
.h
#interface Temp : UIView
#property UIView *subview;
#property UIButton *but;
#end
.m
-(id) init{
self = [super initWithFrame:[[UIScreen mainScreen] bounds]];
if(self){
_subview = [[UIView alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
//[self addSubview:_subview]; // FOR THE FIX
_but = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[_but setTitle:#"OKAY" forState:UIControlStateNormal];
[_but setBackgroundColor:[UIColor blackColor]];
[_but setFrame:CGRectMake(50, 50, 200, 200)];
[_subview addSubview:_but];
[_but addTarget:self action:#selector(pageTurn) forControlEvents:UIControlEventTouchUpInside];
}
return self;
}
-(void) pageTurn{
NSLog(#"WORKS");
}
inside view controller:
Temp *temp = [[Temp alloc] init];
[self.view addSubview:temp.subview];
//[self.view addSubview:temp]; // FOR THE FIX, instead of above line
Who's holding onto temp?
If temp isn't referenced by anyone then it's released. At that point the target is zombie and of course you will crash. temp.subview is being held by self.view.
In the second setup, adding temp as a subview of self.view keeps a reference to it.
You can fix this by adding a Temp * property in the view controller.
self.temp = [[Temp alloc] init];
[self.view addSubview:self.temp.subview];
You're messing with all kinds of view hierarchy stuff you don't need to, which is likely the cause of the problem. I created a Test UIView subclass that had a UIButton instance variable that I added as a subview in the Test object, there's no need to add another view as a subview and then add the button to the subview, and then in your view controller add the button's subview to the view - it's WAY more complicated than it needs to be.
In a nutshell - Create the Temp UIView, add the button as a subview, then in your view controller class add the Temp UIView as a subview. Simple as that, here is the code:
- (id)init {
self = [super initWithFrame:[[UIScreen mainScreen] bounds]];
if(self){
_but = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[_but setTitle:#"OKAY" forState:UIControlStateNormal];
[_but setBackgroundColor:[UIColor blackColor]];
[_but setFrame:CGRectMake(100, 100, 200, 200)];
[_but addTarget:self action:#selector(pageTurn) forControlEvents:UIControlEventTouchUpInside];
[self addSubview:_but];
}
return self;
}
- (void)pageTurn {
NSLog(#"WORKS");
}
Then added an instance to my view controller:
Test *temp = [[Test alloc] init];
temp.backgroundColor = [UIColor redColor];
[self.view addSubview:temp];
This was the result:
I am working on creating an iOS app for iPhone and I am running into a problem with the UITabBarController I am using. Whenever I click a tab that is in the tab bar, the program crashes. I am not using storyboard or anything of the sort and have opted to do everything through code. That being said, here is the code that sets up the tabs.
- (void) loadView
{
// create main view
UIView *contentView = [[UIView alloc] initWithFrame: [[UIScreen mainScreen] applicationFrame]];
contentView.backgroundColor = [UIColor grayColor];
self.view = contentView;
// now create the tab pages
StatusViewController *status = [[StatusViewController alloc] initWithNibName: nil bundle: nil];
OperationsViewController *operations = [[OperationsViewController alloc] initWithNibName: nil bundle: nil];
self.statusTab = status;
self.operationsTab = operations;
status.title = #"Status";
operations.title = #"Operations";
// create the tab bar controller and add all tabs
UITabBarController *tabbar = [[UITabBarController alloc] init];
tabbar.view.frame = CGRectMake(0, 0, 320, 460);
NSMutableArray *tabs = #[status, operations];
[tabbar setViewControllers:tabs];
[self.view addSubview: tabbar.view];
}
The tabs do show up correctly along the bottom of the app, and the content for the first tab is loaded correctly by default, by clicking any tab causes it to crash. When I do crash I am not getting a stack trace and XCode just higlights...
#autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([MyAppDelegate class]));
}
Which is not very helpful (if somebody can comment how to get a stack tract in Xcode 5.1.1 I will be more than happy to update this post with additional information).
The classes that I have created, StatusViewController and OperationsViewController are both just subclasses of UIViewController where I added custom logic in loadView.
I saw other questions that seemed similar here on StackOverflow, but they were either using interfacebuilder/storyboard, or something else was different enough I felt the need to ask. Look forward to hearing any help anybody can offer me.
Thanks!
This is due to memory issues (often called a zombie). You aren't retaining the UITabViewController, so the views that should be managed by a UITabViewController are causing the crash.
When you use a view managed by another UIViewController, you can add them to the childViewControllers array of the parent UIViewController.
[self addChildViewController:tabBar];
[self.view addSubview: tabbar.view];
[tabBar didMoveToParentViewController:self];
Apple refers to this feature as "Custom Container View Controllers" https://developer.apple.com/library/ios/featuredarticles/ViewControllerPGforiPhoneOS/CreatingCustomContainerViewControllers/CreatingCustomContainerViewControllers.html
While researching container view controllers in an attempt to refactor some code, I came upon something that I do not understand.
The Apple documentation tells me that in order for child view controllers to get their appearance methods called they must be added as children to a parent view controller using addChildViewController:
This puzzles me as my code does not use any of the container view controller methods and yet all of my child view controllers are getting the viewWillAppear: message.
I've boiled the code down to this simple example, where you will see "ChildViewController:viewWillAppear:" in the debug log despite any calls to addChildViewController:
#interface ChildViewController : UIViewController
#end
#implementation ChildViewController
- (void)loadView {
self.view = [[UIView alloc] initWithFrame:CGRectMake(10.0f, 10.0f, 250.0f, 250.0f)];
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
NSLog(#"ChildViewController:viewWillAppear:");
}
#end
#interface RootViewController : UIViewController
#property (strong) ChildViewController *cvc;
#end
#implementation RootViewController
#synthesize cvc;
- (void)loadView {
UIView *view = [[UIView alloc] initWithFrame:CGRectMake(10.0f, 10.0f, 500.0f, 500.0f)];
cvc = [[ChildViewController alloc] init];
[view addSubview:[cvc view]];
self.view = view;
}
#end
#implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.window.rootViewController = [[RootViewController alloc] init];
[self.window makeKeyAndVisible];
return YES;
}
#end
Why does this work?
The process of calling addSubview is what will result in the view appearing and thus resulting in the loadView, viewDidLoad, viewWillAppear, viewDidAppear, etc., calls. The addChildViewController (and the call to didMoveToParentViewController that you should also do) does not affect this.
You call addChildViewController to make sure your controller hierarchy stays in sync with the view hierarchy. If you don't do this, you won't get certain events getting passed to your child controller (such as rotation events). Also, by doing addChildViewController, your controller will be retained for you, without you needing to maintain your own property to keep track of the child controllers.
If you see WWDC 2011 - #201 Implementing UIViewController Containment, it will talk about the importance of keeping the view hierarchy and the controller hierarchy synchronized.
I have an UINavigationController which the user navigates with.
When pushing a specific UIViewController onto the navigation stack, a "settings" button appear in the navigationBar. When the user clicks this button I would like to flip the current view/controller, i.e. everything on screen, including the navigationBar, over to a settings view.
So I have a SettingsViewController which I would like to flip to from my CurrentViewController that lives on a navigationController stack.
I get all kinds of strange behavior trying to do this, the UIViews belonging to the SettingsViewController will start to animate, sliding into place, the navigationButtons moves around, nothing acts as I would think.
-(void)settingsHandler {
SettingViewController *settingsView = [[SettingViewController alloc] init];
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:1.0];
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromRight
forView:self.navigationController.view
cache:YES];
[self.navigationController.view addSubview:settingsView.view];
[UIView commitAnimations];
}
The above results in the views flipping correctly, but the subviews of the SettingsViewController are all positioned in (0, 0) and after the transition, they 'snap' into place?
Is it because I instantiate and add my subviews in viewDidLoad, like this?
- (void)viewDidLoad {
UIImageView *imageBg = [[UIImageView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 320.0f, 460.0f)];
[imageBg setImage:[UIImage imageNamed:#"background.png"]];
[self.view addSubview:imageBg];
[imageBg release];
SettingsSubview *switchView = [[SettingsSubview alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 320.0f, 460.0f)];
[self.view addSubview:switchView];
[switchView release];
[super viewDidLoad];
}
1: How should I correctly do the "flip" transition, from within the UIViewController in the UINavigationController, to a new UIViewController and subsequently from the new UIViewController and back to the "original" UIViewController residing on the UINavigationControllers stack?
2: Should I use a different approach, than the "viewDidLoad" method, when instantiating and adding subviews to a UIViewController?
-question 2 is more of a "best practice" thing. I have seen different ways
of doing it and I am having trouble either finding or understanding the life-cycle documentation and the different threads and posts on the subject. I am missing the "best practice" examples.
Thank You very much for any help given:)
If you want to create your view hierarchy programmatically, the place to do it is in -loadView. To do so you must create the view yourself, add all of its subviews, and then assign it to the view property, like this:
- (void)loadView {
UIView *containerView = [[UIView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 320.0f, 460.0f)];
UIImageView *imageBg = [[UIImageView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 320.0f, 460.0f)];
[imageBg setImage:[UIImage imageNamed:#"background.png"]];
[containerView addSubview:imageBg];
[imageBg release];
SettingsSubview *switchView = [[SettingsSubview alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 320.0f, 460.0f)];
[containerView addSubview:switchView];
[switchView release];
self.view = containerView;
[containerView release];
}
It helps to understand the context in which this method gets called, and how it behaves by default. The first time the view property of a UIViewController is accessed, the default getter method calls -loadView to lazy-load the view. The default implementation of -loadView loads the view from a nib if one was specified. Otherwise it creates a plain UIView object and sets that as the controller's view. By overriding this method, you can ensure that your view's hierarchy will be fully formed the first time it is accessed.
-viewDidLoad should be used for any subsequent setup that needs to occur after the view hierarchy is fully loaded. This method will get called whether the view is loaded from a nib or constructed programmatically in loadView.
I am working on an iPhone app but found that I require another view / window to get the user to input and save data / information there.
How do I add another view? Do I add it in interface builder and then link it in the main app delegate or will it have its own .h and .m files.
I selected a window view app to start with, do I need to start over with a flip side view app or can this just be added in anyway if I have the correct code there.
manny thanks
Carl
The Window app is perfect for you. In your AppDelegate file, you should have a section like this:
- (void)applicationDidFinishLaunching:(UIApplication *)application {
//instantiate the venue view controller object
YourViewController *yourViewController = [[YourViewController alloc] initWithNibName:#"YourView" bundle:[NSBundle mainBundle]];
// Configure and show the window
[window addSubview:[yourViewController view]];
[window makeKeyAndVisible];
}
This is the part of the code that declares, allocates and adds your custom view to the window. You have a couple choices for how to add the second view. You can either add it in place of this one, or add it after this one using a Navigation Controller. To add the navigation controller, change the above method to look like this:
- (void)applicationDidFinishLaunching:(UIApplication *)application {
//instantiate the venue view controller object
YourViewController *yourViewController = [[YourViewController alloc] initWithNibName:#"YourView" bundle:[NSBundle mainBundle]];
UINavigationController *yourViewControllerWrapper = [[UINavigationController alloc] initWithRootViewController: yourViewController];
// Configure and show the window
[window addSubview:[yourViewControllerWrapper view]];
[window makeKeyAndVisible];
}
There, we create your custom view, then wrap it in a navigation controller. The navigation controller is what gets added to the window. Next the code to switch to the second view would look like this, assuming you switch views on a button press:
-(IBAction)switchViewController{
MySecondViewController *secondViewController = [[MySecondViewController alloc] init];
[self.navigationController pushViewController:secondViewController];
}
Of course, you should replace the line
MySecondViewController *secondViewController = [[MySecondViewController alloc] init];
with the proper way of instantiating your second view controller. This could be from a nib file like above, or programmatically.
As far as creating the view files, you should create a nib in Interface builder for the layout of everything, then create a .h and .m file for the ViewController code itself.
you can also display new frame instead of new view. It is easier sometimes, as you don;t have to pass parameters - you are in one class:
CGRect frame = okresView.frame;
frame.origin.x = frame.size.width;
if ( [okresView superview] == nil )
{
[self.view addSubview:okresView];
}
okresView.frame = frame;
[okresDataTableView reloadData]; // przeładowanie tabeli na subwidoku
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:.5];
frame.origin.x = 0;
okresView.frame = frame;
[UIView commitAnimations];
if you want new subview, you can use a few methods - just download few applications from XCode help and check how they do this. Nice example are in 'Elements' and 'UICatalog' application where you have flipped view and other examples.
// Create and push another view controller.
UIViewController *myViewController = [[UIViewController alloc] init];
myViewController.title = #"My First View";
myViewController.view.backgroundColor = [UIColor redColor];
//to push the UIView.
[self.navigationController pushViewController:myViewController animated:YES];