I'm trying to figure out how to troubleshoot and/or solve an issue where a UIWebView clearly loads but then disappears.
After tracing in the debugger, the webView is disappearing after viewDidAppear:
The content flashes briefly on screen and is then gone:
To help me trace, I've got some KVO set up to monitor the contentSize:
- (void)viewDidLoad
{
[super viewDidLoad];
[self startObservingChangesInView:self.webView];
}
- (void)dealloc {
[self stopObservingChangesInView:self.webView];
}
- (void)startObservingChangesInView:(UIView *)view {
[view addObserver:self forKeyPath:#"contentSize" options:0 context:&kObservingChangesContext];
}
- (void)stopObservingChangesInView:(UIView *)view {
[view removeObserver:self forKeyPath:#"contentSize" context:&kObservingChangesContext];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if (context == &kObservingChangesContext) {
UIView *view = object;
NSLog(#"View changed for keypath, '%#': '%#'", keyPath, view.description);
} else {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
Extra context
There's more going on in my UIWebView than shown above, I just wanted to show my current mechanism for monitoring keys.
There are some cases where the load works and stays.
Works
Run app in simulator
Log in
Open Menu
Open Url
Doesn't work
It stops working when following these steps:
Run app in simulator
Log in
Open Menu
Open Url
Logout
Login
Open Menu
Open Url
The problem is that I'm not holding on to any references after logging the user out of the app. The webView is created a new object every time it is pushed with UINavigationController.
So how can I go about solving this? Either there is a known solution or maybe there are there variables I should be tracing with KVO?
Check if your WebView is not being removed from the view hierarchy somewhere by the application. I had similar problem and I've noticed that I unintentionally removed my WebView from the view hierarchy in some event handler.
I faced a similar issue. But the problem with my setup was I was initializing the UI and the view in the Launchscreen.storyboard and not the Main.storyboard. Due to the nature of the Launchscreen.storyboard, it displays my view for some time before vanishing. Putting the view in the Main.storyboard fixed the issue.
Related
For UIWebview and WKWebview in iOS, delegate method:
webViewDidFinishLoad:(UIWebView *)webView
and
(void)webView:(WKWebView *)webView didFinishNavigation:(null_unspecified WKNavigation *)navigation
are called when all resources load successfully, however, contents have shown for several seconds before these methods are called.
Now, I want to observe the time when contents show on Webview, anyone who has a good idea please tell me, thanks.
You have to use WKWebView to track the loading progress.
First you have to add observers to listen to the progress.
[self.webView addObserver:self forKeyPath:#"loading" options:NSKeyValueObservingOptionNew context:nil];
[self.webView addObserver:self forKeyPath:#"estimatedProgress" options:NSKeyValueObservingOptionNew context:nil];
Then when the web starts loading you handle in this method:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{
if ([keyPath isEqualToString:#"loading"]) {
if (!self.webView.isLoading) {
// Finished loading.
}
}
else if ([keyPath isEqualToString:#"estimatedProgress"]){
float progress = _webView.estimatedProgress; // Do whatever you want with the progress
}
}
In addition, this delegate method gets called when the web view begins loading contents:
- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(null_unspecified WKNavigation *)navigation{
// The web starts loading contents.
}
Webview did finish load is called when the entire website is loaded.
Your website must have some javascript and css scripts loading after the html is loaded.
Hence the website is displayed, while the javascript is still being loaded.
So the entire website is actually loaded once everything is loaded.
The ideal solution would be to show this to the user. The progress.
I got to solve this very same issue using the delegate didCommitNavigation instead of didFinishNavigation:
- (void)webView:(WKWebView *)webView didCommitNavigation:(WKNavigation *)navigation
{
// Your code here
}
At this point seems like the webView is already able to evaluate javascript code. If that's what you're trying to do, you need to know that the site scripts may have not been loaded yet, so the evaluated code must take that into consideration. My personal solution was to use setInterval and keep checking if the function I wanted to call existed.
I need to find a way to create some class that is always alive in the app. This class doesn't know anything about the other classes in the project. It will be able to "follow" all UIViews on screen-so every moment I can check(loop) over the views and get a pointer to each of them.
It has to run live, and always check positions of views (is it a memory problem?)
Why a pointer? because I need to know everything about it, so if its some kind of moving animation, or maybe it has some meta data like tags, etc. so only knowing there is some view at a certain position is not enough.
Is it possible in iOS ?
The whole idea sounds like an antipattern. However, …
Simply traverse the view tree and add a KVO handler to every view for every interesting property.
- (void)traverseSubviewsOfParentView:(UIView*)view
for( UIView* subview in view.subviews )
{
[view addObserver:self forKeyPath:#"frame" options: NSKeyValueObservingOptionNew];
…
[self traverseSubviewsOfParentView:subview context:NULL];
}
Then implement the observation method:
-(void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
{
if( [#"frame" isEqualToString:keyPath] )
{
// Do what you want to do
}
…
}
Additionally you have to observe the subviews property of every view to get notified, when a view is inserted or removed.
I have an observer on an UIView to adjust a UIWebView when the size is changed (e.g. on rotation of the device):
[[self view] addObserver:self forKeyPath:#"frame" options:0 context:nil];
Naturally I need to remove this observer when the view is dismissed (otherwise there is an exception). But where would I do this? I tried in dealloc, or in viewDidDisappear, but I get a SIGABRT when I do this.
- (void) viewDidDisappear:(BOOL)animated {
NSLog(#"Is moving %d - %d", self.isMovingFromParentViewController, self.isBeingDismissed);
[[self view] removeObserver:self forKeyPath:#"frame"];
}
I tried to move it in earlier on ViewWillDisappear, but the same result.
Moreover, I am not completely happy with this later solution (even if it would work), as it will potentially also unload when the view is temporary out of view (i.e. with pageViewController), although that is not that big a deal for me at the moment.
Tracking the problem down to this item and trying to solve it has already cost me a day, so any suggestions are really appreciated!
I'm dealing with a tricky situation in my app. This app shows a list of files in a UITableView, from there you can download (I use AFnetworking 2.0) a file and then you can see download progress in another UITableView (all the views are in a UITabBarController).
My problem is that I'm not really sure about how to reload the UITableVIew showing current downloads when a new one is added or when one is finished or cancelled. I tried using KVO but it didn't work observing the operation queue from AFnetworking.
EDIT:
This is the KVO code I tried
In my downloads UITableViewCell
- (void)viewDidLoad
{
[super viewDidLoad];
[[[[PodcastManager sharedManager] sessionManager] operationQueue] addObserver:self
forKeyPath:NSStringFromSelector(#selector(operations))
options:NSKeyValueObservingOptionNew
context:nil];
}
and then...
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if (object == [[[PodcastManager sharedManager] sessionManager] operationQueue]) {
if ([keyPath isEqualToString:NSStringFromSelector(#selector(operations))]) {
NSLog(#"OperationCount: %lu", (unsigned long)[[[[[PodcastManager sharedManager] sessionManager] operationQueue] operations] count] );
}
}
}
But this "solution" didn't work here.
In the past, I faced a similar situation with another app and then I used blocks, but this time, I want to avoid that solution because there is some part of my current app's code I want to reuse in the future.
Anybody has faced a similar situation can bring some light?
Thank you.
Why not just run
[tableView reloadData];
after each download? Maybe I"m missing something. But that's what I'd do.
I am working on integrating an ad provider into my app currently. I wish to place a fading message in front of the ad when the ad displays but have been completely unsuccessful.
I made a function which adds a subview to my current view and tries to bring it to the front using
[self.view bringSubviewToFront:mySubview]
The function fires on notification that the ad loaded (the notification is from the adprovider's SDK). However, my subview does not end up in front of the ad. I imagine this is made purposely difficult by the ad provider, but I wish to do it regardless. I am currently discussing with the provider whether or not this can be allowed. But for the time being, I just want to see if it's even possible.
Is there anyway I can force a subview of mine to be the top-most view such that it will not be obstructed by anything?
try this:
self.view.layer.zPosition = 1;
What if the ad provider's view is not added to self.view but to something like [UIApplication sharedApplication].keyWindow?
Try something like:
[[UIApplication sharedApplication].keyWindow addSubview:yourSubview]
or
[[UIApplication sharedApplication].keyWindow bringSubviewToFront:yourSubview]
Swift 2 version of Jere's answer:
UIApplication.sharedApplication().keyWindow!.bringSubviewToFront(YourViewHere)
Swift 3:
UIApplication.shared.keyWindow!.bringSubview(toFront: YourViewHere)
Swift 4:
UIApplication.shared.keyWindow!.bringSubviewToFront(YourViewHere)
Hope it saves someone 10 seconds! ;)
I had a need for this once. I created a custom UIView class - AlwaysOnTopView.
#interface AlwaysOnTopView : UIView
#end
#implementation AlwaysOnTopView
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if (object == self.superview && [keyPath isEqual:#"subviews.#count"]) {
[self.superview bringSubviewToFront:self];
}
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
- (void)willMoveToSuperview:(UIView *)newSuperview {
if (self.superview) {
[self.superview removeObserver:self forKeyPath:#"subviews.#count"];
}
[super willMoveToSuperview:newSuperview];
}
- (void)didMoveToSuperview {
[super didMoveToSuperview];
if (self.superview) {
[self.superview addObserver:self forKeyPath:#"subviews.#count" options:0 context:nil];
}
}
#end
Have your view extend this class. Of course this only ensures a subview is above all of its sibling views.
As far as i experienced zposition is a best way.
self.view.layer.zPosition = 1;
Let me make a conclusion. In Swift 5
You can choose to addSubview to keyWindow, if you add the view in the last.
Otherwise, you can bringSubViewToFront.
let view = UIView()
UIApplication.shared.keyWindow?.addSubview(view)
UIApplication.shared.keyWindow?.bringSubviewToFront(view)
You can also set the zPosition. But the drawback is that you can not change the gesture responding order.
view.layer.zPosition = 1
In c#, View.BringSubviewToFront(childView);
YourView.Layer.ZPosition = 1;
both should work.
In Swift 4.2
UIApplication.shared.keyWindow!.bringSubviewToFront(yourView)
Source: https://developer.apple.com/documentation/uikit/uiview/1622541-bringsubviewtofront#declarations