I've opted to use UIWebView so I can control the layout of text I've got contained in a local html document in my bundle. I want the text to display within a UIWebView I've got contained within my view. So the text isn't the whole view, just part of it.
Everything runs fine, but when the web page loads I get a blank screen for a second before the text appears. This looks really bad. can anyone give me an example of how to stop this happening? I'm assuming I have to somehow hide the web view until it has fully loaded? Could someone one tell me how to do this?
At the moment I'm calling my code through the viewDidLoad like this:
[myUIWebView loadRequest: [NSURLRequest requestWithURL:[NSURL fileURLWithPath:[[NSBundle mainBundle]pathForResource:#"localwebpage" ofType:#"html"] isDirectory:NO]]];
Any help is much appreciated. I've read round a few forums and not seen a good answer to this question, and it seems like it recurs a lot as an issue for beginners like myself.
Thanks for taking the time to read this post!
UPDATED info
Thanks for your response. The suggestions below solves the problem but creates a new one for me as now when my view loads it is totally hidden until I click on my toggle switch. to understand this it's maybe most helpful if I post all my code. Before this though let me explain the setup of my view. I've got a standard view within which I've also got two web views, one on top of the other. each web view contains different text with different styling. the user flicks between views using a toggle switch, which hides/reveals the web views. I'm using the web views because I want to control the style/layout of the text. Below is my full .m code, I can't figure out where it's going wrong. My web views are called oxford & harvard I'm sure its something to do with how/when I'm hiding/revealing views. I've played around with this but can't seem to get it right. Maybe my approach is wrong. A bit of advice ironing this out would be really appreciated:
#implementation ReferenceViewController
#synthesize oxford;
#synthesize harvard;
// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad {
[super viewDidLoad];
[oxford loadRequest: [NSURLRequest requestWithURL:[NSURL fileURLWithPath:[[NSBundle mainBundle]pathForResource:#"Oxford" ofType:#"html"] isDirectory:NO]]];
[harvard loadRequest: [NSURLRequest requestWithURL:[NSURL fileURLWithPath:[[NSBundle mainBundle]pathForResource:#"Harvard" ofType:#"html"] isDirectory:NO]]];
[oxford setHidden:YES];
[harvard setHidden:YES];
}
- (void)webViewDidFinishLoad:(UIWebView *)webView {
if([webView hidden]) {
[oxford setHidden:NO];
[harvard setHidden:NO];
}
}
//Toggle controls for toggle switch in UIView to swap between webviews
- (IBAction)toggleControls:(id)sender {
if ([sender selectedSegmentIndex] == kSwitchesSegmentIndex)
{
oxford.hidden = NO;
harvard.hidden = YES;
}
else
{
oxford.hidden = YES;
harvard.hidden = NO;
}
}
- (void)didReceiveMemoryWarning {
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];
// Release any cached data, images, etc that aren't in use.
}
- (void)viewDidUnload {
[super viewDidUnload];
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
- (void)dealloc {
[super dealloc];
[oxford release];
[harvard release];
}
#end
Sure, in your UIViewController's designated initializer, set the hidden property of the UIWebView instance to YES:
[webView setHidden:YES];
Then, implement the UIWebViewDelegate method - (void)webViewDidFinishLoad:, and perform an if check inside the method like this:
- (void)webViewDidFinishLoad:(UIWebView *)webView {
if([webView hidden]) {
[webView setHidden:NO];
}
}
Also, don't forget to specify that your class conforms to the UIWebViewDelegate protocol:
#interface YourViewController : UIViewController <UIWebViewDelegate> {...}
Related
I use webview in my UIVIewController and load the local HTML file in it using following method.
[self.webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:filePath]]];
If I put this method in ViewDidLoad, I can see white flicking while navigating to my controller that doesn't look good.
I tried putting this method in ViewWillAppear like below. I am using webViewLoaded flag to make sure that webview has been loaded then only show the current view else it waits but it is going in infinite loop of waiting!
- (void)viewWillAppear:(BOOL)animated {
webViewLoaded = NO;
webView.scalesPageToFit = allowZoom;
webView.dataDetectorTypes = UIDataDetectorTypeNone;
webView.delegate = self;
[self.webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:filePath]]];
int count = 0;
while(webViewLoaded == NO)
{
NSLog(#"Waiting...");
[NSThread sleepForTimeInterval:0.1];
if(count++ > 10) break;
}
}
- (void)webViewDidFinishLoad:(UIWebView *)localwebView {
webViewLoaded = YES;
}
I have also tried the same thing in ViewDidLoad but still its going in infinite loop.
Please note that webViewLoaded is "volatile BOOL" but the webview delegate method is not getting called. Not sure what's going on!
Could anyone please help me to fix this. Thanks.
First : You're blocking your main thread and not giving WebView any chance to finish loading. Never block your main thread.
Second : There are two UIWebView delegate methods : webViewDidFinishLoad and webView:didFailLoadWithError: - make sure to listen to both.
If you're trying to wait until WebView completes loading and only show your controller afterwards - use delegation or blocks. Please note that this is not a proper way of doing things - i'm just modifying your example to make it work :
child controller (with WebView) :
-(void)startLoadingWithParent:(id)_parent {
self.parent = _parent;
NSURL * url = [[NSBundle mainBundle] URLForResource:#"resource" withExtension:#"html"];
[webView loadRequest:[NSURLRequest requestWithURL:url]];
}
-(void)webViewDidFinishLoad:(UIWebView *)webView {
[parent showMe:self];
}
-(void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error {
[parent showError:error];
}
master controller :
-(void)doSomething {
SecondController * ctrl; /// secondController should be created AND has its view loaded at this point
/// ctrl = [SecondController new];
/// ctrl.view;
[ctrl startLoadingWithParent:self];
/// show user that we're doing something - display activity indicator or something
}
-(void)showMe:(UIViewController*)me {
[self.navigationController pushViewControllerAnimated:me];
}
Try using [NSURL fileURLWithPath:filePath]
use Following code for
[self performSelectorInBackground:#selector(webViewLoaded) withObject:nil];
so that it will not affect your UI
I have a view with a navigation bar in my iphone app. The navigation bar has a back button on it to go the the previous view. Within the view is a webview. If the webview is loading while the user presses the back button on the navigation bar, the app crashes with the error message:
[WebViewController respondsToSelector:]: message sent to deallocated instance
I am assuming that I release some things when the view unloads. I believe my viewWillDisapear method takes care of stopping the webview from loading... But still, when I press back while the view is not fully loaded, it crashes.
It seems to happen when I let the view finish loading about 20% of the time - and only if I press the back button really quickly after the page finishes loading.
Here is the code that I believe runs during this shinanigans....:
- (void)viewDidLoad {
[super viewDidLoad];
webView.scalesPageToFit = YES;
[webview loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString: url]]];
[addressBar setText:url];
}
and then when the webview is loading:
- (void)webViewDidStartLoad:(UIWebView *)webView {
if (first) {
//mtns is a uiimageview
mtns.alpha = .8;
first = NO;
}
[activityIndicator startAnimating];
}
and after a suggestion, I added this method:
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[self.webView stopLoading];
self.webView.delegate = nil;
}
I don't know why it is crashing so I am sort of at a loss
Thanks,
R
You could override -viewWillDisappear: to stop the UIWebView from loading and set the delegate to nil.
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
[self.webView stopLoading];
self.webView.delegate = nil;
}
This may be because of before the execution code that you have written in viewwillDisapper application crashes.try to put breakpoint and check whether viewwillDisapper method is called or not.If its not called then write the code that mark has suggested before the application navigates to second view controller.
You have to be sure to set the delegate property of the web view to nil before it goes away. Do this both in your dealloc and viewDidUnload methods, since viewDidUnload is not always called before dealloc. What’s happening is that after your view controller is deallocated, the web view still has a reference to its address, and is trying to send it a message (-respondsToSelector:).
- (void)dealloc {
webView.delegate = nil;
[super dealloc];
}
- (void)viewDidUnload {
webView.delegate = nil;
[super viewDidUnload];
}
I'm relatively new to Objective-C and coding. I've tried doing a little dev on my own but have now become stuck on what is probably a rookie error.
I've created a tab bar controller with 5 views, one such view is a UIWebView. I've got the Webview working and it loads, but when I select a different tab, the app crashes. Please find my code below and any help would be appreciated!
#import <UIKit/UIKit.h>
#interface LiveViewController : UIViewController {
IBOutlet UIWebView *liveView;
}
#property (nonatomic, retain) UIWebView *liveView;
#end
#import "LiveViewController.h"
#implementation LiveViewController
#synthesize liveView;
// The designated initializer. Override if you create the controller programmatically and want to perform customization that is not appropriate for viewDidLoad.
/*
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization.
}
return self;
}
*/
/*
// Implement loadView to create a view hierarchy programmatically, without using a nib.
- (void)loadView {
}
*/
// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad {
[self.liveView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:#"http://www.britishseapower.co.uk/live/"]]];
[super viewDidLoad];
}
- (void)webViewDidStartLoad:(UIWebView *)liveView
{
// starting the load, show the activity indicator in the status bar
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
}
- (void)webViewDidFinishLoad:(UIWebView *)liveView
{
// finished loading, hide the activity indicator in the status bar
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
}
- (void)liveView:(UIWebView *)liveView didFailLoadWithError:(NSError *)error
{
// load error, hide the activity indicator in the status bar
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
// report the error inside the webview
NSString* errorString = [NSString stringWithFormat:
#"<html><center><font size=+5 color='red'>An error occurred:<br>%#</font></center></html>",
error.localizedDescription];
[self.liveView loadHTMLString:errorString baseURL:nil];
}
- (void)viewWillDisappear:(BOOL)animated
{
if ( [self.liveView loading] ) {
[self.liveView stopLoading];
}
self.liveView.delegate = nil; // disconnect the delegate as the webview is hidden
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
}
- (void)dealloc {
[liveView release];
[UIWebView release];
[LiveViewController release];
[super dealloc];
}
#end
Many thanks,
Ryan
[UIWebView release]; [LiveViewController release];
This is what make your app crash.
It's not valid to send a release message to a class itself.
What you've done with [liveView release]; is enough (with the call to [super dealloc];.)
You should also set the delegate to nil in the dealloc method as in the viewWillDisappear method self.liveView.delegate = nil;. This way you're sure to avoid any further message sent to the LiveViewController from the UIWebView.
You should read a bit more of documentation on Objective-C to better understand how it works.
Not sure if this is related but I noticed that you aren't setting yourself as the delegate anywhere in code which means that it must be connected in Interface Builder. Now when the view disappears, you are breaking that connection, but if the view were to re-appear and wasn't previously unloaded that connection will remain broken.
One of the most common reasons why an app may crash is to refer to or send a message to an object that has been already released from the memory. And this type of bug can be easily located using NSZombieEnabled and looking into the console message. So if you haven't already tried that, that's the first thing you must do.
The problem could be in LiveViewController but could be in the other view controllers as well. I wouldn't believe the problem is 100% in LiveViewController because the view controller wouldn't try releasing its view when the view is not shown unless it gets a memory warning. And you run the app using the simulator, it's unlikely it will have a memory warning unless you simulate one.
You would probably know that a view controller never create a view unless the view is used by an object. One of the other view controllers may have a silly bug in its view loading process which causes a crash. Or, you might have released another view controller by mistake. Make 100% sure that the other view controllers have no problem showing their views on their own, when you keep changing between their views (without showing LiveViewController).
So what I would do is to try NSZombieEnabled and check if it accesses a released object, and if it does, what the object is. Also, I will make a double check that the problem is related to LiveViewController. If it doesn't help I would log a message when LiveViewController and its liveView is deallocated (for liveView you need to subclass it). Because delegate property almost always does not retain an object, if the LiveViewController object is released (which shouldn't happen) and liveView still has a reference to it in the delegate property it will make a crash.
Crashes like this are almost always related to releasing an object that has already been released and deallocated.
Let XCode help you find the error. In XCode 4:
- In the toolbar, select the scheme list, and select 'Edit Scheme'
- Select the 'Run Your.app' in the list on the left.
- Under 'Environment Variables', add the following name/value pairs in the appropriate columns:
CFZombieLevel 3
NSZombieEnabled YES
Now when debug your app, you will get a message telling when -release is called on an object that already has a -retainCount of zero. Now you have a good clue to start your investigation.
Note that these flags prevent objects from being deallocated, so it is best to turn them on as needed to prevent out of memory errors.
I want to load a PDF file in a modal view controller.
Since the moment that I don't know the size of the file, I want to load it on a separate thread, so:
- (void)viewDidLoad {
[super viewDidLoad];
[[UISharedApplication sharedApplication] setNetworkActivityIndicatorVisibile:YES];
[NSThread detachNewThreadSelector:#selector(loadPDF) toTarget:self withObject:nil];
}
- (void)loadPDF {
NSAutoreleasePool *threadPool = [[NSAutoreleasePool alloc] init];
[self.webView loadRequest:[NSURLRequest requestWithURL:url]];
[threadPool release];
[self performSelectorOnMainThread:#selector(showPDF) withObject:nil waitUntilDone:NO];
}
- (void)showPDF {
[self.webView setHidden:NO];
[self.spinner stopAnimating];
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO];
}
The method works pretty good in other files (at least I guess) where I parse some XML data, but here doesn't. Commenting:
[self performSelectorOnMainThread:#selector(showPDF) withObject:nil waitUntilDone:NO];
lets me to see the spinner and the activity indicator on the top. So I think that:
[self.webView loadRequest:[NSURLRequest requestWithURL:url]];
doesn't wait until finish and gives the control back to the caller. How can I fix it?
You can't/shouldn't (have got it to work once) update the UI on a separate thread, you should to it all on the main. As I believe webView is a UIWebView this is probably the reason.
Although you are changing the webview from hidden to visible on the main thread, all the updating and creating of the actual UIWebView is not.
Best thing to do is create the data from the URL then performSelectorOnMainThread and pass the data to a method on the main thread that updates the UIWebview with the data.
Implement the method webViewDidFinishLoad: of UIWebViewDelegate and call your showPDF there.
I got a pdf viewer in a uiwebview, from Daniel Barry http://github.com/danberry/PDF-Viewer and it works really well, it displays a pdf doc that you can scroll up and down.
Now I would like to display just one page at the time and also have a menu underneat the pdf page with a prev and next button. Can anybody help a newbe to figure out how?
Thanks!
#import "PDFViewController.h" // PDF View Controller
#implementation PDFViewController
// Synthesize UI Element properties
#synthesize webView;
// Synthesize Variable properties
#synthesize pdfURL;
#pragma mark -
#pragma mark UIViewController methods
- (void)viewDidLoad {
[super viewDidLoad];
pdfURL = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:#"stuff pdf" ofType:#"pdf"]];
[webView loadRequest:[NSURLRequest requestWithURL:pdfURL]];
webView.transform = CGAffineTransformScale( webView.transform, 1.00, 1.00 );
}
#pragma mark -
#pragma mark Memory Management
- (void)dealloc {
[webView release];
[pdfURL release];
[super dealloc];
}
#end
I think a webview should show PDFs by using a default request, no need to have a new implementation?
Anyways... There are 2 methods you could check out and link to your prev and next button
- (void)goBack:(id)sender
{
if ([webView canGoBack]) {
[webView goBack];
}
}
- (void)goForward:(id)sender
{
if ([webView canGoForward]) {
[webView goForward];
}
}
Here is the documentation of UIWebView where the methods are explained in detail: http://developer.apple.com/iphone/library/documentation/uikit/reference/UIWebView_Class/Reference/Reference.html
EDIT: Oh I think, I first misunderstood your question...
Just create an Array of the PDF URLs that you want to load. Create a counter variable that will hold the index value of the currently opened PDF.
And finally similar to the methods above create a forward and backward method and link them to the prev and next buttons. In those methods you increment or decrement the counter by 1 and load the appropriate index from the array [array objectAtIndex:counter] so the next or previous PDF is opened.
If you need more help, let me know...
ok im gonna post this as another answer...
In the header file add
NSArray *arrURLs;
NSInteger *currentIndex;
and make the array a property (retain), synthesize it in the .m file, also release it in dealloc.
In - (void)viewDidLoad initalize the array with all the URLs and set the currentIndex to 0.
arrUrls = [[NSArray alloc] initWithObjects:#"url", #"url", ..., nil];
currentIndex = 0;
add two methods that you call something like prevPage and nextPage and link them to your buttons. In those methods you do something like...
// next page
currentIndex += 1;
NSString *str = [arrUrls objectAtIndex:currentIndex];
NSURL *url = [NSURL URLWithString:str];
[webView loadRequest:[NSURLRequest requestWithURL:url]];
I would probably create another method where you set the currentIndex and check if it is in the correct bounds (e.g. should not be smaller than 0 and must be smaller than the length of the array). Than you can change the first line and let the method call handle the in/decrementing of the variable.
hope this helps.