UIWebView. Badaccess when reloading in viewDidDisappear - ios

I am trying to reload content from local file of UIWebView in viewDidDisappear of UIViewController. Badaccess is caught. But if I write the same code in viewWillDisappear, it works.
What might be the reason?
Once I've heard that UIWebView can't reload its content when it is not visible (not sure about it).
My code (hope it'll be enough):
#interface WebViewController : UIViewController <UIWebViewDelegate> {
ExtendedWebView * webView;
}
#property (nonatomic, retain) ExtendedWebView * webView;
#end
//WebViewController implementation
- (void)loadView
{
[super loadView];
WebViewCachingSingleton * webViewSingleton = [WebViewCachingSingleton sharedService];
ExtendedWebView * newWebView = [webViewSingleton getAvailableWebViewResource];//here I get ExtendedWebView. it works =)
newWebView.frame = CGRectMake(0, 0, 320, 400);
newWebView.delegate = self;
[self.view addSubview:newWebView];
self.webView = newWebView;
}
- (void)viewDidDisappear:(BOOL)animated
{
[[WebViewCachingSingleton sharedService] makeWebViewUnused:self.webView];
}
//WebViewCachingSingleton:
- (void) makeWebViewUnused : (ExtendedWebView *) aWebView
{
aWebView.isFree = YES;
[aWebView reload];
}

It will not work because viewDidDisappear is called when the view is disappeared, so all the subviews are released. The viewWillDisappear is called just before releasing all the objects associated with that view.
So you are trying to call the reload method of a UIWebView that has been already released. That is basically the reason why it crashes.
Hope it helps

The reload can be performed even if not displayed.
I tried to make an example of code to put in your state and have not had any problems.
Try to debug and enable NSZombieEnabled to see what actually happens to your application.
Try to post on any piece of code that might help us give you more details.

Related

didAddSubview not being called

my didAddSubview is not being called.
This is what I am doing.
In my viewController
- (void)viewDidLoad {
[super viewDidLoad];
SomeView *view = [[SomeView alloc] initWithFrame:CGRectMake(0.0, 0.0, CGRectGetWidth(self.view.frame), CGRectGetHeight(self.view.frame))];
[self.view addSubview:view];
}
Where my SomeView is a UI view (.h file)
#import <UIKit/UIKit.h>
#interface DyteMeetingView : UIView
#end
in which I have didAddSubview like this
- (void)didAddSubview:(UIView *)subview{
[super didAddSubview:subview];
[self someFunc];
}
I placed debugger in didAddSubview but the debugger never reaches here and hence [self someFunc]; is also not called.
Since didAddSubview is a lifecycle method for UI view, I am also not calling it from anywhere.
Can someone help me in figure out the concept I missed out on/what I could be doing wrong?
PS: I don't have any xib file for UIView.
PS: Intentionally added swift tag as well since there isn't large chunk of code and I am confident that swift developers would be able to understand the question/code and might be able to help me out.
didAddSubview in SomeView will be called when a sub view is added to an instance of SomeView, but that isn't what you are doing here. You are adding a SomeView as a subview of another view.
You want didMoveToSuperview; in this method you can use the superview property of self to identify the view that it moved to.
The didAddSubview will be called in viewController.view, instead of your SomeView, because you using viewController.view.addSubview(subview)

webViewDidFinishLoad not working?

