I have a view that acts as a background (light grey 0.5 alpha) for for another custom alert view.
When the user taps my OK button on the custom alert, i want to hide the custom alert and the background view also.
Both views are subviews of the same superview...
I do this in the buttonTapped: method to hide the views, and it works for the first attempt, but from the second time onwards, the background views never dismiss... the alerts hide correctly every time.
[UIView animateWithDuration:0.5f animations:^{
self.view.alpha=0.0f; //hide alert
[self.view.superview viewWithTag:1].alpha=0.0f; //hide background
}];
They are added as subviews, as follows:
ResultDialogController *dialogController = [[[ResultDialogController alloc] initWithNibName:#"ResultDialogController_" bundle:nil] retain];
ResultBackgroundViewController *bgViewController = [[[ResultBackgroundViewController alloc] initWithNibName:#"ResultView" bundle:nil] retain];
dialogController.view.alpha=0;
bgViewController.view.alpha=0;
bgViewController.view.tag=1;
[UIView animateWithDuration:0.5f animations:^{
bgViewController.view.alpha=0.5f;
dialogController.view.alpha=1.0f;
}];
[self.view addSubview:bgViewController.view];
[self.view addSubview:dialogController.view];
[dialogController release];
[bgViewController release];
How can i always dismiss the background view?
Thanks
You don't seem to remove the views, you are just making the invisible by setting the alpha to zero. So every time you call your second code sample you will add a new version of the background view and the dialog view to self.view. At the second call you will have two background views, both with tag = 1 and you are getting your first background view from the call to [self.view.superview viewWithTag:1] which is why your newly added background view does not get invisible.
But that is not all, you also have a memory leak for ResultDialogController and ResultBackgroundViewController. The call to retain is not necessary when you are calling initWithNibName:bundle:. Perhaps you are doing this because you some crash when you released the controllers?
What you should do is to create ivars and properties for your controllers.
#property (nonatomic, retain) ResultDialogController *resultController;
#property (nonatomic, retain) ResultBackgroundController *backgroundController;
Then when showing the controllers you can do something like:
ResultDialogController *dialogController = [[ResultDialogController alloc] initWithNibName:#"ResultDialogController_" bundle:nil];
self.dialogController = dialogController;
ResultBackgroundViewController *bgViewController = [[ResultBackgroundViewController alloc] initWithNibName:#"ResultView" bundle:nil];
self.backgroundController = bgViewController;
// do the same as before
Then in buttonTapped: you do:
[UIView animateWithDuration:0.5f
animations: ^{
self.dialogController.view.alpha = 0;
self.backgroundController.view.alpha = 0;
}
completion: ^(BOOL finished){
[self.dialogController.view removeFromSuperview];
[self.backgroundController.view removeFromSuperview];
}
];
And to top it off, don't forget to release the controller ivars in dealloc.
You can hide them by setting HIDE property for the views to be true.
Related
I am trying to make a custom login view popup like an alert view. I am simulating the alertview popup with the following function. This function is found in the viewDidload in mine loginViewController.m
-(void)initialDelayEnded {
self.view.transform = CGAffineTransformScale(CGAffineTransformIdentity, 0.001, 0.001);
self.view.alpha = 1.0;
[UIView animateWithDuration:1.0/1.5 animations:^{
self.view.transform = CGAffineTransformScale(CGAffineTransformIdentity, 1.1, 1.1);
}completion:^(BOOL complete){
[UIView animateWithDuration:1.0/2 animations:^{
self.view.transform = CGAffineTransformScale(CGAffineTransformIdentity, 0.9, 0.9);
}completion:^(BOOL complete){
[UIView animateWithDuration:1.0/2 animations:^{
self.view.transform = CGAffineTransformIdentity;
}];
}];
}];
}
- (void)viewDidLoad
{
[self initialDelayEnded];
[super viewDidLoad];
}
And I'm calling in my firstViewController my loginViewController in the following way.
LoginViewController *login = [[LoginViewController alloc]initWithNibName:#"LoginViewController" bundle:NULL];
[self presentViewController:login animated:YES completion:NULL];
But it crashes with the following error.
'UIViewControllerHierarchyInconsistency', reason: 'A view can only be associated with at most one view controller at a time! View <UIView: 0x8674bf0; frame = (0 20; 320 460); autoresize = W+H; layer = <CALayer: 0x8670620>> is associated with <LoginViewController: 0x868a7d0>. Clear this association before associating this view with <LoginViewController: 0x8451e70>.
Can somebody help me?
Thanks in advance !
Here:
[self presentViewController:login animated:YES completion:NULL];
you are presenting a viewController by Self which i guess is a viewcontroller itself.
instead you should use:
[self presentModalViewController:login animated:YES];
if you want to present your viewcontroller rather pushing it on the navigation stack.
In which class you are using this code.
Unless you have special reason, please keep [super viewDidLoad]; call to be done as early as possible within your local implementation. Meaning: call [self initialDelayEnded]; AFTER [super viewDidLoad];
Please make sure your .xib file named LoginViewController has only one File's Owner in Placeholders and NO ViewController object in Objects panels. And make sure File's Owners custom class is LoginViewController. Can you please upload the screenshot of your .xib, specifically showing Document Outline? It will be much easier to figure what could have been wrong
Did you check your LoginViewController xib? Is there a possibility you are mapping the same view with more than 1 view controllers there?
The error message implies that there are two instances of 'LogInViewController'. Are you using story boards? If so you could add the view controller to the main storyboard file (rather than a separate nib), give it a tag/identifier (but don't connect it to the other VCs). You can then grab the storyboard created instance and present that. Like so:
//Get the stroyboard
UIStoryBoard *mainStoryBoard = [UIStoryBoard storyboardWithName:<STORYBOARD_NAME> bundle:nil];
//Get the VC
LogInViewController *login = [mainStoryBoard instantiateViewControllerWithIdentifier:<VIEWCONTROLLER_TAG>];
//Present
[self presentModalViewController:login animated:YES];
Also, you should not try to present other view controllers in viewDidLoad:, it will not work. Move the code to present the new controller into viewDidAppear:
I solved my apps login problem in the following way >
Used Alert View to show up in AppDelegate on applicationDidBecomeActive
Add a UIView Controller with XIB to project named LoginSubView. Kept it blank and added Tag to it say 99
Loaded the LoginSubView controller before the alert box shows up.
AFLoginViewController *LoginSubView = [[AFLoginViewController alloc] init];
[_window addSubview:LoginSubView.view];
[_window makeKeyAndVisible];
UIAlertView * alert = [[UIAlertView alloc] initWithTitle:#"Alert" message:#"This is an example alert!" delegate:self cancelButtonTitle:#"Cancel" otherButtonTitles:#"Login", nil];
alert.alertViewStyle = UIAlertViewStyleSecureTextInput;
[alert show];
If login was successful I removed the LoginSubView just before anything else with following lines
for (UIView *subView in _window.subviews)
{
if (subView.tag == 99)
{
[subView removeFromSuperview];
}
}
could also add the same subview on applicationDidEnterBackground as well. to avoid flicker the sensitive screen view when coming back to foreground next time
i like to create a second starting screen in my app.
My Idea is to use the default.png and load an UIView with an fullscreen UIImageView inside.
In viewDidLoad i thought about placing a sleep option and after this load the real app screen.
But also when my function is called in viewDidLoad, nothing happens.
Seems my superview is empty...
Here is a piece of code:
if (self._pdfview == nil)
{
pdfview *videc = [[pdfview alloc]
initWithNibName:#"pdfview" bundle:nil];
self._pdfview = videc;
[pdfview release];
}
// get the view that's currently showing
UIView *currentView = self.view;
// get the the underlying UIWindow, or the view containing the current view
UIView *theWindow = [currentView superview];
theWindow is empty after this line so that might be the reason why the other view is not loaded.
So my question, how do i create a second starting screen ?
Or three starting screens, like in games when i like to mention another company.
If I understand correctly, your point is that when your function above is executed from viewDidLoad of some controller, theWindow is nil, so your new view (startscreen) is not added to it.
A few observations:
if theWindow is nil, then self.view is the topmost UIView; you can try and replace it, or simply add your view to it:
UIView *currentView = self.view;
// get the the underlying UIWindow, or the view containing the current view
UIView *theWindow = [currentView superview];
UIView *newView = _pdfview.view;
if (theWindow) {
[currentView removeFromSuperview];
[theWindow addSubview:newView];
} else {
self.view = newView; //-- or: [self.view addSubview:newView];
}
if you want to get the UIWindow of your app (which seems what you are trying to do), you can do:
[UIApplication sharedApplication].keyWindow;
and from there you can either set the rootViewController (from iOS 4.0)
[UIApplication sharedApplication].keyWindow.rootViewController = ...;
or add newView as a subview to it:
[[UIApplication sharedApplication].keyWindow addSubview:newView];
in the second case, you should possibly remove all subviews previously added to the UIWindow. (Iterate on keyWindow.subviews and call removeFromSuperview).
OLD ANSWER:
I think that you should try and add your pdfview as a subview to the current view:
[currentView addSubview:videc];
or to what you call theWindow:
[theWindow addSubview:pvidec];
and, please, move the release statement after the `addSubview, otherwise the view will be deallocated immediately.
How can I make it so when a tab is selected, the current one is unloaded, and the next one is loaded so only one loaded at a time? Or should I not even do this? I know how to do it with a normal UIViewController as the root VC, but not sure with a UITabBarController. Also, is there a way to animate the transition from one tab to the next? Any help? Thanks!!
EDIT: ... If I unload the view controllers, then their icons on the tab bar are gone... maybe I'll just unload their views..
I can answer both questions in one...
You just need a class that acts as the UITabBarController delegate, then implement a method like so:
// Animate tab selections so they fade in and fade out
-(void)tabBarController:(UITabBarController*)tbc didSelectViewController:(UIViewController*)newSelection
{
[UIView beginAnimations:#"TabFadeIn" context:nil];
[UIView setAnimationDuration:0.6];
for( UIViewController* vc in tbc.viewControllers )
vc.view.alpha = (vc==newSelection) ? 1 : 0;
[UIView commitAnimations];
}
Now my code simply makes the tab bars fade in and out, but you could also do work here to unload non-used tabs. Sometimes that is a good idea if some of the tabs will be using a ton of memory.
You cant really manage the UITabBarController unfortunaly so you cant do lazy loading. You can by managining your own TabBar but you said u knew that already,
to manage your own tab bar though all you gotta do is setup a UITabBar with its TabBarItems in a ViewController, then implement the TabBar Delegate protocol, mainly the – tabBar:didSelectItem: method which is called whenever the tabbarItem selection is changed, then based on the item id you can load your new ViewController and release any others
so: Edit: this code goes in your UIViewController
-(void)addTabBar{
NSMutableArray* items=[[NSMutableArray alloc] init];
UITabBarItem *eventsItem= [[UITabBarItem alloc] initWithTitle:#"Events" image:nil tag:0];
UITabBarItem *albumItems=[[UITabBarItem alloc] initWithTitle:#"Album" image:nil tag:1]; //the tag is how you tell what was clicked
[items addObject:homeItem];
[items addObject:albumItems];
//MyTabBar is of type UITabBar
myTabBar=[[UITabBar alloc] initWithFrame:CGRectMake(0,411,320,49)];
[myTabBar setItems:items];
myTabBar.delegate=self; //you gotta implement the UITabBar delegate protocol
[myTabBar setSelectedItem:eventItem]; //set the selected item
[homeItem release];
[eventsItem release];
[albumItems release];
[items release];
[self.view addSubview:myTabBar]
}
then the protocol method would look something like below
- (void)tabBar:(UITabBar *)tabBar didSelectItem:(UITabBarItem *)item
{
if(item.tag == 0 )
{
//load the ViewController that pertains to this item and release others
}
...etc
}
Lazy loading is not an UITabBarController task. Instead, it is responsability of your viewControllers associated with your Tab.
To release the UIView, associated with each UIViewControllers, every time you change the TabBarItem, you must implement the following method in each UIViewController subclass, associated with your UITabBarController.viewControllers property:
-(void)viewDidDisappear {
[self.view removeFromSuperview];
self.view = nil;
}
Obviously, this will remove the self.view associated with your UIViewController. However, if your code is smart enough, this will remove all the related objects.
For example, suppose that your loadView method is as follow:
-(void)loadView {
UIView *contentVew = [[UIView alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.view = contentView;
…
...
UILabel *aLabel = [[UILabel alloc] initWithFrame:CGRectMake(0,0,320,50)];
…
…
[contentView addSubview:aLabel];
[aLabel release];
…
[contentView release];
}
This means that every object inside the contentView and their memory responsabilities are demanded to the contentView, that is released and attached to the self.view property.
In this scenario, removing the self.view (that's the reference to the contentView) resulting in a domino-style releasing of every object, that's your goal.
Best regards
Not sure why you'd want to do this, the current tab will get unloaded anyway if there's a memory issue involved. That's what -viewWillAppear, -viewDidUnload, etc. are for.
UITabBarController does lazy load all of its view controllers. When a tab is switched out, then it's view is subject to being deallocated in a memory tight situation. It is then recreated when it is chosen the second time. Furthermore, most of your memory hits are in your views and not the view controllers. Hence, don't worry about the memory hit from the view controller. The view is the proze.
If you are running on v3 of the OS, then you can use the -viewDidUnload method to ensure the maximal amount of memory reduction.
Andrew
I'm currently using this to unload inactive view controllers in the tab bar (based on Kendall's answer)
- (void)tabBarController:(UITabBarController *)tabBarController didSelectViewController: (UIViewController *)viewController {
// reload all inactive view controllers in the tab bar
for (UIViewController *vc in tabBarController.viewControllers) {
if(vc != viewController)
[vc didReceiveMemoryWarning];
}
}
In short: I want to have two fullscreen views, where I can switch between view A and view B. I know I could just use an Tab Bar Controller, but I dont want to. I want to see how this is done by hand, for learning what's going on under the hood.
I have an UIViewController that acts as an root controller:
#interface MyRootController : UIViewController {
IBOutlet UIView *contentView;
}
#property(nonatomic, retain) UIView *contentView;
#end
The contentView is hooked up to an UIView which I added as an subview to the "view" of the Nib. This has green color and I see it fullscreen. Works fine.
Then, I created two other View Controllers pretty much the same way. ViewControllerA and ViewControllerB. ViewControllerA has a blue background, ViewControllerB has a black background. Just to see which one is active.
So, in the implementation of myRootController, I do this:
// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad {
[super viewDidLoad];
ViewControllerA *vcA = [[ViewControllerA alloc] initWithNib];
[self.contentView addSubview:vcA.view];
[cvA release];
}
By the way, the -initWithNib method looks like this:
- (id)initWithNib { // Load the view nib
if (self = [super initWithNibName:#"ViewA" bundle:nil]) {
// do ivar initialization here, if needed
}
return self;
}
That works. I see the view from ViewControllerA when I start the app. But now the big question is: A View Controller typically has all those methods like:
(void)viewWillAppear:(BOOL)animated;
(void)viewDidDisappear:(BOOL)animated;
(void)viewDidLoad;
...and so on. Who or what, or how would those methods be called if I do it "my" way without a tab bar controller? I mean: If I allocate that ViewController's class and the view get's visible, would I have to take care about calling those methods? How does it know that viewWillAppear, viewDidDisappear, or viewDidLoad? I believe that the Tab Bar Controller has all this "cleverness" under the hood. Or am I wrong?
UPDATE: I've tested it. If I release the view controller (for example: ViewControllerA), I will get no log message on viewDidDisappear. Only when allocating and initializing the ViewControllerA, I get an viewDidLoad. But that's it. So all signs stand for the cleverness of UITabBarController now ;) and I have to figure out how to replicate that, right?
There's a nice example of switching views in Chapter 6 of Beginning iPhone Development. You can see the source code for it here:
http://iphonedevbook.com/
SwitchViewController has the code to change views programatically.
- (IBAction)switchViews:(id)sender
{
if (self.yellowViewController == nil)
{
YellowViewController *yellowController = [[YellowViewController alloc]
initWithNibName:#"YellowView" bundle:nil];
self.yellowViewController = yellowController;
[yellowController release];
}
[UIView beginAnimations:#"View Flip" context:nil];
[UIView setAnimationDuration:1.25];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
UIViewController *coming = nil;
UIViewController *going = nil;
UIViewAnimationTransition transition;
if (self.blueViewController.view.superview == nil)
{
coming = blueViewController;
going = yellowViewController;
transition = UIViewAnimationTransitionFlipFromLeft;
}
else
{
coming = yellowViewController;
going = blueViewController;
transition = UIViewAnimationTransitionFlipFromRight;
}
[UIView setAnimationTransition: transition forView:self.view cache:YES];
[coming viewWillAppear:YES];
[going viewWillDisappear:YES];
[going.view removeFromSuperview];
[self.view insertSubview: coming.view atIndex:0];
[going viewDidDisappear:YES];
[coming viewDidAppear:YES];
[UIView commitAnimations];
}
You can begin from the simplest removeFromSuperview/insertSubview and add code to it little by little.
//SwitchViewController.h
#import
#class BlueViewController;
#class YellowViewController;
#interface SwitchViewController : UIViewController {
IBOutlet BlueViewController *blueViewController;
IBOutlet YellowViewController *yellowViewController;
}
- (IBAction)switchViews:(id)sender;
#property (nonatomic, retain) BlueViewController *blueViewController;
#property (nonatomic, retain) YellowViewController *yellowViewController;
#end
//1. remove yellow view and insert blue view
- (IBAction)switchViews:(id)sender {
if(self.blueViewController.view.superview == nil)
{
[yellowViewController.view removeFromSuperview];
[self.view insertSubview:blueViewController.view atIndex:0];
}
}
//2. appear=insert, disappear=remove
if(blueViewController.view.superview == nil)
{
[blueViewController viewWillAppear:YES];
[yellowViewController viewWillDisappear:YES];
[yellowViewController.view removeFromSuperview];
[self.view insertSubview:self.blueViewController.view atIndex:0];
[yellowViewController viewDidDisappear:YES];
[blueViewController viewDidAppear:YES];
}
//3. now add animation
[UIView beginAnimations:#"View Flip" context:nil];
[UIView setAnimationDuration:1.25];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
//blue view will appear by flipping from right
if(blueViewController.view.superview == nil)
{
[UIView setAnimationTransition: UIViewAnimationTransitionFlipFromRight
forView:self.view cache:YES];
[blueViewController viewWillAppear:YES];
[yellowViewController viewWillDisappear:YES];
[yellowViewController.view removeFromSuperview];
[self.view insertSubview:self.blueViewController.view atIndex:0];
[yellowViewController viewDidDisappear:YES];
[blueViewController viewDidAppear:YES];
}
[UIView commitAnimations];
If I understand correctly, what you are trying to accomplish is pretty straightforward.
Just add a UINavigationController on your application delegate and do:
[navigationController pushView:vcA];
Delegates will be called accordingly:
(void)viewWillAppear:(BOOL)animated;
(void)viewDidDisappear:(BOOL)animated;
(void)viewDidLoad;
And when you want to pop the view and push another one:
[navigationController popViewControllerAnimated:true];
[navigationController pushView:vcB];
If you don't want the navigationController showing just use:
[navigationBar setHidden:YES];
Where navigationBar is the UINavigationBar corresponding to your UINavigationController.
This may be an old issue, but I recently came across the same problem and had a hard time finding something that worked. I wanted to switch between two complementary view controllers, but I wanted the switch to be animated (built in animations work fine), and I wanted it to be compatible with storyboards if possible.
For taking advantage of built-in transition, UIView's +transitionFromView:toView:duration:options:completion: method works beautifully. But, it only transitions between views, not view controllers.
For the transition to be between whole view controllers, not just views, creating a custom UIStoryboardSegue is the way to go. Whether or not you use storyboards, this approach lets you encapsulate the whole transition and manage the passing of relevant information from one view controller to the next. It only involves subclassing UIStoryboardSegue and overriding a single method, -perform.
For a reference implementation, see RAFlipReplaceSegue, the exact custom segue I put together using this approach. As a bonus, it also replaces the old view controller with the new if it is in a UINavigationController stack.
I'm trying to create a transparent modal View on top of my navigation controller. Does anyone know if this is possible?
A modal view will cover the view it is pushed on top of as well as the navigation bar for your navigation controller. However, if you use the -presentModalViewController:animated: approach, then once the animation finishes the view just covered will actually disappear, which makes any transparency of your modal view pointless. (You can verify this by implementing the -viewWillDisappear: and -viewDidDisappear: methods in your root view controller).
You can add the modal view directly to the view hierarchy like so:
UIView *modalView =
[[[UIView alloc] initWithFrame:[[UIScreen mainScreen] bounds]] autorelease];
modalView.opaque = NO;
modalView.backgroundColor =
[[UIColor blackColor] colorWithAlphaComponent:0.5f];
UILabel *label = [[[UILabel alloc] init] autorelease];
label.text = #"Modal View";
label.textColor = [UIColor whiteColor];
label.backgroundColor = [UIColor clearColor];
label.opaque = NO;
[label sizeToFit];
[label setCenter:CGPointMake(modalView.frame.size.width / 2,
modalView.frame.size.height / 2)];
[modalView addSubview:label];
[self.view addSubview:modalView];
Adding the modalView as a subview to the root view like this will not actually cover the navigation bar, but it will cover the entire view below it. I tried playing around with the origin of the frame used to init the modalView, but negative values cause it to not display. The best method that I found to cover the entire screen besides the status bar is to add the modalView as a subview of the window itself:
TransparentModalViewAppDelegate *delegate = (TransparentModalViewAppDelegate *)[UIApplication sharedApplication].delegate;
[delegate.window addSubview:modalView];
The easiest way is to use modalPresentationStyle property of navigationController (but you'll have to make animation by yourself):
self.navigationController.modalPresentationStyle = UIModalPresentationCurrentContext;
[self presentModalViewController:modalViewController animated:NO];
modalViewController.view.alpha = 0;
[UIView animateWithDuration:0.5 animations:^{
modalViewController.view.alpha = 1;
}];
I accomplish this most easily by setting up an "OverlayViewController" that sits above all other subviews of my window or root view. Set this up in your app delegate or root view controller, and make OverlayViewController a singleton so that it can be accessed from anywhere in your code or view controller hierarchy. You can then call methods to show modal views, show activity indicators, etc, whenever you need to, and they can potentially cover any tab bars or navigation controllers.
Sample code for root view controller:
- (void)viewDidLoad {
OverlayViewController *o = [OverlayViewController sharedOverlayViewController];
[self.view addSubview:o.view];
}
Sample code you might use to display your modal view:
[[OverlayViewController sharedOverlayViewController] presentModalViewController:myModalViewController animated:YES];
I haven't actually used -presentModalViewController:animated: with my OverlayViewController but I expect this would work just fine.
See also: What does your Objective-C singleton look like?
I had this same problem and in order to The solution is to add the modal view with addSubview: and animate the change in the view hierarchy with UIView’s animateWithDuration:delay:options:animations:completion:
I added a property and 2 methods to a subclass of UIViewController (FRRViewController) that includes other functionalities. I will be publishing the whole stuff on gitHub soon, but until then you can see the relevant code below. For more info, you can check my blog: How to display a transparent modal view controller.
#pragma mark - Transparent Modal View
-(void) presentTransparentModalViewController: (UIViewController *) aViewController
animated: (BOOL) isAnimated
withAlpha: (CGFloat) anAlpha{
self.transparentModalViewController = aViewController;
UIView *view = aViewController.view;
view.opaque = NO;
view.alpha = anAlpha;
[view.subviews enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
UIView *each = obj;
each.opaque = NO;
each.alpha = anAlpha;
}];
if (isAnimated) {
//Animated
CGRect mainrect = [[UIScreen mainScreen] bounds];
CGRect newRect = CGRectMake(0, mainrect.size.height, mainrect.size.width, mainrect.size.height);
[self.view addSubview:view];
view.frame = newRect;
[UIView animateWithDuration:0.8
animations:^{
view.frame = mainrect;
} completion:^(BOOL finished) {
//nop
}];
}else{
view.frame = [[UIScreen mainScreen] bounds];
[self.view addSubview:view];
}
}
-(void) dismissTransparentModalViewControllerAnimated:(BOOL) animated{
if (animated) {
CGRect mainrect = [[UIScreen mainScreen] bounds];
CGRect newRect = CGRectMake(0, mainrect.size.height, mainrect.size.width, mainrect.size.height);
[UIView animateWithDuration:0.8
animations:^{
self.transparentModalViewController.view.frame = newRect;
} completion:^(BOOL finished) {
[self.transparentModalViewController.view removeFromSuperview];
self.transparentModalViewController = nil;
}];
}
}
Here's what I did to solve the problem - Google the details but this approach worked very well for me:
Take a screenshot of the underlying view. https://devforums.apple.com/message/266836 - this leads to a ready-made method that returns a UIView for the current screen.
Hand the screenshot to the modal view (I used a property)
Present the modal view
In the modal view controller's viewDidAppear, set the image as UIImageView at index 0. Adjust the vertical position of the image by the height of the status bar.
In the modal view controller's viewWillDisappear, remove the image again
The effect is:
The view animates in as any modal view does - the semi transparent parts of the modal view glide over the existing view
As soon as the animation stops, the background is set to the screenshot - this makes it appear as if the old view is still underneath even though it isn't.
As soon as the modal view's disappear animation starts, the image is removed. The OS meanwhile shows the old navigation view so the modal view transparently glides away and out of sight as you'd expect.
I tried animating in my own overlay view but it didn't work very well. I got a crash with no indication as to what has crashed. Rather than chase this down I did the bg view & Works really well.
Code in the modal view - I think you can figure out the rest, namely setting the property modalView.bgImage...
- (void)viewDidAppear:(BOOL)animated {
// background
// Get status bar frame dimensions
CGRect statusBarRect = [[UIApplication sharedApplication] statusBarFrame];
UIImageView *imageView = [[UIImageView alloc] initWithImage:self.bgImage];
imageView.tag = 5;
imageView.center = CGPointMake(imageView.center.x, imageView.center.y - statusBarRect.size.height);
[self.view insertSubview:imageView atIndex:0];
}
- (void)viewWillDisappear:(BOOL)animated {
[[self.view viewWithTag:5] removeFromSuperview];
}
self.modalPresentationStyle = UIModalPresentationCurrentContext;
[self presentModalViewController:newview animated:YES];
and make sure you setup the modal view background to be transparent,
self.view.background = .... alpha:0.x;
if you set modalPresentationStyle for the modal view controller to:
viewController.modalPresentationStyle = 17;
The view in the background is not removed. (TWTweetComposeViewController use it).
I did not try to pass App Store review with this code though
This post about displaying a semi-transparent "Loading..." view might give a few pointers on how to proceed.
Yeah, you have to add the view manually, and if you want to slide in from the bottom or whatever you have to do the animation yourself too.
I wrote a class to do this, and a semi-modal datepicker using that class as an example.
You can find documentation in this blog post, the code is on github
I've been researching this same issue for the past week. I tried all the various answers and examples found in Google and here on StackOverflow. None of them worked that well.
Being new to iOS programming, I wasn't aware of something called UIActionSheet. So if you're trying to accomplish this in order to show a modal overlay of buttons (such as a modal asking someone how they want to share something), just use UIActionSheet.
Here is a webpage that shows an example of how to do this.
I got this idea from https://gist.github.com/1279713
Prepare:
In the modal view xib (or scene using storyboard), I setup the full-screen background UIImageView (hook it with the .h file and give it a property "backgroundImageView") with 0.3 alpha. And I set the view (UIView) background color as plain black.
Idea:
Then in "viewDidLoad" of the modal view controller I capture the screenshot from the original status and set that image to the background UIImageView. Set the initial Y point to -480 and let it slide to Y point 0 using 0.4-second duration with EaseInOut animation option. When we dismiss the view controller, just do the reverse thing.
Code for the Modal View Controller Class
.h file:
#property (weak, nonatomic) IBOutlet UIImageView *backgroundImageView;
- (void) backgroundInitialize;
- (void) backgroundAnimateIn;
- (void) backgroundAnimateOut;
.m file:
- (void) backgroundInitialize{
UIGraphicsBeginImageContextWithOptions(((UIViewController *)delegate).view.window.frame.size, YES, 0.0);
[((UIViewController *)delegate).view.window.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage * screenshot = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
backgroundImageView.image=screenshot;
}
- (void) backgroundAnimateIn{
CGRect backgroundImageViewRect = backgroundImageView.frame;
CGRect backgroundImageViewRectTemp = backgroundImageViewRect;
backgroundImageViewRectTemp.origin.y=-480;
backgroundImageView.frame=backgroundImageViewRectTemp;
[UIView animateWithDuration:0.4 delay:0.0 options:UIViewAnimationCurveEaseInOut animations:^{
backgroundImageView.frame=backgroundImageViewRect;
} completion:^(BOOL finished) {
}];
}
- (void) backgroundAnimateOut{
CGRect backgroundImageViewRect = backgroundImageView.frame;
backgroundImageViewRect.origin.y-=480;
[UIView animateWithDuration:0.4 delay:0.0 options:UIViewAnimationCurveEaseInOut animations:^{
backgroundImageView.frame=backgroundImageViewRect;
} completion:^(BOOL finished) {
}];
}
In viewDidLoad, simply call:
[self backgroundInitialize];
[self backgroundAnimateIn];
In anywhere we dismiss the modal view controller, we call:
[self backgroundAnimateOut];
Please note that this will ALWAYS animate the background image. So if this modal view controller transition style (or the segue transition style) is not set to "Cover Vertical", you may not need to call the animation methods.
I finally accomplished this, for a navigation or tab bar interface, by combining an overlay view controller (see: pix0r's answer) that's hidden / un-hidden before hiding or showing a view controller based on this very good blog post.
Concerning the view controller, the tip is to make its background view the clearColor, then the semi-transparent overlay view is visible and whatever views are added as subviews in the view controller are in front and most importantly opaque.
I've created open soruce library MZFormSheetController to present modal form sheet on additional UIWindow. You can use it to present transparency modal view controller, even adjust the size of the presented view controller.
For iOS 8+ you can use UIModalPresentationOverCurrentContext presentation style for presented view controller to easy achieve desired behavior.
UIViewController *viewController = [[UIViewController alloc] init];
viewController.view.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.9f];
viewController.modalPresentationStyle = UIModalPresentationOverCurrentContext;
[self presentViewController:viewController animated:YES completion:nil];
If you also need to support iOS 7 - check this thread.
You can achieve transparent/semi-transparent modal view effect by overlaying a transparent/semi-transparent button on both the view and the navigation bar.
You can access the navigation bar through the navigationBar property of the UINavigationController.
I found that UIButton unlike UILabel will trap mouse events - hence giving the correct modal behavior.
I just found a workaround for that. Just create a 1X1 of UIViewController and add it to your parent view controller. And show the transparent modal view controller in that UIViewController.
on viewDidLoad;
self.dummyViewController = [[UIViewController alloc] init];
[self.dummyViewController.view setFrame:CGRectMake(0, 0, 1, 1)];
[self.view addSubView:self.dummyViewController.view];
when you need to open a transparentViewController;
[self.dummyViewController presentModalViewController:yourTransparentModalViewController animated:true];
If you need a screen like the attached one, the below code may help you.
The code:
MyViewController * myViewController = [[MyViewController alloc] initWithNibName:nibName bundle:nil];
UINavigationController * myNavigationController = [[UINavigationController alloc] initWithRootViewController: myViewController];
myNavigationController.modalPresentationStyle = UIModalPresentationPageSheet;
[self presentModalViewController: myNavigationController animated:YES];
If say you want a screen overlay, use the parentViewController.view, it will place above navigation bar ++
MyCustomViewController* myOverlayView = [[MyCustomViewController alloc] init];
[self.parentViewController.view addSubview:myOverlayView];
This worked for me:
UIViewController *modalViewController = [[UIViewController alloc] init];
modalViewController.view.backgroundColor = [UIColor whiteColor] colorWithAlpha:0.5];
[self showDetailViewController:modalViewController sender:nil];