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!
Related
I have a trio of functions and a property that I use to control my popovers as follows:
-(void)dismissPopoverIfPresentAnimated:(BOOL)animated
{
if (self.currentPopover)
{
[self.currentPopover dismissPopoverAnimated:animated];
self.currentPopover = nil;
}
}
-(void)presentViewController:(UIViewController *)viewController inView:(UIView *)view fromRect:(CGRect)rect suppressArrow:(BOOL)suppressArrow
{
//Did the user just tap on a button to bring up the same controller that's already displayed?
//If so, just dismiss the current controller.
BOOL closeOnly = NO;
if (self.currentPopover)
{
UIViewController *currentController = [self.currentPopover.contentViewController isKindOfClass:UINavigationController.class] ? ((UINavigationController *)self.currentPopover.contentViewController).topViewController : self.currentPopover.contentViewController;
UIViewController *newController = [viewController isKindOfClass:UINavigationController.class] ? ((UINavigationController *)viewController).topViewController : viewController;
if ([currentController isKindOfClass:newController.class])
closeOnly = YES;
[self dismissPopoverIfPresentAnimated:NO];
}
if (!closeOnly)
{
self.currentPopover = [[UIPopoverController alloc] initWithContentViewController:viewController];
self.currentPopover.backgroundColor = [UIColor whiteColor];
[self.currentPopover presentPopoverFromRect:rect inView:view permittedArrowDirections:(suppressArrow ? 0 : UIPopoverArrowDirectionAny) animated:YES];
}
}
(instancetype) initWithContentViewController:(UIViewController )viewController
{
self = [super initWithContentViewController:[[UIViewController alloc] init]];
if (self)
{
UIViewController contentViewController = super.contentViewController;
[contentViewController addChildViewController:viewController];
[viewController didMoveToParentViewController:contentViewController];
[contentViewController.view addSubview:viewController.view];
[self setPopoverContentSize:viewController.preferredContentSize animated:NO];
}
return self;
}
This runs fine in iOS 7, but in iOS 8 the problem is there is a delay between the call to presentPopoverFromRect and when the item actually shows up onscreen. So, if a user double taps a button to show a popover, the first tap will properly dismiss, then "start" the showing of the new controller. The second tap will make the dismiss call (the popover is not yet visible) and then not show the new controller (this is a design feature so that click a button will show a popover, clicking it again will hide it).
The problem is that the call to dismiss the popover doesn't actually work and the popover will show up. At that point I can't get rid of it because my property is nil and I think it is not showing.
My guess is this is an iOS 8 bug where the dismiss somehow doesn't see a visible popover and thus doesn't do anything, where instead, it should prevent it from showing up.
Oh, one last note is that the call to presentViewController is always done on the main thread.
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:^{
}];
}
I have a view controller that is presented on pressing on one of the tabs in a tabBarController. In this view controller I initialise a UIImagePickerController in the viewDidLoad method:
- (void)viewDidLoad
{
[super viewDidLoad];
//Set imagePicker
//-------------------------//
_imagePicker = [[UIImagePickerController alloc] init];
_imagePicker.delegate = self;
_imagePicker.videoMaximumDuration = 10.0f;
}
The intention is to then display the UIImagePickerController at a later time when a button is pressed. For some reason though when the tab icon is pressed for this view controller, there is a 3-4 second hang while this viewDidLoad method is running. When I comment out the line _imagePicker = [[UIImagePickerController alloc] init] there is no hang time and the view controller loads immediately - as it should.
Does anyone know why allocating and initialising the UIImagePickerController is taking so long? If so, is there a way to speed it up other than running it as a background process? It seems like this is not normal behaviour.
I am using iOS7, and I am not calling viewWillAppear or viewDidAppear.
Turns out this is only a problem when in debug mode (when the iPhone is connected and running through Xcode). Once the same app is running without being connected to Xcode the lag doesn't occur.
Try this iOS 12
//show a HUD or activityIndicator
dispatch_async(dispatch_queue_create("openPhotosCamera", NULL), ^{
UIImagePickerController *mediaUI = [[UIImagePickerController alloc] init];
dispatch_async(dispatch_get_main_queue(), ^{
//hide HUD or activityIndicator
[presenter presentViewController:mediaUI animated:YES completion:nil];
});
});
**presenter is yourViewController / self
Try this.
- (void)viewDidLoad{
//Set imagePicker
//-------------------------//
_imagePicker = [[UIImagePickerController alloc] init];
_imagePicker.delegate = self;
_imagePicker.videoMaximumDuration = 10.0f;
[super viewDidLoad];
Apple's "Mobile Human Interface Guidelines" says about Popovers:
When possible, allow people to close one popover and open a new one
with one tap. This behavior is especially desirable when several
different bar buttons each open a popover, because it prevents people
from having to make extra taps.
The only solution I can think right now is to track the position of the touch when dismissing the popover and check whether that was the position of another button.
Is there any easier way to do this?
PS: I searched in stackoverflow and googled quite a while before posting. Sorry, if this was asked here before.
UPDATE
I guess I didn't explain myself well. Let's say I have three buttons. All of them open a popover. My user taps button #1 and a popover opens. While the popover is open, the user taps button #2. The popover gets dismissed (because the user tapped outside of the popover - default behavior of non-modal popovers) and a new popover opens up because the user had clicked on button #2. All of that without having to tap twice: once to dismiss the popover and twice for opening the new one.
2nd UPDATE
I built a simple dummy to reproduce what I'm trying to do. When the user taps on a button and a popover is open, the method that opens the popovers doesn't get called. Therefore the user has to click twice to open the second popover. Any ideas?
#import "RootViewController.h"
#import "AViewController.h"
#interface RootViewController()
#property (nonatomic, retain) UIPopoverController *currentPopover;
#end
#implementation RootViewController
#synthesize currentPopover;
- (void)loadView
{
CGRect applicationFrame = [[UIScreen mainScreen] applicationFrame];
UIView *view = [[UIView alloc] initWithFrame:applicationFrame];
CGRect buttonFrame = CGRectMake(50, 100, 200, 40);
for (int i = 0; i < 3; i++)
{
UIButton *button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[button setTitle:[NSString stringWithFormat:#"Button %i", i + 1] forState:UIControlStateNormal];
[button addTarget:self action:#selector(openPopover:) forControlEvents:UIControlEventTouchDown];
[button setFrame:buttonFrame];
[view addSubview:button];
buttonFrame.origin.y += 50;
}
self.view = view;
[view release];
}
- (IBAction)openPopover:(id)sender
{
AViewController *avc = [[AViewController alloc] init];
UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:avc];
[avc release];
UIPopoverController *tempPopover = [[UIPopoverController alloc] initWithContentViewController:navigationController];
[tempPopover setDelegate:self];
[tempPopover setPopoverContentSize:CGSizeMake(320, 500)];
[tempPopover presentPopoverFromRect:[sender frame] inView:[self view] permittedArrowDirections:UIPopoverArrowDirectionLeft animated:YES];
self.currentPopover = tempPopover;
[tempPopover release];
[navigationController release];
}
- (void)dealloc
{
[currentPopover release];
[super dealloc];
}
#end
If you use bar button items in a toolbar, the open popover is not automatically closed when you tap another bar button item. In these situations you should close the visible popover and open the other one in one step.
- (IBAction)sortAction {
[searchBarView resignFirstResponder];
[self.popoverController dismissPopoverAnimated:YES]; //clear popover
self.popoverController = popoverSetting;
[self.popoverController presentPopoverFromBarButtonItem:sortBarButtonItem
permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES]; //show popover
}
hope will help you
Lets say you have 3 buttons and each opens a popup. You could use a state variable that tracks whether a popup is currently open, and inside each "open popup" method, close the existing one (if it is open) before opening the new popup.
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!