In my app, I am trying to make a splash image appear as my UIWebView loads so it is not just a blank screen. However my webViewDidFinishLoad method will not work. This means that the splash image appears but does not disappear from the screen once the UIWebView has loaded.
My code for the method is:
- (void)webViewDidFinishLoad:(UIWebView *)webView {
NSLog(#"content loading finished");
[loadingImageView removeFromSuperview];
}
Any help on why the method will not work would be appreciated greatly.
My .h:
#interface ViewController : UIViewController
-(IBAction)makePhoneCall:(id)sender;
#property (nonatomic, strong) IBOutlet UIWebView *webView;
#property(nonatomic, strong) UIImageView *loadingImageView;
#end
My ViewDidLoad and webViewDidFinishLoading:
- (void)viewDidLoad {
UIWebView *mWebView = [[UIWebView alloc] init];
mWebView.delegate = self;
mWebView.scalesPageToFit = YES;
[super viewDidLoad];
}
//**************** Set website URL for UIWebView
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:#"http://www.sleafordpizza.com/food"]]];
//**************** Add Static loading image to prevent white "flash" ****************/
UIImage *loadingImage = [UIImage imageNamed:#"LittleItalyLogo.png"];
loadingImageView = [[UIImageView alloc] initWithImage:loadingImage];
loadingImageView.animationImages = [NSArray arrayWithObjects:
[UIImage imageNamed:#"LittleItalyLogo.png"],
nil];
[self.view addSubview:loadingImageView];
}
- (void)webViewDidFinishLoad:(UIWebView *)webView {
NSLog(#"content loading finished");
// Remove loading image from view
[loadingImageView removeFromSuperview];
}
Hi probably you do not set proper delegate.
This is small code tip for you.
-(void)viewDidLoad {
mWebView = [[UIWebView alloc] init];
mWebView.delegate = self;
mWebView.scalesPageToFit = YES;
}
-(void)webViewDidFinishLoad:(UIWebView *)webView {
[loadingImageView removeFromSuperview];
NSLog(#"finish");
}
In you're .h file add.
#interface MyView: UIViewController <UIWebViewDelegate> {
UIWebView *webView;
}
Code fixes.
For .h file
#interface ViewController : UIViewController<UIWebViewDelegate>
-(IBAction)makePhoneCall:(id)sender;
#property (nonatomic, strong) IBOutlet UIWebView *webView;
#property(nonatomic, strong) UIImageView *loadingImageView;
#end
For .m file
- (void)viewDidLoad
{
[super viewDidLoad];
webView.delegate = self;
//**************** Set website URL for UIWebView
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:#"http://www.sleafordpizza.com/food"]]];
//**************** Add Static loading image to prevent white "flash" ****************/
UIImage *loadingImage = [UIImage imageNamed:#"LittleItalyLogo.png"];
loadingImageView = [[UIImageView alloc] initWithImage:loadingImage];
loadingImageView.animationImages = [NSArray arrayWithObjects:
[UIImage imageNamed:#"LittleItalyLogo.png"],
nil];
[self.view addSubview:loadingImageView];
}
At certain times, this delegate method actually never gets fired. I have had severe problems with the same thing in some of my projects.
At one occasion, I actually had to solve it with a timer, checking the state of the web view every second or so to see if I could proceed.
In that particular case, I just needed a certain element to be present. Still, the view did not trigger the finish loading event, due to external script errors being injected.
So, I just started a trigger when the web view begun loading, then called a method every now and then to see if the web view contained the element in question.
- (void)methodCalledByTimer {
if (<I still do not have what I need>) {
//The web view has not yet finished loading; keep checking
} else {
//The web view has finished loading; stop the timer, hide spinners and proceed
}
}
You could also check if the web view is actually loading, if that is absolutely necessary:
- (void)methodCalledByTimer {
if (self.webView.isLoading) {
//The web view has not yet finished loading; keep checking
} else {
//The web view has finished loading; stop the timer, hide spinners and proceed
}
}
Then, naturally, I'd check for the finishedLoading event as well, just to be sure. Remember to also implement the webView:didFailLoadWithError: method as well.
When waiting for a web page to finish loading, there are some things to keep in mind.
For instance, do you really need it to stop loading, or is there anything else you can do? In my case, I needed an element. Being able to properly execute a script is another thing that may be required.
Second, is the loading page using any external resources? I once had external script errors causing the webViewDidFinishLoad: method to not being called at all. If I removed the external scripts, it worked.
Third, if the page is using external resources, you are exposed not only to the loading capacity of your own resources, but that of the external resources as well. Tracking scripts, ads etc...if one resource provider is delivering content sloooowly (or not at all), you could page could be stuck in loading state forever.
So, I'd go with checking for something else. :)
I see you aren't handling errors. If there is an error, all subsequent delegate calls will not happen. I was surprised to find that this is true when the webview uses a plugin too. It calls this error method telling you that the webview handed off to the delegate, in my case the movie player.
implement this and see if that is it.
-(void) webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error
{
if (error.code == 204) {
//request was handled by a plugin instead of by the webview directly
...
}
else
{
NSLog(#"didFailLoadWithError. ERROR: %#", error);
}
}
I was able to do all the remaining loading work in this method instead of the webviewdidfinishLoad

How do I keep scroll position when navigating in a navigation controller?

have been looking around, but I have not found an answer that helped me out. I am wondering how I can keep the scroll position in a scrollview when I move forwards and backwards in a navigation controller. Currently, when I go to another view in the navigation controller, the scroll view resets and is at the top when I go back. Any code would be greatly appreciated. Sorry if my explanation was not good enough. So far this is what I have, I know that this won't work, but what should I do?
- (void)viewWillAppear:(BOOL)animated {
scrollView.contentOffset.y = scrollSave;
[super viewWillAppear:YES];
}
- (void)viewWillDisappear:(BOOL)animated {
CGFloat scrollSave = scrollView.contentOffset.y;
[super viewWillDisappear:animated];
}
The code you have above shouldn't even compile. You could save this scroll position in another variable, but generally the scroll position shouldn't be resetting itself anyway. You should remove any code that is trying to manipulate the content offset at all, and see if it restores it to the correct scroll position upon returning to the view.
With your current approach, scrollSave is a local variable declared in viewWillDisappear so you cannot access the value in viewWillAppear. You will need to create an instance variable or a property. (If the view controller is still in memory, it should maintain the contentOffset so this might not be necessary)
#interface MyViewController ()
#property (nonatomic) CGPoint scrollSave;
#end
#implementation MyViewController
- (void)viewWillAppear:(BOOL)animated {
[scrollView setContentOffset:self.scrollSave];
[super viewWillAppear:YES];
}
- (void)viewWillDisappear:(BOOL)animated {
self.scrollSave = scrollView.contentOffset;
[super viewWillDisappear:animated];
}
Now there is an issue with persistence. If the you navigate back in the navigation controller, this view controller will be released from memory. If you need to save the users position then I would suggest using NSUserDefaults.
#interface MyViewController ()
#property (nonatomic) NSUserDefaults *defaults
#end
#implementation MyViewController
- (void)viewWillAppear:(BOOL)animated {
CGPoint scrollSave = CGPointFromString([_defaults objectForKey:#"scrollSaveKey"]);
scrollView.contentOffset = scrollSave;
[super viewWillAppear:YES];
}
- (void)viewWillDisappear:(BOOL)animated {
[_defaults setObject:NSStringFromCGPoint(scrollView.contentOffset) forKey:#"scrollSaveKey"];
[_defaults synchronize];
[super viewWillDisappear:animated];
}
Kyle's answer should get you storing the variable okay, but in regards to assigning the contentOffset.y, trying animating the scrollview.
[scrollView scrollRectToVisible:
CGRectMake(scrollView.frame.origin.x, scrollSave.y, scrollView.frame.size.width, scrollView.frame.size.height)
animated:NO];
That code would go into viewWillAppear.

Cleaver (PhoneGap / Cordova as component) not working in iOS

I tried to use Cleaver, that is PhoneGap / Cordova as a component of an existing iOS application. I followed this step by step guide twice, without success.
My application can easily instantiate a web view, and i can pass content to it using the stringbyevaluatingjavascriptfromstring method, but as soon as i try to access PhoneGap API (navigator.compass, navigator.network, ...), nothing seems to work. console.log(navigator) shows no sign of Cordova objects (console.log does not output anything is Xcode, i'm using http://debug.phonegap.com/). The deviceready event is not triggered either.
In my html file, i include PhoneGap js file cordova-1.7.0rc1.js which i copied from another project (since /www folder is missing when you use PhoneGap as a component).
My ViewController.h imports <Cordova/CDVViewController.h>.
Here my ViewController.m
//
// ViewController.m
// CordovaComponent
//
#import "ViewController.h"
#interface ViewController (){
CDVViewController *cdvViewController ;
}
#end
#implementation ViewController
- (void)pushPhoneGap:(id)sender {
cdvViewController = [[CDVViewController alloc] init];
cdvViewController.wwwFolderName = #"www";
cdvViewController.startPage = #"index.html";
cdvViewController.view.frame = self.view.bounds;
cdvViewController.webView.delegate = self;
[self.navigationController pushViewController:cdvViewController animated:YES];
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
UIButton * button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[button setTitle:#"Push PhoneGap view" forState:UIControlStateNormal];
[button addTarget:self action:#selector(pushPhoneGap:) forControlEvents:UIControlEventTouchUpInside];
button.frame = CGRectMake(60, 50, 200., 50.);
[self.view addSubview:button];
}
- (void)viewDidUnload
{
[super viewDidUnload];
// Release any retained subviews of the main view.
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown);
}
- (void)webViewDidFinishLoad:(UIWebView *)webView {
NSString *jsReturn = [cdvViewController.webView stringByEvaluatingJavaScriptFromString:#"setArray([1, 1, 2, 3, 5, 8, 13, 21, 34, 1]);"];
NSLog(jsReturn);
}
#end
Do you have an idea of what's happening ? Any help would be greatly appreciated !
Okay, after many hours of trying to solve this issue, I finally found what was going wrong.
This is the line that messed things up:
cdvViewController.webView.delegate = self;
You can't just override the delegate of the view controllers's web view. The original delegate has instantiation code which enables PhoneGap to run. Don't override it and everything will be fine !
If you want to get notified about webview events without breaking Cordova you could try something like this:
#interface WrappingWebViewDelegate : NSObject <UIWebViewDelegate>
#property (nonatomic,retain) id<UIWebViewDelegate> wrappedDelegate;
#end
#implementation WrappingWebViewDelegate
- (void)webViewDidStartLoad:(UIWebView*)theWebView {
[self.wrappedDelegate webViewDidStartLoad: theWebView];
// your code
}
// implement all other delegate methods and forward them to the wrapped delegate
[...]
#end
Usage like this:
cdvViewController = [[CDVViewController alloc] init];
WrappingWebViewDelegate wrappingDelegate = [[WrappingWebViewDelegate alloc] init];
wrappingDelegate.wrappedDelegate = cdvViewController.webView.delegate;
cdvViewController.webView.delegate = wrappingDelegate;
I haven't tested this, so you should be careful.

UITableView headings shown on top of MBProgressHUD

So I have a subclass of UITableViewController that loads some data from the internet and uses MBProgressHUD during the loading process. I use the standard MBProgressHUD initialization.
HUD = [[MBProgressHUD alloc] initWithView:self.view];
[self.view addSubview:HUD];
HUD.delegate = self;
HUD.labelText = #"Loading";
[HUD show:YES];
This is the result:
.
Is there any way to resolve this issue, or should I just abandon MBProgressHUD?
Thanks!
My solution was pretty simple. Instead of using self's view, I used self's navigationController's view.
HUD = [[MBProgressHUD alloc] initWithView:self.navigationController.view];
[self.navigationController.view addSubview:HUD];
This should work for the OP because his picture shows he's using a UINavigationController. If you don't have a UINavigationController, you might add another view on top of your UITableView, and add the HUD to that. You'll have to write a little extra code to hide/show this extra view.
An unfortunate thing with this simple solution (not counting my idea adding another view mentioned above) means the user can't use the navigation controls while the HUD is showing. For my app, it's not a problem. But if you have a long running operation and the user might want to press Cancel, this will not be a good solution.
It's probably because self.view is a UITableView, which may dynamically add/remove subviews including the headers, which could end up on top of the HUD after you add it as a subview. You should either add the HUD directly to the window, or (for a little more work but perhaps a better result) you could implement a UIViewController subclass which has a plain view containing both the table view and the HUD view. That way you could put the HUD completely on top of the table view.
My solution was:
self.appDelegate = (kmAppDelegate *)[[UIApplication sharedApplication] delegate];
.
.
_progressHUD = [[MBProgressHUD alloc] initWithView:self.appDelegate.window];
.
[self.appDelegate.window addSubview:_progressHUD];
Works like a charm for all scenarios involving the UITableViewController. I hope this helps someone else. Happy Programming :)
Create a category on UITableView that will take your MBProgressHUD and bring it to the front, by doing so it will always appear "on top" and let the user use other controls in your app like a back button if the action is taking to long (for example)
#import "UITableView+MBProgressView.h"
#implementation UITableView (MBProgressView)
- (void)didAddSubview:(UIView *)subview{
for (UIView *view in self.subviews){
if([view isKindOfClass:[MBProgressHUD class]]){
[self bringSubviewToFront:view];
break;
}
}
}
#end
A simple fix would be to give the z-index of the HUD view a large value, ensuring it is placed in front of all the other subviews.
Check out this answer for information on how to edit a UIView's z-index: https://stackoverflow.com/a/4631895/1766720.
I've stepped into a similar problem a few minutes ago and was able to solve it after being pointed to the right direction in a different (and IMHO more elegant) way:
Add the following line at the beginning of your UITableViewController subclass implementation:
#synthesize tableView;
Add the following code to the beginning of your init method of your UITableViewController subclass, like initWithNibName:bundle: (the beginning of viewDidLoad might work as well, although I recommend an init method):
if (!tableView &&
[self.view isKindOfClass:[UITableView class]]) {
tableView = (UITableView *)self.view;
}
self.view = [[UIView alloc] initWithFrame:[UIScreen mainScreen].applicationFrame];
self.tableView.frame = self.view.bounds;
self.tableView.contentInset = UIEdgeInsetsMake(0.0, 0.0, 0.0, 0.0);
[self.view addSubview:self.tableView];
Then you don't need to change your code you posted in your question any more. What the above code does is basically seperating the self.tableView from self.view (which was a reference to the same object as self.tableView before, but now is a UIView containing the table view as one might expect).
I've Just solved that issue manually , it has been 2 years since Chris Ballinger asked but maybe someone get used of what is going on here.
In UITableViewController i execute an HTTP method in viewDidLoad , which is running in background so the table view is loaded while the progress is shown causing that miss.
i added a false flag which is changed to yes in viewDidLoad, And in viewDidAppear something like that can solve that problem.
-(void) viewDidAppear:(BOOL)animated{
if (flag) {
[self requestSomeData];
}
flag = YES;
[super viewDidAppear:animated];
}
I had the same problem and decided to solve this by changing my UITableViewController to a plain UIViewController that has a UITableView as a subview (similar to what jtbandes proposed as an alternative approach in his accepted answer). The advantage of this solution is that the UI of the navigation controller isn't blocked, i.e. users can simply leave the ViewController in case they don't want to waiting any longer for your timely operation to finish.
You need to do the following changes:
Header file:
#interface YourViewController : UIViewController <UITableViewDelegate, UITableViewDataSource>
- (id)initWithStyle:(UITableViewStyle)style;
#end
Implementation file:
#interface YourViewController ()
#property (nonatomic, retain) UITableView *tableView;
#property (nonatomic, retain) MBProgressHUD *hud;
#end
#implementation YourViewController
#pragma mark -
#pragma mark Initialization & Memory Management
- (id)initWithStyle:(UITableViewStyle)style;
{
self = [super init];
if (self) {
// create and configure the table view
_tableView = [[UITableView alloc] initWithFrame:CGRectNull style:style];
_tableView.delegate = self;
_tableView.dataSource = self;
}
return self;
}
- (void)dealloc
{
self.tableView = nil;
self.hud = nil;
[super dealloc];
}
#pragma mark -
#pragma mark View lifecycle
- (void)loadView {
CGRect frame = [self boundsFittingAvailableScreenSpace];
self.view = [[[UIView alloc] initWithFrame:frame] autorelease];
// add UI elements
self.tableView.frame = self.view.bounds;
[self.view addSubview:self.tableView];
}
- (void)viewWillDisappear:(BOOL)animated
{
// optionally
[self cancelWhateverYouWereWaitingFor];
[self.hud hide:animated];
}
The method -(CGRect)boundsFittingAvailableScreenSpace is part of my UIViewController+FittingBounds category. You can find its implementation here: https://gist.github.com/Tafkadasoh/5206130.
In .h
#import "AppDelegate.h"
#interface ViewController : UITableViewController
{
MBProgressHUD *progressHUD;
ASAppDelegate *appDelegate;
}
In .m
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
appDelegate = (ASAppDelegate *) [UIApplication sharedApplication].delegate;
progressHUD = [MBProgressHUD showHUDAddedTo:appDelegate.window animated:YES];
progressHUD.labelText = #"Syncing To Sever";
[appDelegate.window addSubview:progressHUD];
This should work.
[MBProgressHUD showHUDAddedTo:self.navigationController.view animated:YES];
And to remove you can try
[MBProgressHUD hideHUDForView:self.navigationController.view animated:YES];

Resources