I have tried all these (one, two, three, four) solutions, but after I come back from web view screen to previous viewController, it freezes for around 2 seconds(sometimes more). There is nothing in viewWillAppear which will cause the freezing.
Here is viewWillDisappear of web view controller :
-(void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
[detailWebView stopLoading];
detailWebView.delegate = nil;
NSLog(#"viewWillDisappear called !!!");
}
First viewController :
-(void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self.navigationController.navigationBar setBarTintColor:[UIColor colorWithRed:41.0/255.0 green:151.0/255.0 blue:132.0/255.0 alpha:1.0]];
UIImage *imagePlus = [[UIImage imageNamed:#"create.png"] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
buttonCreate = [UIButton buttonWithType:UIButtonTypeCustom];
[buttonCreate setImage:imagePlus forState:UIControlStateNormal];
[buttonCreate addTarget:self action:#selector(createActivity:) forControlEvents:UIControlEventTouchUpInside];
buttonCreate.frame = CGRectMake(self.view.frame.size.width - 40, 10, 16, 16);
[self.navigationController.navigationBar addSubview:buttonCreate];
UIBarButtonItem *backButton = [[UIBarButtonItem alloc] initWithTitle:NSLocalizedString(#"back", nil) style:UIBarButtonItemStylePlain target:nil action:nil];
[backButton setTitleTextAttributes:#{NSForegroundColorAttributeName : [UIColor blackColor]} forState:UIControlStateNormal];
self.navigationItem.backBarButtonItem = backButton;
[self.navigationController.navigationBar setTintColor:[UIColor blackColor]];
}
Update : I just confirmed that if I let the webView load completely and then go to previous viewController, there is no freeze observed.
I do the following, which is similar to what #Mrunal suggests but loads a valid page. I originally added this to deal with a situation where a loaded page had timers which continued to run forever after popping back from a UIWebView. For me this works well.
- (void) viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
// Load a blank page as found things hang around.
[self.theWebView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:#"about:blank"]]];
}
NB I do not call stopLoading as that stops the load request. This could be what is wrong with Mrunal's solution?
Try this in viewWillDisappear:
-(void)viewWillDisappear:(BOOL)animated {
[detailWebView setDelegate:nil];
[detailWebView loadRequest: [NSURLRequest requestWithURL: [NSURL URLWithString:#""]]];
[detailWebView stopLoading];
[super viewWillDisappear:animated];
}
Are you using NSURLProtocol to intercept the web requests from your UIWebView? The below answer is written on that assumption.
Loading of the web page involves sending out requests for multiple resources ( images, videos, html, javascript). Each of those resources have an URL that will be intercepted by the NSURLProtocol
Now if you close the webView before all the resources are loaded, NSURLProtocol takes sometimes to cancel all the open requests for resources it is intercepting and in this process freezing your app.
You will unregister from NSURLProtocol before you exit out of webView. So
override func viewWillDisappear{
NSURLProtocol.unregisterClass(/*your custom url protocol class name here*/)
}
should fix the freezing issue
PS: Sometimes you might have NSURLProtocol defined for the entire app, in which case I think it is better to separate out the NSURLProtocol for UIwebView from the rest of app. I don't know much about your project for me to comment if this is a feasible option for you
I think the webview might still process in the background. And I made the experience that it is quite annoying to release a UIWebView completely. You might want to have a look in the Profiler: up and close the webview's ViewController several times, the buffer should steadily increase.
This is why I came up to merge several solution approaches from stack overflow to finally and completely remove all storage from a webview and also stop it from running.
It is crucial, that the webview is set to be nil, to really deallocate it's memory, in former days we had the release method, which is now "obsolete" if you make use of the ARC properly.
- (void)releaseWebView:(UIWebView *)webView
{
/*
There are several theories and rumors about UIWebView memory leaks, and how
to properly handle cleaning a UIWebView instance up before deallocation. This
method implements several of those recommendations.
#1: Various developers believe UIWebView may not properly throw away child
objects & views without forcing the UIWebView to load empty content before
dealloc.
Source: http://stackoverflow.com/questions/648396/does-uiwebview-leak-memory
*/
[webView loadHTMLString:#"" baseURL:nil];
/*
#2: Others claim that UIWebView's will leak if they are loading content
during dealloc.
Source: http://stackoverflow.com/questions/6124020/uiwebview-leaking
*/
[webView stopLoading];
/*
#3: Apple recommends setting the delegate to nil before deallocation:
"Important: Before releasing an instance of UIWebView for which you have set
a delegate, you must first set the UIWebView delegate property to nil before
disposing of the UIWebView instance. This can be done, for example, in the
dealloc method where you dispose of the UIWebView."
Source: UIWebViewDelegate class reference
*/
[webView setDelegate:nil];
/*
#4: If you're creating multiple child views for any given view, and you're
trying to deallocate an old child, that child is pointed to by the parent
view, and won't actually deallocate until that parent view dissapears. This
call below ensures that you are not creating many child views that will hang
around until the parent view is deallocated.
*/
[webView removeFromSuperview];
webView = nil;
}
Please give it a try and call this method in viewWillDisappear:
Related
Before I present my issue, I want to mention that I tried looking for solutions here and here.
I am creating a hybrid application which uses native UIWebView for rendering the responsive designed web application. Following is the issue description :
1. I have a UITabBarController.
2. Each of the tab has a UIWebView.
3. I have to preload all tabs.
4. I am showing a UIActivityIndicator till the content loads on the first tab.
5. White screen appears for about 8-10 seconds and then the content starts to appear.
I will be happy to see this time become 2-4 seconds.
Following is my implementation :
[self performSelectorOnMainThread:#selector(loadAllTabs) withObject:nil waitUntilDone:YES];
-(void) loadAllTabs
{
for(UIViewController * viewController in self.viewControllers){
if(![[NSUserDefaults standardUserDefaults]boolForKey:#"isSessionExpired"])
{
if((int)[self.viewControllers indexOfObject:viewController] != 4)
{
viewController.tabBarItem.tag = (int)[[self viewControllers] indexOfObject:viewController];
[viewController view];
}
}
}
}
In WebView controller's viewDidLoad I have :
[_tgWebView loadRequest:[NSURLRequest requestWithURL:homeURL]];
I was looking forward to suppressesIncrementalRendering, but since I am preloading all tabs, this does not work.
Since my app supports iOS 7+, thus WKWebView can't be applied here.
I also thought of increasing the launch image duration but learned that is won't be a good practice.
Can this be implemented using GCD?
Please bring out the pitfalls in my implementations so that my application makes better performance.
First, have UIWebView on each tab hidden until it has finished loading, then show it. Underneath the UIWebView you can have some placeholder image to describe it loading. Then using the webViewDidFinishLoad delegate method show the web view when it has finished loading. This approach will be non blocking.
- (void)webViewDidFinishLoad:(UIWebView *)webview
{
if (webview.isLoading)
return;
else
webView.hidden = false;
}
Second, preload the first tab then load the subsequent tabs while displaying the first. You can do that by placing the following code in the first tabs viewDidLoad method:
// Preload the subsquent tabs
for (UIViewController *aVC in self.tabBarController.viewControllers)
if ([aVC respondsToSelector:#selector(view)] && aVC != self)
aVC.view;
This way, the additional tabs web views are loaded in the background in a non blocking manner. You could combine it with hiding the web views while loading in case the user navigates to the additional tabs before their pages load.
I tested this with three tabs and it worked nicely.
So the first view controller could look something like this:
#implementation FirstViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Load the website
[self loadWebView];
// Preload the subsquent tabs
for (UIViewController *aVC in self.tabBarController.viewControllers)
if ([aVC respondsToSelector:#selector(view)] && aVC != self)
aVC.view;
}
-(void)loadWebView
{
// Create Request
NSURL *url = [NSURL URLWithString:#"http://anandTech.com"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
// Load the page
[webView loadRequest:request];
}
- (void)webViewDidFinishLoad:(UIWebView *)webview
{
if (webview.isLoading)
return;
else
webView.hidden = false;
}
EDIT:
Removed GDC as it was crashing when the views had WebViews
This post made my day. I am now using NSNotificationCenter for this.
It's hard to identify the where the problem is directly.
First, I suggest that you check your network connection by loading the page on a desktop machine on the same Wifi to see if it is your server that is too slow.
Also you can load your pages one by one instead of load all pages concurrently, since multiple HTTP request are not processed in a FIFO sequence, they may be processed out of order, and your page may need all resource to be loaded before displaying.
Do you control the web page your self? You can use safari inspector to inspect your web page to see where is the time spent, is it resource loading, javascript processing or rendering.
I am developing an app in Xcode but am having trouble with one part.
Basicly on the main app page, there is a small section that contains a web view. Everytime I change to another view controller and back, i see it flicker and reload. Is there a way to prevent it from reloading every time i open the view? instead just reloading every time I open the app.
This is the code:
NSURL *webUrl = [NSURL URLWithString:#"myurl.com"];
NSURLRequest *webrequestUrl = [NSURLRequest requestWithURL:webUrl];
[webView loadRequest:webrequestUrl];
webView.scrollView.scrollEnabled = NO;
webView.scrollView.bounces = NO;
Your webview is reloading every time you go to that screen is probably because you added it to either viewDidAppear: or viewWillAppear.
Add your block of code to the viewDidLoad method so it only gets executed when the view is loaded (aka when it's shown for the first time).
- (void)viewDidLoad:(BOOL)animated {
NSURL *webUrl = [NSURL URLWithString:#"myurl.com"];
NSURLRequest *webrequestUrl = [NSURLRequest requestWithURL:webUrl];
[webView loadRequest:webrequestUrl];
webView.scrollView.scrollEnabled = NO;
webView.scrollView.bounces = NO;
}
Edit: Oh by going to another view and back you meant going back on the navigation stack (or switching the navigation stack). In that case, you can keep a strong reference to your view controller with the webview and reuse it. Though I wouldn't suggest it doing it this was, unless that screen is the most important screen of your application.
Hope you are having your logic in other than the method
- (void)viewDidLoad
move your code to - viewDidLoad
even if your are having trouble then pass the preloaded UIWebView when ever you are navigating.
If I have a UIWebView with mediaPlaybackRequiresUserAction = YES, then later in my app create a new UIWebView and set mediaPlaybackRequiresUserAction = NO on it, it also changes the value of that property on the first instance.
e.g. I have a UIWebView and then present a second UIWebView modally (for an ad), changing mediaPlaybackRequiresUserAction on the modal webView affects the presenting UIWebView.
Any ideas why this is? Are UIWebViews all backed by a single instance?
Link to sample project here.
not sure your app purpose, just try this way:
- (IBAction)unwind:(UIStoryboardSegue *)unwindSegue
{
[self TS_updateLabel];
[[self webView] setMediaPlaybackRequiresUserAction:YES];
[self TS_reloadWebView];
}
....
in method TS_reloadWebView
if (self.webView.isLoading) {
[self.webView stopLoading];
}
[self.webView loadHTMLString:htmlString baseURL:baseURL];
I guess it also is UIWebView bug .... but now this way maybe can solve your problem.
Hello I have a memory issue in my iPad app. Each time I change from a view to another view (this transition is made with segues), the app is increasing the memory used and never releases the memory. It is always increasing the memory used.
Let's see an example:
I am in my first view "home" which has these lines in viewDidLoad and viewDidAppear
(void)viewDidLoad
{
[super viewDidLoad];
self.view.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:#"background.png"]];
[self initializeHomeDataSources];
DateService* dateService = [[DateService alloc] init];
self.currentDate = [dateService today];
[self checkHomeStatus];
[self showEmptyHomeViews];
[self setUpFonts];
}
and this my view did appear method
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
_homeAutomaticUpdate = YES;
//This is a Thread
[NSThread detachNewThreadSelector:#selector(automaticHome) toTarget:self withObject:nil];
[self.phrasesView startPhrasesThread];
if ([InternetService internetConnection]) {
[self synchronizeHome];
}
if (self.scheduleDataSource.currentEvent) {
[self loadMessagesFor:self.homeDataSource.currentEvent];
[self loadLibraryFor:self.homeDataSource.currentEvent];
} else {
[self loadLibrary];
}
}
- (void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
_homeAutomaticUpdate = NO;
}
All the IBOutlet's are defined as (nonatomic, strong).
Each time the HomeView is loaded the memory increases it's quantity and I don't know what is happening.
Can anybody help me here? This problem is causing me consternation.
I'm guessing that you're going "backwards" to previous controllers using segues. Is that true? If so, that's your problem -- unless you use an unwind segue, you should never go backwards using segues because they always instantiate new controllers. So, when going back to previous controllers, either use an unwind, or use code that reverses your forward segues -- that is, popViewControllerAnimated: if the forward segue was a push, and dismissViewControllerAnimated:completion: if the segue was a modal.
Few questions:
Is your app killed after a while, because of memory usage?
Why you are creating new thread in -viewDidAppear?
Have you tried to simulate memory warning?
(In simulator: Hardware -> Simulate Memory Warning or Shift + CMD + M)
Does the memory gets down after memory warning or not?
This is not a whole answer for your question but your outlets must be weak unless their not top level objects.
All the IBOutlet's used should be (nonatomic, weak). Try this out..
I'm making a split-view based iPad application(Portrait mode only), and I want to know how to recover initial state after viewDidUnload is called.
When split-view application started for the first time,
-splitViewController:willHideViewController:withBarButtonItem:forPopoverController:
is called automatically (right after -viewDidLoad).
I prepares UIBarButtonItems in the method.
If I open modal dialog or something with UIWebViewController (it consumes a lot of memory), application receives memory warning, viewDidUnload(s) are called.
When I close the modal dialog, -viewDidLoad is called automatically, but this time
-splitViewController:willHideViewController:withBarButtonItem:forPopoverController: is not called.
I prepares UIBarButtonItems in
-splitViewController:willHideViewController:withBarButtonItem:forPopoverController:
but it is not called, so buttons are dismissed.
In that case, should I call the method manually?
I found similar posting here.
https://github.com/grgcombs/IntelligentSplitViewController/issues/6
Thanks.
I don't know it is OK to answer to my own question, but maybe I found an answer for this. http://osdir.com/ml/cocoa-dev/2011-02/msg00430.html
It says that we should preserve BarButtonItems in viewDidUnload, and load it in viewDidLoad.
It seems working fine.
- (void)viewDidUnload {
[super viewDidUnload];
self.toolbarItems = self.toolbar.items; // property with retain policy
}
- (void)viewDidLoad {
[super viewDidLoad];
if (self.toolbarItems) {
self.toolbar.items = self.toolbarItems;
self.toolbarItems = nil;
}
}