Adding another view controller's view as subview - ios

I am trying to get a popup effect and want to design the popup view in another view controller so i can use the xib to do it.
When i used the presentViewController or pushViewController and set the background to transparent, i end up seeing the Window's background color.
I tried this code to add subview to the navigation controller's view so that i can have the Info view cover the entire screen with a transparent background. I also have tab bar to cover up as well.
InfoVC *vc = [[InfoVC alloc] initWithNibName:#"InfoVC" bundle:nil];
[self.navigationController.view addSubview:vc.view];
My problem is inside my InfoVC when i try to dismiss it, the app will crash with some EXC_BAD_ACCESS message:
[self.view removeFromSuperview];
EDIT:
I found a way to stop it crashing but setting the InfoVC as a property in the MainVC. I think the reason for crash is when i call "self.view" in the action inside the InfoVC, it doesn't know that self is the InfoVC inside MainVC.

InfoVC *vc = [[InfoVC alloc] initWithNibName:#"InfoVC" bundle:nil];
[self.navigationController.view addSubview:vc.view];
No no no no. Never never do that.
There is an elaborate dance that you must traverse in order to put a view controller's view inside another view controller's view (or remove it afterwards) if it doesn't come with built-in facilities for doing this (the way a UISplitViewController does, or the way a navigation controller manages the views of the view controllers that are pushed and popped within it).
Read up on customer container controllers. One of the examples from my book is here:
https://github.com/mattneub/Programming-iOS-Book-Examples/blob/master/ch19p556containerController/p476containerController/ViewController.m

Shouldn't you be using the following to remove the view from its superview?
[vc.view removeFromSuperview];

You can never have a UIView remove it's subviews, the subviews themselves must remove themselves from it's superview. You can easily loop through subviews and have them removed like so
for (UIView *view in vc.view.subviews) {
[view removeFromSuperview];
}
Docs for reference:
http://developer.apple.com/library/ios/#documentation/uikit/reference/uiview_class/uiview/uiview.html

After a "modally" presented view controller has appeared the views under the now presented view controller will be removed; this saves memory, and eases rendering. In your case, though, you also end up seeing the window behind the "modally" presented view.
The natural, and seemingly logical, next step is to simply take one view controller's view and cram it into another. However, as you have discovered, this is problematic. With the newly inserted view safely retained by the view hierarchy it is safe, but the new view controller is not so lucky, it is quickly deallocated. So when this new view tries to contact its controller you will get an EXC_BAD_ACCESS and crash. One workaround, again as you have found, is to simply have the original view controller keep a strong reference to the new view controller. And this can work... badly. There's still a good chance you will get an UIViewControllerHierarchyInconsistencyException.
Of course if you simply want to add a small view you create in IB you don't need to use a view controller as the "File's Owner" and there are many examples of creating an instance of a view from a xib file.
The more interesting question here is, "How would/does apple do it?" Apple consistently says that a view controller is the correct controller for an encapsulated unit of work. For example, their TWTweetComposeViewController, you present it, and it seems to float. How?
The first way of accomplishing this that comes to my mind is to have a clear background that isn't clear. That is, create an image of the screen before the presented view controller appears and set that as the background before the presenting view is removed. So for example(Explanation to follow):
QuickSheetViewController.xib
QuickSheetViewController.h
#import <UIKit/UIKit.h>
#interface QuickSheetViewController : UIViewController
- (IBAction)dismissButtonPressed:(id)sender;
#end
QuickSheetViewController.m
#import "QuickSheetViewController.h"
#import <QuartzCore/QuartzCore.h>
#implementation QuickSheetViewController {
UIImage *_backgroundImage;
}
-(void)renderAndSaveBackgroundImageFromVC:(UIViewController *)vc{
UIGraphicsBeginImageContext(vc.view.bounds.size);
[vc.view.layer renderInContext:UIGraphicsGetCurrentContext()];
_backgroundImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
}
-(void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
// save an image of the current view, and set our background to clear so we can see the slide-in.
[self renderAndSaveBackgroundImageFromVC:self.presentingViewController];
self.view.backgroundColor = [UIColor clearColor];
}
-(void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
// Time to use our saved background image.
self.view.backgroundColor = [UIColor colorWithPatternImage:_backgroundImage];
}
-(void)viewWillDisappear:(BOOL)animated{
[super viewWillDisappear:animated];
// Set our background to clear so we can see the slide-out.
self.view.backgroundColor = [UIColor clearColor];
}
- (IBAction)dismissButtonPressed:(id)sender {
[self dismissViewControllerAnimated:YES completion:nil];
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation{
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
#end
The majority of this example hinges upon the renderAndSaveBackgroundImageFromVC: method. In which, we create a graphics context render the view we are about to cover into it, and then create a UIImage to later (in viewDidAppear) use as a background.
Now simply use it like:
QuickSheetViewController *newVC = [[QuickSheetViewController alloc] initWithNibName:nil bundle:nil];
[self presentViewController:newVC animated:YES completion:nil];
You will see through the background just long enough for the animation to happen, then we use our saved image to hide the removal of the presenting view.

Related

UIbutton crashing app with no error other than (lldb)

I am working on view A (createExerciseViewController) that adds view B (createRoutinePopupViewController) after clicking a UIButton.
This part works fine and the view is added fine.
Then inside view B (createRoutinePopupViewController) I have another UIButton. When I click this UIButton then the app crashes and all i get as an error is (lldb) and the NSLog is not executed.
but then sometimes and only sometimes it all gets executed fine after several crashes...
I am quite new to the iOS dev world and I have no idea what I could be doing wrong.
All UIButton method are strong
Does anyone know why this could be happening?
I think the issue could be in how i am inserting the subview and handling the whole subview??
A ---- createExerciseViewController.m
#import "createExerciseViewController.h"
#import "createExercisePopupViewController.h"
#import "createRoutinePopupViewController.h"
// ....more code
- (IBAction)createRoutine:(id)sender {
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Storyboard" bundle:nil];
[self.view addSubview:[[storyboard instantiateViewControllerWithIdentifier:#"createRoutinePopupView"] view]];
}
this is UIViewController
B ---- createRoutinePopupViewController.m
#import "createRoutinePopupViewController.h"
#import "createExerciseViewController.h"
#import "User.h"
#import "Routine.h"
- (IBAction)createRoutine:(UIButton *)sender {
NSLog(#"Please dont crash");
}
You shouldn't be creating view controllers just to add their views to another view controller's view willy-nilly. You need to tell the system that you're moving views from one controller to another, so that it can do its housekeeping. If you don't do this, one view controller ends up owning a view that's being presented by another view controller, so events and touches etc get confused. This may be what's causing the crash.
iOS now provides a 'container view controller' mechanism to manage this situation, whereby you tell the system that you're moving a view from one controller to another.
From Apple's doc:
The goal of implementing a container is to be able to add another view
controller’s view (and associated view hierarchy) as a subtree in your
container’s view hierarchy. The child remains responsible for its own
view hierarchy, save for where the container decides to place it
onscreen. When you add the child’s view, you need to ensure that
events continue to be distributed to both view controllers. You do
this by explicitly associating the new view controller as a child of
the container.
In practice, it's simpler than it sounds. Try something like this in createExerciseViewController.m:
- (IBAction)createRoutine:(id)sender {
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Storyboard" bundle:nil];
CreateRoutinePopupViewController* popupController = [storyboard instantiateViewControllerWithIdentifier:#"createRoutinePopupView"];
//Tell the operating system the CreateRoutine view controller
//is becoming a child:
[self addChildViewController:popupController];
//add the target frame to self's view:
[self.view addSubview:popupController.view];
//Tell the operating system the view controller has moved:
[popupController didMoveToParentViewController:self];
}
Try this:
CreateRoutinePopupViewController *popUp = [[storyboard instantiateViewControllerWithIdentifier:#"createRoutinePopupView"];
[self presentModalViewController:popUp Animated:YES];

uiview duplicated when switching view controller

I have 2 ViewControllers that I use App delegate to switch them according to user interaction.
in AppDelegate.m I have:
- (void) switchViews
{
if (_viewController.view.superview == nil) {
[_window addSubview:_viewController.view];
[_window bringSubviewToFront:_viewController.view];
[viewController2.view removeFromSuperview];
} else
{
[_window addSubview:_viewController2.view];
[_window bringSubviewToFront:_viewController2.view];
[_viewController.view removeFromSuperview];
}
}
_viewController is for main view and _viewController2 is for glview(I am using isgl3d). The switch works but everytime I switch back to glview, I see duplicated view on top, which I suspect even main view is duplicated too.
Any idea how can I remove the view entirely so that I don't have this issue? Thanks!
You shouldn't be adding and removing the views like this, just change which controller is the root view controller of the window. Doing that make the new controller's view a subview of the window, and removes the old controller's view.
if ([self.window.rootViewController isEqual: _viewController]) {
self.window.rootViewController = viewController2;
}else{
self.window.rootViewController = viewController;
I found out how to do this after watching Stanford Coding Together:IOS.
Some critical info of VC that I am not aware of:
Everytime VC is instantiate, viewDidLoad is called once to setup all the important stuff like outlets and such. Then viewWillAppear and viewWillDisappear will be called for in between view swapping. Because it is called just a moment before view is shown to user, all the geometry setting like view orientation and size is set here.
so what I do is:
I addSubview in viewDidLoad, the do all the running setup in viewWillappear and viewWillDisappear.
one more note: view will remain there as long as the app still running.
anyway Thanks rdelmar for helping.

iOS uiview and uiviewcontroller

Hy
i have two classes uiviewcontroller and uiview. I have one view controller. Inside i have uiview. Inside uiview i have textfield and when i write a text and click done i need to refresh uiviewcontroller.
I tried with this in uiview class:
-(IBAction)textFieldReturn:(id)sender
{
ViewController *vc = [[ViewController alloc] init];
[vc viewDidLoad];
}
i need refresh the same as you click the button and open viewcontroller.
I am guessing you mean that you want to "refresh" the view, not the view controller. To do that simply call [self setNeedsDisplay] from the view, or [self.view setNeedsDisplay] from the view controller. Also make sure that the textfield is a subview of the uiview. Either do that in the nib file or in code by calling [self addSubview: (textfield here)].
Also, if you want to access the view controller from the view you will need to create an IBOutlet, simply allocating a new ViewController object within the view does not mean that the created view controller controls the view. Hopefully that makes sense. I'd recommend going through some ios starter tutorials as well. Just google that there are a lot.

How do I remove a no longer needed view controller?

I am using my app delegate to transition between view controllers. When the delegate decides it no longer needs the view controller, based on messages from the server, it needs to remove the current view and replace it with another one. Currently my code looks like the following:
- (void) showFight: (NSNotification*) notification
{
if(self.window.rootViewController != self.fightViewController)
{
NSDictionary* dict = [notification userInfo];
FightViewController *fightView = [[FightViewController alloc]
initWithNibName:#"FightViewController" bundle:nil];
fightView.userId = _userId;
self.fightViewController = fightView;
[fightView release];
[self.radarViewController.view removeFromSuperview]; // Doesn't work.
self.window.rootViewController = self.fightViewController;
[self.fightViewController showMonster:dict];
}
}
I know my view controller isn't being removed because I can hear sound effects from it in the background.
I want to completely destroy the view controller, as I only want one view controller in memory at any time. I plan to create the view controller each time from scratch, as shown in the code above. Am I doing this improperly?
The problem here seems to be that you are not releasing the view controller. Think about what actually happens in your code at:
[self.radarViewController.view removeFromSuperview];
You remove the view from its super view. The view controller still exists, and what it does is control what should be shown on the view, and in your case apparently playing sound.
Put in an easy way: The view controller is an object. It has a child, the view. That's another object. You remove the view from another view, but the object controlling the removed view still lives (and actually, so does the view object).
If you want to kill the entire view controller, call this after removing the view from its superview:
[self.radarViewController release];
Or, if the view is a retain property (which i assume by looking at your code) you can also use:
self.radarViewContoller = nil;
which automatically releases for you in the synthesized setter.
When the view controller is released, its reference count is subtracted by one. If the reference count reaches zero, then the controller will be deallocated.
So far I understand your problem is to add the new ViewController on server notify and change the current view with new View. First of all you've to add the view controller just like below because the reference won't help to update the view.
[self.window.rootViewController.view addSubview: self.fightViewController.view]
In my opinion you need to tag your Controllers and check before adding the controller that if it's already exist in the memory, otherwise the pool of object will leak. Just Say No to Memory Leaks!!
- (void) showFight: (NSNotification*) notification
{
UIView *fightView = (UIView *)[self.window.rootViewController.view viewWithTag: FIGHT_VIEW_TAG];
if (self.window.rootViewController.view.tag != fightView.tag) {
NSDictionary* dict = [notification userInfo];
FightViewController *fightView = [[FightViewController alloc]
initWithNibName:#"FightViewController" bundle:[NSBundle mainBundle]];
//Remove the current view.
[self.window.rootViewController.view removeFromSuperview]; // If you're adding the fighting View in the root View, then why are you trying to remove current view through radar controller which has already added in the window (root view).
fightView.userId = _userId;
[fightView setTag: FIGHT_VIEW_TAG];
[self.window.rootViewController.view addSubView: self.fightViewController.view];
[self.fightViewController showMonster:dict];
[fightView release];
}
}
You don't need to take them as global until your requirements are different.

UIModalPresentationFormSheet, dim is missing

I am trying to display a modal viewController in an iPad app using the UIModalPresentationFormSheet view style. I am looking to produce something similar to the Mail app's new message UI/animation.
There are two things that are not behaving correctly:
The modal viewController that is presented always animates to y=0, i.e. to the very top of the
view and not some pixels below the status bar as it does in the mail app.
The documentation says:
UIModalPresentationFormSheet The width
and height of the presented view are
smaller than those of the screen and
the view is centered on the screen. If
the device is in a landscape
orientation and the keyboard is
visible, the position of the view is
adjusted upward so that the view
remains visible. All uncovered areas
are dimmed to prevent the user from
interacting with them.
However, in my case there is no dimming and I can still interact with the parentView below the modalViewController.
The controller that presents the modalView I do this:
AddNewItemViewController *newItemViewController = [[AddNewItemViewController alloc] initWithNibName:#"AddNewItemViewController" bundle:nil];
[self presentModalViewController:newItemViewController animated:YES];
[newItemViewController release];
In the viewController being presented I do this:
- (void)viewDidLoad {
[nameField becomeFirstResponder];
[self setModalTransitionStyle:UIModalTransitionStyleCoverVertical];
[self setModalPresentationStyle:UIModalPresentationFormSheet];
[super viewDidLoad];
}
I hope someone can help me out.
Is there some other properties I need to set on the parent and modalViewController?
Is the viewDidLoad not the right place to do this setup?
Thanks in advance:)
You set the transition and presentation styles when you create the modal view, before you call presentModalViewController. Remember, the view that creates the modal view 'owns' that object. You want the owner to set these properties because you might implement this modal view elsewhere in the app and want different transition or presentation styles. This way, you set it each time as appropriate.
AddNewItemViewController *newItemViewController = [[AddNewItemViewController alloc] initWithNibName:#"AddNewItemViewController" bundle:nil];
newItemViewController.modalPresentationStyle = UIModalPresentationFormSheet;
[self presentModalViewController:newItemViewController animated:YES];
[newItemViewController release];
You're right in calling becomeFirstResponder in viewDidLoad.

Resources