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.
Related
Is there a way to have two webview synchronized.
I would like to display on a second screen using Airplay the same WebView but in 16:9.
I managed to create a separate window for the second screen (which is 16:9) and load my local html file but the actions on my iPad screen are not synced with this second screen.
I just have two independent windows.
I really want to use the second screen to display a larger HTML file.
"Synchronized" is underspecified in the OP, but given the most essential representation of a web view's state is it's request, you could try the following: In the controller of the webView that the user is controlling (call it "webViewA"), declare conformance to UIWebViewDelegate:
self.webViewA.delegate = self;
then:
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
UIWebView *webViewB = // presuming you have a way to get this pointer
[webViewB loadRequest:request];
}
You can achieve scrolling synchronization (probably the next most essential part of state) by declaring the same controller as conforming to UIScrollViewDelegate, and then:
self.webViewA.scrollView.delegate = self;
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
UIWebView *webViewB = // ...
[webViewB setContentOffset:scrollView.contentOffset animated:YES];
}
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.
I'm facing a problem trying to take screenshots form UIWebViews. I need to take some screenshots of my UIWebView and it works but the screenshot is not correct because they are taken in the event webViewDidFinishLoad, but it calls webViewDidFinishLoad when the UIWebView is not loaded fully, I mean, I take the screenshot in the event webViewDidFinishLoad but the UIWebView is not correctly and fully loaded so it takes a screenshot and it makes right but the screenshot is not totally correct because its the event is triggered (webViewDidFinishLoad) but the UIWebView is not totally loaded. Any ideas?
Thank you very much
A user was having a issue with their use of a activity indicator that apears until a page has loaded but theirs would come back after the main content had loaded the question is here UIWebView not finishing loading? and the answer code is used below. After the code I will go into detail about how this could be used for your situation.
(In the code below the web page truly starts loading on //show UIActivityIndicator and truly finishes loading the main content (not the extra content you are struggling with) on //hide UIActivityIndicator)
//Define the NSStrings "lastURL" & "currentURL" in the .h file.
//Define the int "falsepositive" in the .h file. (You could use booleans if you want)
//Define your UIWebView's delegate (either in the xib file or in your code)
- (void)webViewDidFinishLoad:(UIWebView *)webView {
lastURL = [[NSString alloc] initWithFormat:#"%#", webView.request.mainDocumentURL];
if (falsepositive != 1) {
NSLog(#"Loaded");
//hide UIActivityIndicator
} else {
NSLog(#"Extra content junk (i.e. advertisements) that the page loaded with javascript has finished loading");
//This method may be a good way to prevent ads from loading hehe, but we won't do that
}
}
-(BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType; {
NSURL *requestURL =[request mainDocumentURL];
currentURL = [[NSString alloc] initWithFormat:#"%#", requestURL]; //not sure if "%#" should be used for an NSURL but it worked...
return YES;
}
- (void)webViewDidStartLoad:(UIWebView *)webView {
if ([currentURL isEqualToString:lastURL]) {
falsepositive = 1;
NSLog(#"The page is loading extra content with javascript or something, ignore this");
} else {
falsepositive = 0;
NSLog(#"Loading");
//show UIActiviyIndicator
}
}
When a page loads in iOS, webviewdidstart and webviewdidfinish are called but not knowing the difference in your main page/html content objective-c calls this again for extra content such as ads or frames. Using this if I were in your situation I would create a BOOL such as pageIsLoading and set it to true in webviewdidstart and then set it to false in webviewdidfinish. After the BOOL is turned off in webviewdidfinish I would end webviewdidfinish by calling a method that will check after a short delay if the BOOL pageIsLoading == YES and if it is, do nothing because more content is loading. If pageIsloading == no then all content must be loaded and now would be a good time to take your snapshot.
Rather than taking screenshot in the event webViewDidFinishLoad you can try to take the screenshot explicitly using UIButton. Let this button be disabled and you can just set this button's enabled property to YES after web view fully loads its content.
On button click to can implement your code to take screenshot.
Edited : Webview's webViewDidFinishLoad method is called when it finishes loading its content. Might be its possible that it may be taking some time to render some images/content on its view.
If possible you can use NSTimer in your webViewDidFinishLoad method to wait for sufficient time(1 min) so that mean while webview can load its content fully.
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:60.0
target: self
selector: #selector(MethodName:)
userInfo: nil
repeats: NO];
In Selector method you can implement your code of taking screenshot.
Hope this will help you...
How to determine progress in UIWebView?
Few Observations:
1. To determine the progress of downloaded content, we need to make a NSURLConnection object and fetch data twice: one with the UIWebView and the other with NSURLConnection
2. If we just fetch data once using NSURLConnection and load the webview with that data text/html then that data renders poorly
Difficulties:
a) As fetching data twice can largely slow down the process, is it feasible (appstore safe?) to use private api's like the one given here: https://github.com/petr-inmite/imtwebview
b) If we cannot, then how we may display a progress bar?
c) Also will downloading the data asynchronously using NSURLConnection mirror the progress of UIWebView loading? How bad the performance of fetching data twice would be?
There are some browsers like safari, dolphin which are displaying progress bar...any ideas on how to do this???
Use the UIWebView delegates for it.First set your webview's delegate to be self and then utilize these methods.Prior to this create and add a spinner to the view which shall indicate the progress of uiwebview e.g UIActivityIndicatorView *spinner
- (void)webViewDidStartLoad:(UIWebView *)localWebView {
[ spinner performSelectorInBackground: #selector(startAnimating) withObject: nil];
}
- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error {
[ spinner performSelectorInBackground: #selector(stopAnimating) withObject: nil];
}
- (void)webViewDidFinishLoad:(UIWebView *)localWebView {
[ spinner performSelectorInBackground: #selector(stopAnimating) withObject: nil];
}