I've been developing for iOS for quite a while now, but I always got problems with custom alerts / popups, I just don't know what is the right way to add a custom view as a popup on top of all other views in app.
I've checked online and there are several ways to do it, but what is the best way for creating those custom alerts / popups?
There is a CustomViewController class and this is how I've created it and add it as a popup:
Create a new UIViewController
Add a UIView inside the UIViewController's XIB file as a child of self.view (This will hold the popup elements inside)
Add this method inside the UIViewController for presenting the view:
- (void)presentViewController
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.window.windowLevel = UIWindowLevelStatusBar;
self.window.screen = [UIScreen mainScreen];
[self.window makeKeyAndVisible];
UIWindow *mainWindow = [[[UIApplication sharedApplication] delegate] window];
[mainWindow setUserInteractionEnabled:NO];
[self.window addSubview:self.view];
self.view.alpha = 0.0f;
self.backgroundImage.image = [self takeSnapshot];
[UIView animateWithDuration:0.2 delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{
self.view.transform = CGAffineTransformIdentity;
self.view.alpha = 1.0f;
} completion:nil];
}
Add this method inside the UIViewController for removing the view:
- (void)dismissShareViewController
{
[UIView animateWithDuration:DEFAULT_ANIMATION_DURATION
animations:^{
self.view.alpha = 0.0f;
} completion:^(BOOL finished) {
[self.view removeFromSuperview];
self.window = nil;
[[UIApplication sharedApplication] setStatusBarHidden:NO withAnimation:UIStatusBarAnimationSlide];
UIWindow *mainWindow = [[[UIApplication sharedApplication] delegate] window];
[mainWindow setUserInteractionEnabled:YES];
}];
}
Usage:
Creating a property for the UIViewController
#property (strong, nonatomic) CustomViewController *viewController;
Initialize and present
- (void)showCustomViewController
{
self.viewController = [[CustomViewController alloc] init];
[self.viewController presentViewController];
}
The problem is that I always get a Received Memory Warning. Also I've added a - (void)dealloc method inside the CustomViewController to know if the UIViewController is releasing properly, but for some reason the dealloc method is getting called right after the presentViewController and not after the dismissShareViewController as it should.
Another thing that I want to achieve: This CustomViewController has no UIStatusBar and when I try to present MFMailComposeViewController, I always get the MFMailComposeViewController to show without a UIStatusBar or with white status bar, but I can't change it to default back again. I've tried several question here on StackOverflow but without any luck, it just won't change.
Putting a UIView or UIWindow above Statusbar
Please, for once and for all, someone will help me with it.
UIAlertView is a subclass of UIView. If you wanted to create a custom alert view, I would also subclass UIView so that it is easy to swap out your existing UIAlertView's with your new custom view.
You can place your custom alert view atop your topmost UIWindow to cover everything.
#interface MyAlertView : UIView
- (void)duplicateAllTheFunctionsOfUIAlertViewHere;
#end
#implementation MyAlertView : UIView
- (id)initWithFrame:(CGRectMake)frame {
// add your custom subviews here
// An example of a custom background color:
self.backgroundColor = [UIColor colorWithRed:1.0 green:0.0 blue:0.0 alpha:0.5];
}
- (void)duplicateAllTheFunctionsOfUIAlertViewHere {
//re-implement all the functionality in your custom ways
}
- (void)show
{
UIView* superview = [UIApplication sharedApplication].keyWindow;
self.frame = superview.bounds;
[superview addSubview:self];
// Do an animation if you want to get fancy
}
#end
Usage in a view controller:
- (void)showAlertView
{
UIView* alertView = [[MyAlertView alloc] initWithTextAndButtons:#"blah"];
[alertView show];
}
Related
I use keyWindow to add a subView, it add success but did not shows in the front of the screen hierarchy.
The deep gray view is my added view:
My code is:
#interface ViewController ()
{
LMLUpspringView *pop_v;
}
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
pop_v = [[LMLUpspringView alloc] initWithFrame:CGRectMake(0, 0, self.view.bounds.size.width, self.view.bounds.size.height)];
UIWindow *keywindow = [[[UIApplication sharedApplication] delegate] window];
[keywindow addSubview:pop_v];
//[keywindow bringSubviewToFront:pop_v];
//[self.view addSubview:pop_v];
}
I have tried use [keywindow bringSubviewToFront:pop_v] to bring to front.But do not work.
And if I use [self.view addSubview:pop_v], it shows in the front.
You run into this issue, is caused by you do not know the difference between the viewDidLoad method and the viewDidAppear: method.
viewWillAppear:
Called before the view is added to the windows’ view hierarchy
viewDidAppear:
Called after the view is added to the view hierarchy
The controller's view add to the window's view hierarchy is betweenviewWillAppear: and viewDidAppear:, the viewDidLoad is in front of viewWillAppear:, so you can not do that. you should in viewDidAppear: add your view.
You can solve this problem using appdelegate's Windows :
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
[appDelegate.window addSubview:pop_v]
Use this:
#interface ViewController ()
{
LMLUpspringView *pop_v;
}
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
pop_v = [[LMLUpspringView alloc] initWithFrame:CGRectMake(0, 0, self.view.bounds.size.width, self.view.bounds.size.height)];
UIWindow *keywindow = [[UIApplication sharedApplication] keyWindow];
[keywindow addSubview:pop_v];
//[keywindow bringSubviewToFront:pop_v];
//[self.view addSubview:pop_v];
}
I Created a UIViewController that I want to add on the top of all of my views, the problem is that it's visible for only 1 sec and then disappears from the screen and deallocated. Why this is happening?
This is my code:
- (void)show
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.window.windowLevel = UIWindowLevelAlert;
self.window.screen = [UIScreen mainScreen];
[self.window makeKeyAndVisible];
UIWindow *mainWindow = [[[UIApplication sharedApplication] delegate] window];
[mainWindow setUserInteractionEnabled:NO];
[self.window addSubview:self.view];
self.view.alpha = 0.0f;
self.backgroundImage.image = [self takeSnapshot];
[UIView animateWithDuration:0.2 delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{
self.view.transform = CGAffineTransformIdentity;
self.view.alpha = 1.0f;
} completion:nil];
}
- (void)dealloc
{
NSLog(#"Screen deallocated.");
}
Looking at the graph of your objects,I assume self is some view controller.
your self.view is subview of Appdelegates UIWindow. When you do
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
your Appdelegate window gets released subsequently your view controller(self) will also be released.
So I would suggest you to retain UIWindow instance before actually releasing it.
You must save variable for your UIViewController in that place from where "show" method was called. For example - just create property…
#property (nonatomic, strong) CustomViewController *cvc;
Otherwise instance of your UIViewController will be released at the end of that method. Because your object won't have any strong references…
If you work with XIB, you can use this code: You have to create an UIView class, for example HeaderView. In your UIViewController [ I will call it BaseViewController] .h you have to add a property:
#property (nonatomic, strong) HeaderView *headerView;
and in .m you have to add this code:
- (id)init {
self = [super init];
if (self) {
[self configureMenuView:self];
}
return self;
}
- (void)configureMenuView:(UIViewController *)vc {
NSArray *topLevelObjects = [[NSBundle mainBundle] loadNibNamed:#"HeaderView" owner:self options:nil];
for( id currentObject in topLevelObjects)
{
if([currentObject isKindOfClass:[HeaderView class]]) {
self.headerView = (BannerView *)currentObject;
self.headerView.frame = CGRectMake(0, 0, 768, 1024);
self.headerView.backgroundColor = [UIColor clearColor];
[vc.view addSubview:self.headerView];
break;
}
}
[self.bannerView.closeHeaderButton addTarget:self action:#selector(closeHeaderrView) forControlEvents:UIControlEventTouchUpInside]; // it's only an example if you want add some button in your header.
So, in every view controller you have to import this class: BaseViewContoller and to add it like parent:
#interface TestViewController : HeaderViewController
Hope it works for you :)
I try to use Auto Layout for my application's root view, i.e. I install Auto Layout constraints in the application's UIWindow and enable Auto Layout on the root VC's view.
The problem: When I dismiss a modally presented VC, the view hierarchy "collapses" and only the UIWindow remains visible. I assume that the root VC's view is resized to zero.
If I do not use Auto Layout on the application's root view everything seems to work fine.
My question: Is it forbidden to use Auto Layout for an application's root view? If not, what am I doing wrong? If yes, is this restriction documented somewhere in the official Apple docs, or is it merely "common knowledge"?
The following code is a minimal sample application that demonstrates the problem. You can simply copy&paste the code into a new Xcode project (use the "empty application" template).
#pragma mark Interface declarations
#interface AppDelegate : UIResponder <UIApplicationDelegate>
#property (strong, nonatomic) UIWindow* window;
#end
#interface PresentingViewController : UIViewController
#end
#interface PresentedViewController : UIViewController
#end
#pragma mark AppDelegate implementation
#implementation AppDelegate
- (BOOL) application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Set this to false and the problem goes away
bool useAutoLayout = true;
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.window.backgroundColor = [UIColor whiteColor];
PresentingViewController* pvc = [[PresentingViewController alloc] init];
UINavigationController* nc = [[UINavigationController alloc] initWithRootViewController:pvc];
self.window.rootViewController = nc;
[self.window addSubview:nc.view];
if (useAutoLayout)
{
nc.view.translatesAutoresizingMaskIntoConstraints = NO;
NSDictionary* viewsDict = [NSDictionary dictionaryWithObjectsAndKeys:
nc.view, #"ncView",
nil];
NSArray* hConstraintsWindow = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|-0-[ncView]-0-|"
options:0
metrics:nil
views:viewsDict];
[self.window addConstraints:hConstraintsWindow];
NSArray* vConstraintsWindow = [NSLayoutConstraint constraintsWithVisualFormat:#"V:|-0-[ncView]-0-|"
options:0
metrics:nil
views:viewsDict];
[self.window addConstraints:vConstraintsWindow];
}
nc.view.backgroundColor = [UIColor redColor];
[self.window makeKeyAndVisible];
return YES;
}
#end
#pragma mark PresentingViewController implementation
#implementation PresentingViewController
- (void) loadView
{
self.view = [[UIView alloc] initWithFrame:CGRectZero];
self.view.backgroundColor = [UIColor greenColor];
self.title = #"presenting vc";
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:#selector(add:)];
}
- (void) add:(id)sender
{
PresentedViewController* pvc = [[PresentedViewController alloc] init];
UINavigationController* navigationController = [[UINavigationController alloc]
initWithRootViewController:pvc];
navigationController.modalTransitionStyle = UIModalTransitionStyleCoverVertical;
[self presentViewController:navigationController animated:YES completion:nil];
}
#end
#pragma mark PresentedViewController implementation
#implementation PresentedViewController
- (void) loadView
{
self.view = [[UIView alloc] initWithFrame:CGRectZero];
self.view.backgroundColor = [UIColor yellowColor];
self.title = #"modal vc";
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:#selector(done:)];
}
- (void) done:(id)sender
{
[self dismissViewControllerAnimated:YES completion:nil];
}
#end
EDIT: In response to trojanfoe's answer, here are my thoughts why I believe I should be allowed to use Auto Layout for the root view:
UIWindow is derived from UIView, so I see no reason why I should not be allowed to install constraints in it.
It's true that there is no UIViewController instance that manages a UIWindow - but if we talk about the roles of the MVC design pattern, from my point of view the application delegate clearly takes the controller role, and therefore should be allowed to set up constraints.
To make this clear: If it's not possible to use Auto Layout on the root view for some technical reason, I will, of course, accept that. In fact, I want to be convinced not to use Auto Layout, but I prefer rational argument to blind coding.
I believe you are over-complicating the set-up of the views:
Constraints should be set on views only, not on the window.
The view controller is responsible for managing the view hierarchy and you don't need to be involved at all.
I think removing your constraints/view manipulation code will solve your issues:
PresentingViewController* pvc = [[PresentingViewController alloc] init];
UINavigationController* nc = [[UINavigationController alloc] initWithRootViewController:pvc];
self.window.rootViewController = nc;
nc.view.backgroundColor = [UIColor redColor];
[self.window makeKeyAndVisible];
Also shouldn't that be PresentedViewController given the navigation view controller will do the presenting?
My problem is when I present a UIViewController the presenting views are going black.
I have a UIViewController named mainViewController which is the root view of my windows.
Inside I have a MMDrawerController (just added as a subview of mainViewController's view).
The MMDrawerController contains the rest of my views.
But when I'm presenting a new UIViewController from my mainViewController, the new VC displays well, but when it dismisses it left only black screen behind.
To note the black screen appears when adding (I can see it directly). Not when dismissing.
For testing purpose I did this code :
UIViewController *vc = [UIViewController new];
vc.view.backgroundColor = [UIColor redColor];
[[[[UIApplication sharedApplication] keyWindow] rootViewController] presentViewController:vc animated:NO completion:^{
[vc dismissViewControllerAnimated:YES completion:nil];
}];
Which do the same black result as normal use. (normally it's a QLViewController...)
My mainViewController is set like it :
_mainViewController = [MyMainViewController new];
_window.rootViewController = _mainViewController;
[self.window addSubview:_mainViewController.view];
Souce code of MMDrawerController which is up to date in my project
I have tested the code just in MMDrawerController example project, but I can't reproduce the problem, following is what I have tried:
MMAppDelegate.m
-(BOOL)application:(UIApplication *)application willFinishLaunchingWithOptions:(NSDictionary *)launchOptions{
//...
UIViewController *testVc = [[UIViewController alloc] init];
testVc.view.backgroundColor = [UIColor greenColor];
[testVc.view addSubview:self.drawerController.view];
[self.window setRootViewController:testVc];
[self.window addSubview:testVc.view];
return YES;
}
MMExampleSideDrawerViewController.m:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
switch (indexPath.section) {
case MMDrawerSectionViewSelection:{
UIViewController *vc = [UIViewController new];
vc.view.backgroundColor = [UIColor redColor];
UIViewController *mainVC = [[UIApplication sharedApplication] keyWindow].rootViewController;
[mainVC presentViewController:vc animated:YES completion:^{
[vc dismissViewControllerAnimated:YES completion:nil];
}];
return;
//...
}
MMExampleCenterTableViewController.m:
-(void)doubleTap:(UITapGestureRecognizer*)gesture{
UIViewController *vc = [UIViewController new];
vc.view.backgroundColor = [UIColor redColor];
UIViewController *mainVC = [[UIApplication sharedApplication] keyWindow].rootViewController;
[mainVC presentViewController:vc animated:YES completion:^{
[vc dismissViewControllerAnimated:YES completion:nil];
}];
return;
[self.mm_drawerController bouncePreviewForDrawerSide:MMDrawerSideLeft completion:nil];
}
In the end, the problem was that I applied inset constraints on my mainViewController.
Behind the scene I suppose that method presentViewController:animated:completion: use frames and not AutoLayout, which break the UIView of the UIViewController that are in the mainViewController.
Thanks to #simalone, I used the same way to find the origin of the problem (use MMDrawerController example project).
I you want to test yourself the problem, I uploaded the example project with the issue so you can understand it.
There is a breakpoint on the line which is the origin of the bug.
Then just double click on the view.
Thanks again to #simalone. Cheers !
I want to use my own controller for navigating in between uiviewcontrollers. So like that I can have custom transitions in between my views.
I did something which is working but I'm not sure if it is the right way? Could there be problems related to memory later?...I'm not sure everything is deallocated the right way.
Thanks for your help!
Here is my code:
In my AppDelegate, when it starts:
self.rootVC = [[[MenuView alloc] initWithNibName:#"MenuView" bundle:nil] autorelease];
[self.window addSubview:self.rootVC.view];
In my AppDelegate, the method for switching in between UIViewController:
-(void) changeRootController: (NSString*) ctrlName{
UIViewController *oldVC = self.curVC;
UIViewController *newVC;
if(ctrlName == #"Studio"){
newVC = [[StudioView alloc] initWithNibName:#"StudioView" bundle:nil];
}
else if(ctrlName == #"Menu"){
newVC = [[MenuView alloc] initWithNibName:#"MenuView" bundle:nil];
}
self.curVC = newVC;
[UIView transitionWithView:self.window
duration:1.0
options:UIViewAnimationOptionCurveEaseIn
animations:^{
newVC.view.transform = CGAffineTransformConcat(newVC.view.transform, CGAffineTransformMakeTranslation(-1024, 0));
[self.rootVC.view addSubview:newVC.view];
newVC.view.transform = CGAffineTransformConcat(newVC.view.transform, CGAffineTransformMakeTranslation(1024, 0));
oldVC.view.transform = CGAffineTransformConcat(oldVC.view.transform, CGAffineTransformMakeTranslation(1024, 0));
}
completion:^(BOOL finished){
if(finished){
[oldVC release];
}
}
];
}
And I switch views in my UIViewController as follow:
AppDelegate *ad = (AppDelegate*)[[UIApplication sharedApplication] delegate];
[ad changeRootController:#"Studio"];
newVC isn't released.
The block should retain the newVc so after your transitionWithView
you should call. (last line of function)
[newVC autorelease];
Thats assuming that the self.curVC property is strong OR retained