Is it possible to display a modal view over the top of a UIWebView? I have a UIViewController that loads a WebView. I then want to push a Modal View Controller over the top so that a modal view covers up the WebView temporarily...
The WebView is working fine; here's how it's loaded in the View Controller:
- (void)loadView {
// Initialize webview and add as a subview to LandscapeController's view
myWebView = [[[UIWebView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame]] autorelease];
myWebView.scalesPageToFit = YES;
myWebView.autoresizesSubviews = YES;
myWebView.autoresizingMask = (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight);
myWebView.delegate = self;
self.view = myWebView;
}
If I attempt to load a Modal View controller from within viewDidLoad, however, no modal view appears:
- (void)viewDidLoad {
[super viewDidLoad];
// Edit dcftable.html with updated figures
NSMutableString *updated_html = [self _updateHTML:#"dcftable"];
// Load altered HTML file as an NSURL request
[self.myWebView loadHTMLString:updated_html baseURL:nil];
// If user hasn't paid for dcftable, then invoke the covering modal view
if (some_condition) {
LandscapeCoverController *landscapeCoverController = [[[LandscapeCoverController alloc] init] autorelease ];
[self presentModalViewController:landscapeCoverController animated:YES];
}
}
I suspect that there's something that needs to be done with the UIWebView delegate to get it to receive the new modal view...but can't find any discussion or examples of this anywhere...again, the objective is to invoke a modal view that covers over the top of the WebView.
Thanks for any thoughts in advance!
I ran into this same problem. Moving the presentModalViewController call from viewDidLoad to viewDidAppear did the trick.
I couldn't get it to work in viewDidLoad either, but I got it to work in a different method. Here's what I did:
- (void)viewDidLoad {
...
if (some_condition) {
[NSTimer scheduledTimerWithTimeInterval:0.2 target:self selector:#selector(showModal) userInfo:nil repeats:NO];
}
}
- (void)showModal {
LandscapeCoverController *landscapeCoverController = [[[LandscapeCoverController alloc] init] autorelease ];
[self presentModalViewController:landscapeCoverController animated:YES];
}
Basically, it works if it's not being called in viewDidLoad. I wish I could explain why, I'm curious myself, but this should at least fix your problem.
Hope this helps!
Related
I have a UIButton in my app that when pressed it shows the next view controller. Sometimes the UI locks up and the app freezes for a moment due to background processes. When this happens the user might tap the button multiple times because nothing happened immediately on the first tap, and when this occurs the UINavigationController pushes the ViewController again a bunch of times on top of itself, so that you have to go back several times to get back to home. Here is my code:
- (void)viewDidLoad {
[super viewDidLoad];
self.pushVCButton.multipleTouchEnabled = NO;
}
- (IBAction)pushVCButtonPressed:(id)sender {
self.pushVCButton.enabled = NO;
ViewController *viewController = [[ViewController alloc] init];
[self.navigationController pushViewController:viewController animated:YES];
self.pushVCButton.enabled = YES;
}
How do I get this to never push multiple instances of viewController?
You should really try to make the background process run not on UI thread, but if you can not try setting the button enabled to yes only when view did disappear or listen for completion of push animation:
- (IBAction)pushVCButtonPressed:(id)sender {
self.pushVCButton.enabled = NO;
ViewController *viewController = [[ViewController alloc] init];
[self.navigationController pushViewController:viewController animated:YES];
// Hack: wait for this view to disappear to enable the button
//self.pushVCButton.enabled = YES;
}
- (void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated]
self.pushVCButton.enabled = YES;
}
Also make sure that the action is not called twice.
In my app, I do a search asynchronously. When that completes my mapViewController with pins for the locations is displayed. 2 seconds after that, I do a modal transition over to a listViewController. I set the backgroundcolor like this:
self.view.backgroundColor = [UIColor colorWithWhite:1.0 alpha:0.65];
Making it possible to see the map behind it.
The problem is this: Less then a second after the listView appears you can see the map in the background for a second before it goes away. What I can see in the background now, is the mainViewController the app starts with.
it looks like this:
Less than a second later:
Any help/explanation would be greatly appreciated.
Your view is still transparent, but once your modal controller is at the top of the stack, the view behind it is hidden.
Try using :
yourController.modalPresentationStyle = UIModalPresentationCurrentContext;
[yourController present...];
This code seems to work. I hope it will help.
-(void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
MKMapView *mapView = [MKMapView new];
[self.view addSubview:mapView];
mapView.frame = self.view.bounds;
UIGestureRecognizer *gestureRecognizer = [UITapGestureRecognizer new];
[gestureRecognizer addTarget:self action:#selector(displayTransparentVC)];
[self.view addGestureRecognizer:gestureRecognizer];
}
-(void)displayTransparentVC
{
UIViewController *vc = [TransparentViewController new];
self.modalPresentationStyle = UIModalPresentationCurrentContext;
[self presentViewController:vc animated:YES completion:^{
}];
}
The problem I am experiencing is that when i try to pop a view controller using the default back swipeGesture in iOS7 viewDidDisappear of the present ViewController does not always get called after viewWillDisappear. I am using the UINavigationController as rootViewController.
App remain struck and does not receive any user inputs after this scenario. Sometimes app gets crashed, when i look at the log: it shows "Can't add self as subview' and in crash log, it showss EXC_BAD_ACCESS. How to fix this, but when i use back button in navigation bar app works normally.
- (void)viewWillDisappear:(BOOL)animated
{
// [self.navigationController.navigationBar setAlpha:1.0f];
[self createBarButtonITems];
self.navigationItem.title = #"Back";
}
- (void)viewDidDisappear:(BOOL)animated
{
[self zoomOutTableWithoutAnimation];
}
-(void)zoomOutTableWithoutAnimation
{
backgroundView.frame = CGRectMake(0,0,320,480);
backgroundView.transform=CGAffineTransformMakeScale(1, 1);
sideMenuTableView.transform=CGAffineTransformMakeScale(0.5,0.5);
sideMenuTableView.frame = CGRectMake(0,150,self.view.frame.size.width/2, self.view.frame.size.height);
sideMenuTableView.hidden = YES;
}
As you mentioned swipe back gesture, this is probably due to the interactive pop back.
As it is mentioned in WWDC 2013, session Custom Transitions Using View Controllers, you cannot assume a viewWillDisappear will be followed by viewDidDisappear. The same goes to viewWillAppear and viewDidAppear.
I'm not sure why you want to call
[self createBarButtonITems]
in viewWillDisappear, did you mean viewWillAppear?
Anyway, it seems to me that [self createBarButtonITems] did some side effect.
Try the following code in viewWillDisappear to undo the side effect:
- (void)viewWillDisappear
{
[self doSomethingHasSideEffect];
id <UIViewControllerTransitionCoordinator> coordinator;
coordinator = [self transitionCoordinator];
if(coordinator && [coordinator initiallyInteractive])
{
[coordinator notifyWhenInteractionEndsUsingBlock:
^(id <UIViewControllerTransitionCoordinatorContext> ctx)
{
if(ctx.isCancelled)
{
[self undoAnySideEffect]
}
}];
}
}
From your code what I can understand is you need a back button with title #"Back" instead of the previous view controllers title
just add this code tho your view controller, in which you trying to do the above stuff view did load method
UIBarButtonItem *backButton = [[UIBarButtonItem alloc] initWithTitle:(IS_IOS_7 ? #"" : #"Back") style:UIBarButtonItemStylePlain target:Nil action:nil];
self..navigationItem.backBarButtonItem = backButton;
Add [super viewWillDisappear:animated];
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
[self createBarButtonITems];
self.navigationItem.title = #"Back";
}
I have a UIPopoverController that I am loading with a ViewController to hold a UIWebView. When I press a button to present the Popover, the webpage loads fine. I can dismiss the Popover just as fine as well. When I press a button to present the Popover again, the UIWebView reloads. Is there a way to maintain/persist the state of the WebView in the ViewController so that the content remains the same when the popover is presented/dismissed?
Maybe another question is, when a Popover is dismissed, is my ViewController wiped? I don't believe it should be because it isn't a local variable, but I'm not so sure if I have a misunderstaing of how UIPopoverController's behave.
Relevant code below:
In my main ViewController:
_popoverMenuVC = [[TBPopoverMenuViewController alloc] init];
_popoverMenuVC.delegate = self;
_popoverMenuVC.contentSizeForViewInPopover = CGSizeMake(300, 300*(1+sqrt(5)/2.));
_popoverCtr = [[UIPopoverController alloc] initWithContentViewController:_popoverMenuVC];
Code for my button:
- (void)menuBtnPressed:(id)sender
{
if (IS_IPAD) {
UIButton *btn = (UIButton*)sender;
if (!_popoverCtr.isPopoverVisible) {
//set url for popover webview
_popoverMenuVC.popoverURL = #"http://www.google.com";
// show popover
[_popoverCtr setPopoverContentSize:CGSizeMake(300, 300*(1+sqrt(5)/2.))];
[_popoverCtr presentPopoverFromRect:btn.frame
inView:self.view
permittedArrowDirections:UIPopoverArrowDirectionDown
animated:YES];
} else {
// hide popover
[_popoverCtr dismissPopoverAnimated:YES];
}
}
Code for my popoverMenuVC:
-(void) viewDidAppear:(BOOL)animated{
[self setupUIs];
}
- (void)setupUIs
{
// webview
wv = [[UIWebView alloc] initWithFrame:CGRectMake(0, 0, 300, 300*(1+sqrt(5)/2.))];
//activity indicator
self.activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
self.activityIndicator.frame = CGRectMake(0.0, 0.0, 40, 40);
self.activityIndicator.center = self.view.center;
// darkview for activity indicator
self.darkView = [[UIView alloc] initWithFrame:CGRectMake(0, 0,300, 300*(1+sqrt(5)/2.))];
[self.darkView setBackgroundColor:[UIColor blackColor]];
[self.darkView setAlpha:0.4];
wv.delegate = self;
wv.scalesPageToFit = YES;
wv.scrollView.bounces = NO; //get rid of bounce for popover menu
[self.view addSubview:wv];
}
A few delegates for handing activity indicator:
#pragma mark Activity Indicator
- (void) showLoadingIndicator
{
[self.view insertSubview:self.darkView aboveSubview:self.view];
[self.view insertSubview:self.activityIndicator aboveSubview:self.darkView];
[self.activityIndicator startAnimating];
}
- (void) hideLoadingIndicator
{
[self.activityIndicator stopAnimating];
[self.activityIndicator removeFromSuperview];
[self.darkView removeFromSuperview];
}
#pragma mark UIWebView Delegates
-(void)webViewDidStartLoad:(UIWebView *)webView{
NSLog(#"Webview is starting to load");
[self showLoadingIndicator];
}
-(void)webViewDidFinishLoad:(UIWebView *)webView{
NSLog(#"request: %#", [[webView request] description]);
if(!webView.loading){
NSLog(#"Webview has finished loading");
[self hideLoadingIndicator];
}
}
I have a very similar implementation of this working for iPhone, without the Popover obviously, just with ViewControllers and it seems to work fine. Wondering if this has to work with the PopoverController itself...
Thanks for any help!
Edit 1:
I moved the following code to my main ViewController:
_popoverMenuVC = [[TBPopoverMenuViewController alloc] init];
_popoverMenuVC.wv = [[UIWebView alloc] initWithFrame:CGRectMake(0, 0, 300, 300*(1+sqrt(5)/2.))];
_popoverMenuVC.wv.scalesPageToFit = YES;
_popoverMenuVC.wv.scrollView.bounces = NO; //get rid of bounce for popover menu
_popoverMenuVC.req = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:#"http://www.google.com"]];
_popoverCtr = [[UIPopoverController alloc] initWithContentViewController:_popoverMenuVC];
I have nothing in my viewDidAppear function anymore, I now moved it to viewDidLoad:
- (void)viewDidLoad
{
[super viewDidLoad];
wv.delegate = self;
[wv loadRequest:req];
[self.view addSubview:wv];
}
But the WebView doesn't load anymore (which I think is related to this issue: iOS 7 UIWebView doesn't load webpage)
Moving the above code from ViewDidLoad to viewDidAppear will load my WebView, yet the same reloading problem persists.
Any additional help would be appreciated!
Edit 2:
I did some code refactoring in order to focus in on what might be causing this behavior:
In my main ViewController, I am setting up my PopoverViewController, along with its WebView, and setting it to be the init content for our UIPopoverController:
_popoverMenuVC = [[TBPopoverMenuViewController alloc] init];
_popoverMenuVC.wv = [[UIWebView alloc] initWithFrame:CGRectMake(0, 0, 300, 300*(1+sqrt(5)/2.))];
_popoverMenuVC.wv.scalesPageToFit = YES;
_popoverMenuVC.wv.scrollView.bounces = NO; //get rid of bounce for popover menu
_popoverMenuVC.req = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:#"http://www.google.com"]];
[_popoverMenuVC.view addSubview:_popoverMenuVC.wv];
_popoverCtr = [[UIPopoverController alloc] initWithContentViewController:_popoverMenuVC];
All of this is happening once outside of my TBPopoverMenuViewController's code.
In my TBPopoverMenuViewController.h, I set up a property for wv and req.
In my TBPopoverMenuViewController.m, I synthesize my wv, but not my req.
Here is my viewDidLoad, where I set the wv's delegate to self.
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
[self.view setBackgroundColor:[UIColor whiteColor]];
wv.delegate = self;
// [wv loadRequest:_req];
}
And in viewDidAppear, this is where I load my request.
-(void) viewDidAppear:(BOOL)animated{
[super viewDidAppear:NO];
[self setupActivityIndicator];
[wv loadRequest:_req];
}
Trying to load my request anywhere else, in view did load, or even in the main ViewController, it doesn't load the request in the popover. It only seems to want to happen in the viewDidAppear function.
Further, when I try to load the popover the second time, you can see the previous wv with the page loaded where it was before, but then almost immediately after the request seems to reload on top of that webview.
Does anyone have a solution? How can I maintain my webview when the popover dismisses and is re-presented. This seems to work fine on iPhone in a non-popover implementation, making me think that there is something I'm missing for iPad/iOS7/UIPopoverControllers.
Any help would be appreciated!
Edit 3:
After some debugging with Xcode's Instruments tool, I've realized that calling:
[wv loadRequest:_req];
In the viewDidAppear of my ViewController results in a memory leak! So there seems to be something essential in my problem with the UIWebView reloading and where/how I'm loading my request. As I've mentioned before, I've tried calling loadRequest outside of my Popover's ViewController in my main View Controller, with no luck. I also tried to call loadRequest in viewDidLoad, with no luck as well.
I was able to successfully call loadRequest in my menuBtnPressed function after the popover is presented:
- (void)menuBtnPressed:(id)sender
{
if (IS_IPAD) {
popOverBtn = (UIButton*)sender;
if (!_popoverCtr.isPopoverVisible) {
[_popoverCtr setPopoverContentSize:CGSizeMake(300, 300*(1+sqrt(5)/2.))];
[_popoverCtr presentPopoverFromRect:popOverBtn.frame inView:self.view permittedArrowDirections:UIPopoverArrowDirectionDown animated:YES];
[_popoverMenuVC.wv loadRequest:_popoverMenuVC.request];
} else {
// hide popover
[_popoverCtr dismissPopoverAnimated:YES];
}
Which boils down to the two places I can actually successfully call loadRequest for the UIWebView in my popover are:
-in viewDidApepar in the ViewController
-after presenting my popover with a button press
Both of these seem to still result in a memory leak according to Xcode's Instruments tools. I still can't get my UIWebView to maintain/persist state when going back from presenting -> dismissing -> re-presenting the popover.
Any suggestions?
The viewDidAppear method of the popover is called every time the present is done.
So every time the popover is presented, the current code adds a new UIWebView which goes to the default url (with all the previous web views from the previous presents underneath).
A couple of changes should give you the behavior you want:
In the popover content view controller, move the call to setupUIs from viewDidAppear to viewDidLoad.
In the main view controller, move the setting of popoverURL to where the popover content view controller is first created (right after you alloc+init it).
It turns out the answer to my problem as to...call loadRequest twice.
//setup popover menu vc
_popoverMenuVC = [[TBPopoverMenuViewController alloc] init];
_popoverMenuVC.wv = [[UIWebView alloc] initWithFrame:CGRectMake(0, 0, 300, 300*(1+sqrt(5)/2.))];
_popoverMenuVC.delegate = self;
_popoverMenuVC.wv.scalesPageToFit = YES;
_popoverMenuVC.wv.scrollView.bounces = NO; //get rid of bounce for popover menu
_popoverMenuVC.request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:#"http://www.google.com/"]];
[_popoverMenuVC.wv loadRequest:_popoverMenuVC.request]; //first request
[_popoverMenuVC.view addSubview:_popoverMenuVC.wv];
_popoverCtr = [[UIPopoverController alloc] initWithContentViewController:_popoverMenuVC];
[_popoverMenuVC.wv loadRequest:_popoverMenuVC.request]; // second request
This results in the behavior I want. The UIWebView loads in the PopoverViewController with the page loaded, and when I present-> dismiss-> re-present the popover, the page UIWebView is where I left it. I have no idea why this works though, it seems like a bug or a hack, having to call the function twice. The loadRequest also seems to result in a memory leak according to the Instruments toolset in Xcode.
So I solved my problem, but I'm still a bit confused as to why this is the solution. Luckily I stumbled onto it...
Any clarification by anyone more experienced would be appreciated!
I'm confused. I have a navigation controller with a BarItem which opens a first view. After some work is done, I want this view to disappear and I want a second view to open.
root view: navigation controller
first view: activity indicator, where some data is put together
second view: MFMailComposeViewController
In the root view, the BarItem runs these lines to open the first view:
IndicatorViewController *indicator = [[IndicatorViewController alloc] initWithNibName:#"IndicatorViewController" bundle:nil];
indicator.view.backgroundColor = [UIColor clearColor];
self.modalPresentationStyle = UIModalPresentationCurrentContext;
[self presentModalViewController:indicator animated:YES];
The first view (IndicatorViewController) does some work and finally runs
[self dismissModalViewControllerAnimated:YES];
This works fine. But - how do I open the second view?
I tried this:
I open the second view. After closing the second view, my first view pops up again (since it is still there) and get's dismissed at this point. This code is placed in the first view:
- (void) viewDidAppear:(BOOL)animated {
static BOOL firstTime = YES;
if (firstTime) {
//do stuff that takes some time (that's why I show the indicator)
MailViewController *controller = [[MailViewController alloc] init];
if (controller)
[self presentModalViewController:controller animated:YES];
firstTime = NO;
} else {
[self dismissModalViewControllerAnimated:YES];
}
}
Since the first view pops up again, the user can see the indicator one more time, after the second view is closed - and that is not what I want.
What am I missing here? What would be the better way to do this?
I would do something like this. Make a navigationController, and make the first view as the root controller. Then do something like this:
FirstView.m
- (void)viewDidLoad
{
[super viewDidLoad];
[self.navigationController setNavigationBarHidden:YES];
}
- (void) nextView { // however you get to your next view, button/action/etc.
UIViewController *screen = [self.storyboard instantiateViewControllerWithIdentifier:#"yourIdentifier"];
[self.navigationController pushViewController:screen animated:YES];
}
Then in the second view:
SecondView.m
- (void) nextView { // however you get to your next view, button/action/etc.
UIViewController *screen = [self.storyboard instantiateViewControllerWithIdentifier:#"yourIdentifier"];
[self.navigationController pushViewController:screen animated:YES];
}
And finally in the rootview:
RootView.m
- (void)viewDidLoad
{
[super viewDidLoad];
NSArray *navStack = [NSArray arrayWithObject:self];
self.navigationController.viewControllers = navStack;
[self.navigationController setNavigationBarHidden:NO];
}
This will make your RootView the new rootview of the NavigationController.
self.navigationController.viewControllers
is the array with all the ViewControllers that are on the navcontrollers stack. The first object is the rootcontroller. If we replace the whole array with our own array, it knows only one item. You CAN go back by dismissing if that's what you want though. This isn't the prettiest way of doing it, but it's not the worst either.