I have a UIWebView and want to display an activity indicator while it is loading. And then hide it when the webViewDidFinishLoading. The only problem is that I am getting this NSURLErrorDomain error -999 thing going on. After searching around I found this fix which works to not display any error message but my webViewDidFinishLoading doesn't ever get called to get rid of my activity indicator and other stuff that I have going on. I guess I could just make a call to the didFinishLoading method in my webViewDidFailWithError method if it fails with -999 but that seems super hacky and wrong. Any ideas on how to fix this?
edit*
I have figured out where the webview was being asked to load twice so I was able to get rid of the error -999. However, it seems neither of the delegate methods are being called unless I try to load the webview twice (in which case the webViewDidFinishLoading method is only called once).
- (void)viewDidLoad
{
self.webView.delegate = self;
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(refresh)
name:#"DidBecomeActive"
object:nil];
[super viewDidLoad];
}
-(void)refresh{
[self.webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:#"http://myapp.com/app/"]]];
self.webView.scrollView.bounces = NO;
}
- (void)webViewDidFinishLoading:(UIWebView *)wv
{
NSLog(#"finished loading");
[self.activityInd stopAnimating];
[self.view bringSubviewToFront:wv];
}
- (void)webView:(UIWebView *)wv didFailLoadWithError:(NSError *)error
{
NSLog(#"Failed: %#", error);
if([error code] == NSURLErrorCancelled){
return;
}
[self.activityInd stopAnimating];
[[[UIAlertView alloc] initWithTitle:#"Can't find server" message:#"Check your internet connection and try again." delegate:nil cancelButtonTitle:#"OK" otherButtonTitles: nil]show];
}
Did u assigned the UIWebView delegates to its object like the following
webViewObject.delegate = self
Copy/Paste error.
- (void)webViewDidFinishLoading:(UIWebView *)wv
Should be changed to:
- (void)webViewDidFinishLoad:(UIWebView *)wv
implemente the protocol "UIWebViewDelegate" in .h file
In case you ended up here because you're wondering why your delegate methods aren't being called when the bug wasn't due to an incorrectly written delegate method:
Say your webview is normally instantiated via Interface Builder, but you forget to hook it up correctly (meaning it isn't instantiated). You can still set its delegate (e.g., self.webview.delegate = self;) without crashing or exceptions (provided self conforms to the UIWebViewDelegate protocol).
Similarly, you can load HTML into it (e.g., [self.webview loadHTMLString:someHTMLString baseURL:nil];) and the app will continue along happily without complaints. This would take you to an awkward situation where you've set the delegate, written the delegate methods correctly, loaded something into the webview, but your delegate methods will never fire.
So the advice for that situation: make sure you double check that your webview is connected properly (to the correct instance property/variable) in Interface Builder.
Related
My app is running correctly until I attempt to create an instance of my custom class Transcript. Is my problem coming from the initialization method or the way I am allocating my pointer ? I am perplex since the app doesn't crash. The signal (lldb) appears in the debug area, while the declaration line of my initializer gets underlined in green with the following message:
Thread 1: EXC_BAD_ACCESS (code=2, address=0xbf7ffffc)
Here is the complete implementation of my class :
- (id)initTranscriptWithID:(NSString *)peerID message:(NSString *)message direction:(NSString *)direction {
if (self = [super init]) {
_peerID = peerID;
_message = message;
_direction = direction;
}
return self;
}
- (id)initWithPeerID:(NSString *)peerID message:(NSString *)message direction:(NSString*)direction
{
return [self initWithPeerID:peerID message:message direction:direction];
}
This initializer is called in a view controller the following way, messageSent being a property of the same view controller:
Transcript* transcript = [[Transcript alloc]initWithPeerID: [self.messageReceived objectForKey:#"senderId"] message:[self.messageSent objectForKey:#"content"] direction:#"right"];
I'm pretty sure that I'm creating the instance of Transcript correctly in the view controller, however, is there a problem with the declaration of my initializer ?
I have tried to use NSZombieEnabled unsuccessfully. Otherwise, I don't think that I am ever deallocating the instance transcript...
What is the error ? What does my thread exactly correspond to and what is the usual way to tackle these problems ? If you need more information I'd be happy to provide it. Thank you so much!
the method initWithPeerID calls itself – I guess in an infinite loop.
You probably mean
- (id)initWithPeerID:(NSString *)peerID message:(NSString *)message direction:(NSString*)direction
{
return [self initTranscriptWithID:peerID message:message direction:direction];
}
i've some javascript code on the webpage in the uiwebview that i want to use to call one of my objective c methods.
i found some code online which i decided to use. but it still doesn't seem to be working. can anyone see where the problem is?
javascript code:
function someMethod() {
window.location = "ios:webToNativeCall";
}
objective c code:
-(BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
if ([[[request URL] absoluteString] hasPrefix:#"ios:"]) {
// Call the given selector
[self performSelector:#selector(webToNativeCall)];
// Cancel the location change
return NO;
}
return YES;
}
-(void)webToNativeCall
{
//my code here
}
i'm not sure, how to use this method so it might be that i have implemented it incorrectly.
does anyone have any ideas about what could be causing this?
Thanks in advanced.
This code looks ok, please check whether delegate for UIWebView is set or not.
Otherwise you can use EasyJSWebView download it from Github, it is easy to use.
You must have missed to link the delegate.
Either connect the delegate of the webView to the file owner in the .xib file
or
Use Following code
webView = [[UIWebView alloc] init];
webView.delegate = self;
in your viewDidLoad also write below code
[webView stringByEvaluatingJavaScriptFromString:#"yourJavascriptFunction()"];
Hope it helps you...
Background:
All my OpenTok methods are in one ViewController that gets pushed into view, like a typical Master/detail VC relationship. The detailVC connects you to a different room depending on your selection. When I press the back button to pop the view away, I get a crash (maybe 1 out of 7 times):
[OTMessenger setRumorPingForeground] message sent to deallocated instance xxxxx
or
[OTSession setSessionConnectionStatus:]: message sent to deallocated instance 0x1e1ee440
I put my unpublish/disconnect methods in viewDidDisappear:
-(void)viewDidDisappear:(BOOL)animated{
//dispatch_async(self.opentokQueue, ^{
[self.session removeObserver:self forKeyPath:#"connectionCount"];
if(self.subscriber){
[self.subscriber close];
self.subscriber = nil;
}
if (self.publisher) {
[self doUnpublish];
}
if (self.session) {
[self.session disconnect];
self.session = nil;
}
//});
[self doCloseRoomId:self.room.roomId position:self.room.position];
}
Here is a trace:
Here is the DetailViewController on Github: link here
How to reproduce:
Make a selection from the MasterVC, that takes you into the DetailVC which immediately attempts to connect to a session and publish
Go back to previous, MasterVC quickly, usually before the session has had an a chance to publish a stream
Try this several times and eventually it will crash.
If I slow down and allow the publisher a chance to connect and publish, it is less likely to cause a crash.
Expected result:
It should just disconnect from the session/unpublish and start a new session as I go back and forth between the Master/DetailVC's.
Other:
What is your device and OS version?
iOS 6
What type of connectivity were you on?
wifi
Zombies Enabled?
Yes
ARC Enabled?
Yes
Delegates set to nil?
Yes, as far as I know
Any help solving this crash would be greatly appreciated. Perhaps I'm missing something basic that I just can't see.
What seems to happen is that the OTSession object in the OpenTok library continues to to send messages to objects in that library that have since been deallocated by switching views. The library has a [session disconnect] method that works fine if you give it enough time, but it takes close to 2-3 seconds, and that's a long time to pause an app between views.
This might be a stupid question, but:
Is there anyway to stop all processes initiated by a certain VC?
Closing the session from viewWillDisappear() works if you can determine for sure that the view is going to be popped, not pushed or hidden. Some answers suggest putting this code in dealloc(). Regarding those suggestions, Apple says,
You should try to avoid managing the lifetime of limited resources using dealloc.
So, here is how you can determine for sure that your view will get popped. viewWillDisappear() is called when the view is popped from the stack, or is otherwise pushed somewhere else. This is the easiest way to determine which, and then unpublish/disconnect if it is truly popped. You can test for this with isMovingFromParentViewController. Also, here is where you can remove specific observers.
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated]
// This is true if the view controller is popped
if ([self isMovingFromParentViewController])
{
NSLog(#"View controller was popped");
// Remove observer
[[NSNotificationCenter defaultCenter] removeObserver:self.session];
...
//dispatch_async(self.opentokQueue, ^{
if(self.subscriber){
[self.subscriber close];
self.subscriber = nil;
}
if (self.publisher) {
[self doUnpublish];
}
if (self.session) {
[self.session disconnect];
self.session = nil;
}
//});
[self doCloseRoomId:self.room.roomId position:self.room.position];
}
else
{
NSLog(#"New view controller was pushed");
}
}
Ref: Testing for Specific Kinds of View Transitions
Looks like OpenTok have a bug with usage NSNotificationCenter inside of OTSession and OTMessenger classes. You can see these classes in call-stack are separated with NSNotificationCenter calls:
You can manually unsubscribe your OTSession object when dealloc (hope OpenTok uses defaultCenter):
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self.session];
}
You need to check if this code (dealloc) is really executed. If not - you need to fix problem of UIViewController deallocation. A lot of other answers contains tips how to help UIViewController to be deallocated.
-(void)viewDidDisappear:(BOOL)animated is called whenever the view is hidden, not only when it is popped from the view stack.
So if you push a view over it, viewWillDisappear will be called and your objects deleted.
This is specially problematic if you load these same objects from viewDidLoad: instead of viewDidAppear:.
Perhaps you should put your unpublish/disconnect code in -(void)dealloc.
This is what Apple suggests:
-(void) dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
But this is only the last resort to remove observers, still often a good habit to always add it to make sure everything is cleand up on dealloc to prevent crashes.
It's still a good idea to remove the observer as soon as the object is no longer ready (or required) to receive notifications.
I most of the time put such a code in the viewWillDisappear, but I guess that doesn't really matter.
I believe the issue is that your session delegate is not set to nil. Just add the following in your viewDidDisappear:
self.session.delegate=nil;
You must call [super viewDidDisappear:animate]; at the beginning. May be it will fix your issue.
And better cleanup your session and subscriber in dealloc method:
- (void) dealloc {
[self.session removeObserver:self forKeyPath:#"connectionCount"];
if(self.subscriber){
[self.subscriber close];
self.subscriber = nil;
}
if (self.publisher) {
[self doUnpublish];
}
if (self.session) {
[self.session disconnect];
self.session = nil;
}
[self doCloseRoomId:self.room.roomId position:self.room.position];
//[super dealloc]; //for non-ARC
}
According to the stack trace you have posted, the notification center reaches out to an OTSession instance that is still alive. Afterwards, this instance provokes a crash calling methods on deallocated objects.
Adding to that the two different deallocated instance messages, we know there are asynchronous events occuring after the death of some objects that trigger the random crash you are having.
As ggfela suggested, you should make sure to nil out the delegates you have connected to the OpenTok framework. I strongly suggest you do that in the dealloc method as we want to make sure that after that point, no one has any dangling references to your object :
- (oneway void)dealloc
{
self.session.delegate = nil;
self.publisher.delegate = nil;
self.subscriber.delegate = nil;
}
Another odd thing in the code is that your handler for sessionDidConnect: creates a new dispatch_queue every time it is being called in order to call doPublish:. This means that you have concurrent threads sharing the SROpenTokVideoHandler instance which makes it prone to race conditions.
I have been using webView delegate successfully from long time. But recently I faced strange issue with this delegate. In my current project I am trying to access my router from webview. I am passing username and password inside URL only. Below is load request code.
[self.webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:#"http://uname:password#192.168.1.1"]]];
This calls webView delegate method (webViewDidFinishLoad and webViewDidStartLoad) 5 times. Is it expected? When I pass simple URL like google.com it works as expected. But with username and password why these delegate methods are called 5 times?
If this behaviour is correct then I need to know why it calls 5 times only. The reason is, in my program - I am calling performSegueWithIdentifier in webViewDidFinishLoad method and in present form it calls segue 5 times. For workaround I can maintain count and will call performSegueWithIdentifier on 5th count only.
Thanks
webViewDidStartLoad/webViewDidFinishLoad are called once per HTML frame. Your content likely has multiple frames in it.
See UIWebViewDelegate docs.
webViewDidStartLoad:
Sent after a web view starts loading a frame.
This Methods works for me... :)
#pragma mark UI Web View Delegate
NSInteger webViewLoads;
//a web view starts loading
- (void)webViewDidStartLoad:(UIWebView *)webView{
webViewLoads++;
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
[SVProgressHUD showWithStatus:#"Loading..." maskType:SVProgressHUDMaskTypeBlack];
}
//web view finishes loading
- (void)webViewDidFinishLoad:(UIWebView *)webView{
webViewLoads--;
[self performSelector:#selector(webViewFinishLoadWithCondition) withObject:nil afterDelay:0.5];
}
//web view handling error
- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error{
webViewLoads--;
NSLog(#"Web View Did Fail Load With Error : %#",error);
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
[SVProgressHUD dismiss];
}
-(void)webViewFinishLoadWithCondition{
if(webViewLoads==0){
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
[SVProgressHUD dismiss];
}
}
As webViewDidStartLoad/webViewDidFinishLoad are called once per HTML frame, Use an integer to find when the last frame load is finished,
In detail :
Increment the integer in webViewDidStartLoad
Decrement the integer in webViewDidFinishLoad
In webViewDidFinishLoad check when integer is zero, Which means all the frames of web page are loaded, Now call the selector
This is an explanation of #marvin's answer
Best approach:
Check for isLoading in webViewDidFinishLoad and isLoading is false, do what ever u want
-(void)webViewDidFinishLoad:(UIWebView *)webview
{
if (!webview.isLoading)
{
// webview is done loading content.
// `performSegueWithIdentifier` / Your code Here
}
}
I'm posting to a users Facebook wall with code similar to this:
[appDelegate.facebook requestWithGraphPath:#"me/feed"
andParams:params
andHttpMethod:#"POST"
andDelegate:self];
If I dismiss the hosting UIViewController before the request completes, I get a crash when the request does actually complete because the delegate has been dealloc'd.
There's a good description of the problem I'm facing here: https://github.com/facebook/facebook-ios-sdk/issues/220
- (void)dealloc {
appDelegate.facebook.sessionDelegate = nil;
[super dealloc];
}
This does not work!
The proper solution is to save the FBRequest object when you call the GraphAPI.
So you will be able to set it's delegate property to nil when your class is being dealloced.
Thus cleaning up your mess and avoid crash caused by the SDK's respondsToSelector method.
First declare a FBRequest property in your class.h:
#property (nonatomic, retain) FBRequest *fbRequest;
Synthesize it in your class.m:
#synthesize fbRequest;
Set it when you call the graph API as so:
fbRequest = [appDelegate.facebook requestWithGraphPath:#"me/feed"
andParams:params
andHttpMethod:#"POST"
andDelegate:self];
Set it's delegate propery to nil on your class's dealloc method:
-(void) dealloc
{
fbRequest.delegate = nil;
[fbRequest release];
.
.
.
[super dealloc]
}
I had a solution for that but not very convenient;
I am having 2 separate views in the same view controller, where one of them is the one you request the publish the other the destination view.
You just hide one of the views when request is finished. Or hide the other and display the one while requesting.
For example if you are going to another page after logging in :
- (void)fbDidLogin {
// Do necessary stuff
self.secondView.hidden = YES;
self.view.hidden = NO;
}
The idea is like that, it works but not very convenient.
Create an object (separate from the view controller) that will be the FB delegate and don't deallocate that. You could instantiate it from the same place you instantiate the view controller that calls FB.
Or, if you don't care about the return status, don't use a delegate.
If you look at how Facebook's own code deals with this in Facebook.m they create a list to hold _request objects 'NSMutableSet* _requests;'
and this is alloc/init'd & the requests added as you make calls then they remove them all when cleaning up.
See the lines with ' [_requests addObject:_request];' and look at dealloc where you will see:-
for (FBRequest* _request in _requests)
{
_request.delegate = nil;
[_request removeObserver:self forKeyPath:requestFinishedKeyPath];
}
I used this approach in my own FB code to nil the delegates & it stopped the crashes I was having when exiting while a request was active.
What I did to overcome this problem is to create a wrapping class for FB which I called "FacebookManager". It is a singleton in charge of every FB request done along your application. Being a singleton app-wide, it's life cycle lasts also for the whole app life too.
#protocol
#optional
-(void)fbDidLogin;
-(void)fbDidNotLogin:(BOOL)cancelled;
-(void)fbDidExtendToken:(NSString *)accessToken expiresAt:(NSDate *)expiresAt;
-(void)fbDidLogout;
-(void)fbSessionInvalidated;
-(void)request:(FBRequest *)request didLoad:(id)result;
#end
#interface FacebookManager : Facebook <FBSessionDelegate, FBRequestDelegate>
+(FacebookManager *)sharedFacebookInstance;
-(void)setFacebookDelegate:(id)delegate;
-(void)requestWithGraphPath:(NSString *)fbPath;
Other classes, usually ViewControllers, can be FacebookManager's delegate (or, if needed, you can make an array of delegates when processing concurrent requests). When FacebookManager receives the response from the request, it passes this along to the original class.
Ans since it's the sole delegate for every FB request and never deallocates through your program, even when the original class is dealloc'ed, no errors will arise.
As a bonus to all this, you automatically lose those warning for the methods you need not implement if not desired.
Hope this helps in any way!