Frustrating UIWebView Delegate Crash issue - iphone

I've created an ARC Application that run's perfect. It's got a UINavigationController that I use to push through the views and everything runs fine.
I'm converting the Application to iPad and i've decided to show one of the views as a popover. (I don't like UIPopoverController so i've created my own basic popup). It's added to the view as follows..
MyViewController *hotelinf = [[MyViewController alloc]
initWithNibName:#"MyViewController_iPad" bundle:nil];
[self.view addSubview:hotelinf.view];
The view is added as a subview fine. The view i'm adding contains a UIWebView that has the usual delegate methods in it, but when the view tries to access one of the delegates it simply crashes.
*** -[MyViewController respondsToSelector:]: message sent to deallocated instance 0x7917b90
Specifically, it crashes on this line..
[self.webView loadHTMLString:stringResponse baseURL:nil];
I've displayed views (and UINavigationControllers) as subViews many of times without any issues, although none of them included a UIWebView delegate. I'm guessing I have to set the delegate of my UIViewController istance but i'm not sure how. It's also worth noting that if I push the view in my existing UINavigationController it calls and loads the HTML fine, which surely means it has nothing to do with the code of the view itself.
Any help would be greatly appreciated! Here is the code (in addition to above that shows the controller)..
.h
#interface MyViewController : UIViewController <UIWebViewDelegate, UIActionSheetDelegate> {
//Unrelated IBOutlets
}
#property (nonatomic, strong) UIWebView *webView;
#end
.m
#implementation MyViewController
#synthesize webView;
- (void)viewDidLoad
{
[super viewDidLoad];
self.webView = [[UIWebView alloc]initWithFrame:CGRectMake(317,283,393,354)];
self.webView.delegate = self;
[self.view addSubview:self.webView];
[NSThread detachNewThreadSelector:#selector(getHTMLString) toTarget:self withObject:nil];
}
-(void)getHTMLString {
#autoreleasepool {
//Download a valid HTML String
[self performSelectorOnMainThread:#selector(loadHTML) withObject:nil waitUntilDone:NO];
}
}
- (void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
self.webView = nil;
}
-(void)loadHTML {
self.webView.opaque = NO;
self.webView.backgroundColor = [UIColor clearColor];
if ([stringResponse isEqualToString:#""]) {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Error" message:#"Could not connect to XXXXX.com. Please verify you are connected to a working 3G/WIFI Network." delegate:self cancelButtonTitle:#"OK" otherButtonTitles:nil];
[alert show];
} else {
//it crashes here only when loaded as a subview - the first access to the delegate
[self.webView loadHTMLString:stringResponse baseURL:nil];
}
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
}
- (void)webViewDidFinishLoad:(UIWebView *)webView {
[self stopIndicator];
}
- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error
{
if (error.code == NSURLErrorCancelled) return; // this is Error -999
[self stopIndicator];
// report the error inside the webview
NSString* errorString = [NSString stringWithFormat:
#"<html><center><font size=+10 color='black' face='Helvetica'>An error occurred:<br>%#</font></center></html>",
error.localizedDescription];
[self.webView loadHTMLString:errorString baseURL:nil];
UIAlertView *alert = [[UIAlertView alloc]
initWithTitle:#"Cannot load URL."
message:#"You have a connection failure. Please verify you are connected to a WIFI or 3G enabled Network."
delegate:self cancelButtonTitle:#"Ok" otherButtonTitles:nil];
[alert show];
}
#end

The issue has nothing to do with the UIWebView, rather with your controller class. Indeed,
MyViewController *hotelinf = [[MyViewController alloc]
initWithNibName:#"MyViewController_iPad" bundle:nil];
[self.view addSubview:hotelinf.view];
You are allocating the controller and assigning it to a local variable; then you add the controller's view as subview to your current view. Doing that, that view is retained, but what happens to the controller object itself? Are you releasing it? Or it leaks (since it is assigned to a local variable)?
This possibly explains why when later the respondsToSelector method is called, the controller has already been deallocated...
A way to fix this is creating a new property or an ivar in your main controller class and store MyViewController in there. Don't forget to release it in dealloc.
I would also suggest another thing. In:
- (void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
self.webView = nil;
}
set the webView delegate to nil before releasing the view:
- (void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
self.webView.delegate = nil;
self.webView = nil;
}
And I would also possibly review the reason why you release the webView in viewDidDisappear. On the other hand you allocate it in viewDidLoad. This asymmetry is dangerous, since whenever the main view disappears (for any reason) the webView will be removed and when the view reappears, it is not there anymore.

Better add all the delegate methods. You havent added the first two. Most probably, your code is crashing when message webViewDidStartLoad is sent
– webView:shouldStartLoadWithRequest:navigationType:
– webViewDidStartLoad:
– webViewDidFinishLoad:
– webView:didFailLoadWithError:

Related

Why is my web view fail delegate not working?

.h
IBOutlet UIWebView *webview;
.m
- (void)viewDidLoad {
[webview loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:#"http://blabla.com"]]];
}
- (void)webView:(UIWebView *)webViewfail didFailLoadWithError:(NSError *)error {
if([webViewfail isEqual:webview]) {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Connection Failed"
message:#"Check your Internet connection before refreshing."
delegate:self
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[alert show];
}
}
I do not know what I did wrong, I tried making it appear but setting off internet connection. Any tips or suggestions will be helpful.
Make sure you have hooked up the delegate in interface builder (if that is what you are using). You can do this by selecting the web view, and drag (while holding control) to the view controller / file's owner, and then select delegate (which pops up when you release the mouse).
Or you can hook up your delegate in code in your viewDidLoad method like so:
[webView setDelegate:self]
Make sure your view controller conforms to the UIWebViewDelegate protocol by adding in your .h
#interface MyViewController : UIViewController <UIWebViewDelegate>
Good luck :)
Check this:
Make sure the object containing the quoted method is indeed the web view's delegate.
Give the web view's url request a really short timeoutInterval.
Try this also:
In your ViewController.h
#interface ViewController : UIViewController <UIWebViewDelegate>
And in ViewController.m below [super viewDidLoad] add :
self.webView.Delegate = self;

MFMailComposeViewController in the delegate

The question deals with an application which uses many views in a UINavigation controller Style.
I have a simple function in my delegate which can be used by all views to plot-out error message
// In Appdelegate.m
-(void)popErrorWindow:(NSString *)theError
{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:nil message:theError
delegate:self cancelButtonTitle:#"OK" otherButtonTitles:#"Report",nil];
[alert show];
[alert release];
}
- (void)alertView:(UIAlertView *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex {
if (buttonIndex == 1)
{
NSLog(#"report");
[self mailIt:#"error name"];
}
}
Now, wanting to have a mechanism that will email the error along with some other data I have created this:
-(void)mailIt:(NSString *)theError {
NSLog(#"Mail it");
pickerMail = [[MFMailComposeViewController alloc] init];
pickerMail.mailComposeDelegate = self;
[pickerMail setSubject:#"error via email"];
NSMutableString *body = [NSMutableString string];
[body appendString:#"Error XXX "];
[pickerMail setMessageBody:body isHTML:YES];
// Problem here:
[self.window presentModalViewController:pickerMail animated:YES];
}
- (void)mailComposeController:(MFMailComposeViewController*)controller didFinishWithResult:(MFMailComposeResult)result error:(NSError*)error
{
// Problem here:
[self.window dismissModalViewControllerAnimated:YES];
//NSLog(#"mail was sent");
}
The problem is in self.window , which is not the right way to access this from the delegate,
I still want to have the mail element in the delegate as all views can call the error alert, and I would like to have only one place for this mechanism.
How should I do it from inside the delegate, what should replace the self.window?
Perhaps reimplementing popErrorWindow: and mailIt: in a category on UIViewController. This way you have access to the top-level view controller to call presentModalViewController and dismissModalViewControllerAnimated on.
Alternatively you can do this in a subclass of UIViewController and then make your other custom view controller's subclass of that. The downside to this method is when you have subclasses of classes other than UIViewController
- (void)mailComposeController:(MFMailComposeViewController *)controller
didFinishWithResult:(MFMailComposeResult)result
error:(NSError *)error
{
[controller dismissModalViewControllerAnimated:YES];
}
EDIT :
The - (void)presentModalViewController:(UIViewController *)vc and - (void)dismissModalViewControllerAnimated:(BOOL)animated methods are an UIViewController instance method, so you cannot use it with an UIWindow.
In order to present your mail controller with a nice animation, you can do that :
UIViewController *aController = self.navigationController.presentedViewController;
[aController presentModalViewController:pickerMail animated:YES];

-[UINavigationController pushViewController:animated:] crashes with no error in console

Update
I swapped out completely different code for pushViewController, and it is still crashing... seems like pushViewController is not the culprit. Here is what I added instead:
NSString *videoURL = [[NSString alloc] initWithFormat:#"http://www.vimeo.com/m/#/%#", videoID];
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:videoURL]];
It opens up the URL in Safari, and then crashes.. wtf?
PushViewController crashes with no error in the console, but I do get an EXC_BAD_ACCESS error in Xcode. The crash doesn't happen until after the view controller has been pushed... but the view its pushing is empty... no code to mess up.
My code is below:
MainViewController.m
PlayVimeo *playTest = [[PlayVimeo alloc] initWithNibName:#"PlayVimeo" bundle:nil];
//playTest.videoID = videoID;
[self.navigationController pushViewController:playTest animated:YES];
[playTest release];
PlayVimeo.m
#import "PlayVimeo.h"
#import "SVProgressHUD.h"
#implementation PlayVimeo
#synthesize videoID, wView;
-(void)viewDidLoad {
[super viewDidLoad];
//Show loading alert
[SVProgressHUD showInView:self.view status:#"Loading Video..."];
}
-(void)viewWillAppear:(BOOL)animated {
NSLog(#"Play View Loaded!");
[self vimeoVideo];
}
-(void)vimeoVideo {
NSLog(#"Video ID: %#", videoID);
NSString *html = [NSString stringWithFormat:#"<html>"
#"<head>"
#"<meta name = \"viewport\" content =\"initial-scale = 1.0, user-scalable = no, width = 460\"/></head>"
#"<frameset border=\"0\">"
#"<frame src=\"http://player.vimeo.com/video/%#?title=0&byline=0&portrait=1&autoplay=1\" width=\"460\" height=\"320\" frameborder=\"0\"></frame>"
#"</frameset>"
#"</html>",
videoID];
NSLog(#"HTML String: %#", html);
[wView loadHTMLString:html baseURL:[NSURL URLWithString:#""]];
//Dismiss loading alert
[SVProgressHUD dismissWithSuccess:#"Playing..."];
}
- (void)viewDidUnload {
[super viewDidUnload];
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
return (interfaceOrientation == UIInterfaceOrientationLandscapeLeft || interfaceOrientation == UIInterfaceOrientationLandscapeRight);
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}
-(void)dealloc {
[super dealloc];
}
Navigation Controller Code:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
self.window.rootViewController = self.navigationController;
[self.window makeKeyAndVisible];
[Appirater appLaunched];
return YES;
}
Console on crash:
sharedlibrary apply-load-rules all
Current language: auto; currently objective-c
(gdb)
It's likely that the culprit is
[playTest release];
Without seeing the rest of your code, I would still say that you likely need to release this after you're done with the video.
The code can not be fixed, it seems. With the UIWebView class reference, there is an example program TransWeb. Take this as base, it has a window and a navigation controller with a webview in it (in the xib). In MyViewController it reads a html-file and displays it. What you need to do is to change the main view to landscape and replace the html-code with yours. Avoid the frame-stuff.

'NSObject' may not respond to -navigationController

Hi there I currently I have a warning on a line of code where I am trying to push a new view onto the screen.
Outline // my NSObject receives a code=1 from my server I have set up. Everything works fine the code comes through which then initializes an AlertView where I have set up an if statement to catch the button click of my AlertView message. When that button is pressed my application falls over.
I have declared my ViewController of the view I am trying to push in its header file and there are no errors just the warning when compiled.
this is my NSObject I have made
/////.h
#import <Foundation/Foundation.h>
#interface alerts : NSObject {
}
- (void)pleaseRegisterDevice;
#end
/////.m
#import "alerts.h"
#import "instaCode1_3AppDelegate.h"
#import "RegisterDeviceViewController.h"
#implementation alerts
//use this alert when phone falls out of sync
- (void)pleaseRegisterDevice {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Please Register Device"
message:#"click OK to register"
delegate:self
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[alert autorelease];
[alert show];
}
//Catch pleaseRegisterDevice method
- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex {
NSString *buttonTitle=[alertView buttonTitleAtIndex:buttonIndex];
if ([buttonTitle isEqualToString:#"OK"]) {
NSLog(#"msg from alertView method");
//open new wndow
RegisterDeviceViewController *regViewController = [[RegisterDeviceViewController alloc] init];
//Push it onto the top pf the navigation controller's stack
**[[self navigationController] pushViewController:regViewController animated:YES];**
}
else {
NSLog(#"was not able to push view");
}
}
- (void)dealloc {
[super dealloc];
}
#end
I have bolded the line of code where I get the warning 'alerts' may not respond to -navigationController
any help would be greatly appreciated.
I dont think an NSObject subclass has a UINavigationController...
You need to get a pointer to your app delegate's navigation controller like so
MyAppDelegate *appDelegate = (MyAppDelegate *)[[UIApplication sharedApplication] delegate];
[appDelegate.navigationController pushViewController:regViewController animated:YES];
navigationController is a property defined on a UIViewController. A NSObject does not have this method.
You don't have any instance member or method called navigationController, hence the warning.

View loaded from nib will not update contents of UILabel while running in a method

I am having an issue with updating the contents of an "myInfoBox" object I created to be displayed while some background processes are done.
In the delegate method I am creating a new viewController:
-(void)loadMainView
{
myFirstViewController = [[MyFirstViewController alloc] initWithNibName:#"MyFirstView" bundle:nil];
navigationController = [[UINavigationController alloc] initWithRootViewController:myFirstViewController];
// myFirstViewController was retained again by the controller, release one
[myFirstViewController release];
navigationController.navigationBar.hidden = YES;
[window addSubview:navigationController.view];
[window makeKeyAndVisible];
// the next method is run after the "viewDidLoad" is finished loading
[myFirstViewController loadAlertViewForNewUser];
}
Following is my implementation of "myFirstViewController", it creates an instance of the "infoBox" class(I will show its code later):
- (void)viewDidLoad {
self.navigationController.navigationBar.frame = CGRectMake(0, 0, 0, 0);
self.navigationController.navigationBar.bounds = CGRectMake(0, 0, 0, 0);
self.myInfoBox = [[InfoBoxController alloc] initWithNibName:#"InfoBox" bundle:[NSBundle mainBundle]];
CGRect infoBoxFrame;
infoBoxFrame = CGRectMake(60, 120, 200, 200);
myInfoBox.view.frame = infoBoxFrame;
myInfoBox.i_statusLabel.text = #"Downloading Account Updates";
myInfoBox.i_titleLabel.text = #"Updating";
// disabled for testing
//myInfoBox.view.hidden = YES;
[self.view addSubview:myInfoBox.view];
[super viewDidLoad];
}
// this method is called after the view has been loaded by the delegate
- (void)loadAlertViewForNewUser
{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Welcome!" message:#"Connect to download stuff from your account?"
delegate:self cancelButtonTitle:#"Cancel" otherButtonTitles:#"OK", nil];
alert.tag = 0;
[alert show];
}
// implementation of the alertview delegate
- (void)alertView:(UIAlertView *)actionSheet didDismissWithButtonIndex:(NSInteger)buttonIndex
{
if (actionSheet.tag == 0)
{
if (buttonIndex == 0)
{ NSLog(#"button 0 was pressed"); }
if (buttonIndex == 1)
{
// this is the button that is pressed
[actionSheet removeFromSuperview];
[actionSheet release];
// tried using this also
//[self performSelectorOnMainThread:#selector(userInitialSetupMainThread) withObject:nil waitUntilDone:NO];
// do stuff and update the infobox about it
[self loadInfoBoxInitialUserSetup];
// tried using this as well
//[self performSelectorInBackground:#selector(loadInfoBoxInitialUserSetup) withObject:nil];
}
return;
}
}
- (void)loadInfoBoxInitialUserSetup
{
[self performSelectorOnMainThread:#selector(userInitialSetupMainThread) withObject:nil waitUntilDone:NO];
}
- (void)userInitialSetupMainThread
{
// fetch JSON data
NSDictionary *responseJSON = [NSDictionary dictionaryWithDictionary:[self getUserstuff]];
self.myInfoBox.i_statusLabel.text = #"Processing Recieved information";
// breakpoint - nothing changes in the view on the simulator
[myInfoBox.view setNeedsLayout];
// breakpoint - nothing changes in the view on the simulator
[myInfoBox.view setNeedsDisplay];
// breakpoint - nothing changes in the view on the simulator
[myInfoBox.parentViewController.view setNeedsLayout];
// breakpoint - nothing changes in the view on the simulator
[myInfoBox.parentViewController.view setNeedsDisplay];
// breakpoint - nothing changes in the view on the simulator
[myInfoBox performSelectorOnMainThread:#selector(updateValuesForTitle:) withObject:#"test" waitUntilDone:YES];
// breakpoint - nothing changes in the view on the simulator
[self.view setNeedsLayout];
// breakpoint - nothing changes in the view on the simulator
[self.view setNeedsDisplay];
// breakpoint - nothing changes in the view on the simulator
self.myInfoBox.i_statusLabel.text = #"Reloading...";
// breakpoint - nothing changes in the view on the simulator
[self readStuffFromDB];
sleep(2);
//disabled view removal for testing..
//[self.myInfoBox.view removeFromSuperview];
// breakpoint - nothing changes in the view on the simulator
}
What happens for me in the testing is that the myInfoBox object is created on screen when the -(void)loadMainView method is complete, then I can see on screen the "myInfoBox" in the background while the alertView in front (for testing...) at this point the screen is responsive and I can select the YES, once I select yes the delegate method is called.
As I commented in the source file, using breakpoints I am monitoring the simulator and following the code, never the less the changed label values are not reflected while I am still in the - (void)userInitialSetupMainThread method, but once it finishes the view updates with the latest set .text value!! grrr..
Also, the source for the myInfoBox class:
#interface InfoBoxController : UIViewController {
IBOutlet UILabel* i_titleLabel;
IBOutlet UILabel* i_statusLabel;
IBOutlet UIImageView* i_loadingImage;
IBOutlet UIImageView* i_background;
IBOutlet UIActivityIndicatorView* i_activityIndicator;
}
#property(nonatomic, retain) IBOutlet UILabel* i_titleLabel;
#property(nonatomic, retain) IBOutlet UILabel* i_statusLabel;
#property(nonatomic, retain) IBOutlet UIImageView* i_loadingImage;
#property(nonatomic, retain) IBOutlet UIImageView* i_background;
#property(nonatomic, retain) IBOutlet UIActivityIndicatorView* i_activityIndicator;
//- (void)updateValuesForTitle:(NSString *)title Label:(NSString *)label;
- (void)updateValuesForTitle:(NSString *)title;
#end
#implementation InfoBoxController
#synthesize i_titleLabel, i_statusLabel, i_loadingImage, i_background;
#synthesize i_activityIndicator;
//-(void)updateValuesForTitle:(NSString *)title Label:(NSString *)label
-(void)updateValuesForTitle:(NSString *)title
{
self.i_titleLabel.text = title;
self.i_statusLabel.text = title;
[self.i_titleLabel setNeedsDisplay];
[self.i_statusLabel setNeedsDisplay];
}
Sorry for the LOONG post :)
PLEASE ASSIST!
At the risk of sounding unhelpful, that's kind of just how it works. If you have long-running code in the main event loop (i.e., you don't explicitly create a thread or similar), the operating system won't be able to update the UI.
To update the UI while your code is running, you either need to run your complex operation in the back ground using thread, NSOperationQueue, etc, or just break it into smaller steps and return control to the main loop occasionally so that the UI can be updated.