Reputation: 19790
Suppose that I have a UIWebView that's stacked against a UIImageView (or some other view):
I want to resize my UIWebView so that I know where to put the UIImageView. The way I do it is to wait for the UIWebView to finish loading:
webViewCalculating = YES;
while (webViewCalculating == YES) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
Then I resize it when the UIWebView has finished loading, according to this suggestion:
-(void)webViewDidFinishLoad:(UIWebView *)webView
{
CGRect frame = webView.frame;
frame.size.height = 1;
webView.frame = frame;
CGSize fittingSize = [webView sizeThatFits:CGSizeZero];
frame.size = fittingSize;
webView.frame = frame;
heightSoFar += webView.frame.size.height;
webViewCalculating = NO;
}
After this point, I'd know how tall my UIWebView is, and I can place my UIImageView accordingly. However when I push another view controller and came back to this, my hacky [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
doesn't seem to wait until my webView finished calculating the height first. Is there a better way for me to know the size of my UIWebView without waiting for it to load? Maybe something similar to sizeWithFont
method of NSString?
Upvotes: 0
Views: 1279
Reputation: 13484
You should not be doing your while loop that is pumping the run-loop. This is a very bad idea. You should have your scroll view container respond and relayout the subviews when the webview finishes loading. Start with a "reasonably sized webview area". Maybe show a spinner in pace while the web content is loading.
A technique I've used to do exactly this is as follows:
Firstly add some category methods to UIWebView as follows:
@interface UIWebView (ContentSize)
- (CGFloat)documentOffsetHeight;
@end
@implementation UIWebView (ContentSize)
- (CGFloat)documentOffsetHeight
{
return [[self stringByEvaluatingJavaScriptFromString:@"document.documentElement.offsetHeight"] floatValue];
}
@end
Then I've written a UIView subclass that contains the UIWebView. Eg:
@interface MyWebView : UIView <UIWebViewDelegate>
@end
@implementation MyWebView
{
BOOL _loaded;
UYIWebView *_webView;
}
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
_webView = [[UIWebView alloc] initWithFrame:CGRectMake(0, 0, frame.size.width, 1)];
_webView.delegate = self;
_webView.alpha = 0.0f;
[self addSubview:_webView]
}
}
- (CGSize)sizeThatFits:(__unused CGSize)size
{
if (_loaded)
{
CGFloat height = [webView_ documentOffsetHeight];
CGFloat width = self.frame.size.width;
return CGSizeMake(width, height);
}
return self.frame.size;
}
- (void)sizeToFit
{
CGSize fittingSize = [self sizeThatFits:CGSizeZero];
self.bounds = CGRectMake(0, 0, fittingSize.width, fittingSize.height);
}
- (void)layoutSubviews
{
[super layoutSubviews];
_webView.frame = CGRectMake(0, 0, self.bounds.size.width, self.bounds.size.height);
}
- (void)loadHTMLString:(NSString *)htmlString baseURL:(NSURL *)baseURL
{
// This code assumes jQuery is already used in the HTML content. If not, added it as a script resource here too.
NSString *scriptTag = @"<script type=\"text/javascript\" >jQuery(document).ready(function() { window.location = 'x-webview://ready'; });</script>\n";
NSString *const headOpeningTag = @"<head>";
if ([htmlString rangeOfString:headOpeningTag].location != NSNotFound )
{
htmlString = [htmlString stringByReplacingOccurrencesOfString:headOpeningTag
withString:[headOpeningTag stringByAppendingString:headContent]
options:NSCaseInsensitiveSearch
range:NSMakeRange(0, [htmlString length])];
}
else
{
htmlString = [headContent stringByAppendingString:htmlString];
}
[_webView loadHTMLString:htmlString baseURL:baseURL];
}
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
if ([request.URL.scheme isEqualToString:@"x-webview"] && [request.URL.host isEqualToString:@"ready"])
{
_loaded = YES;
[UIView animateWithDuration:0.1
delay:0
options:UIViewAnimationOptionAllowUserInteraction
animations:^{ webView.alpha = 1.0f; }
completion:nil];
[self sizeToFit];
return NO;
}
return YES;
}
@end
So basically what this UIView subclass does is embed an invisible _webview. Then when the document has loaded and rendered the jQuery based JavaScript tries to navigate away from the current page using a custom URL scheme. This navigation is trapped an denied. But in response the view sizes to fit and gets the proper size from the HTML document.
Note that you don't have to use jQuery. You can add a DOM JavaScript event. I'm just more familiar with how jQuery does things that raw DOM events.
In your case you would have to communicate to the scrollview that the content has finished loading and that the scroll view views should re-layout. You can do this with a delegate protocol or something similar. Or maybe the scrollview is the "MyWebView" container UIView subclass. Adapt as needed.
Upvotes: 4
Reputation: 2085
properties are not reset, so when you come back to this controller you need to webViewCalculating = YES;
so that it doesn't instantly fail the while loop.
If you are already doing that you could try putting
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
with in the
-(void)webViewDidFinishLoad:(UIWebView *)webView
{
}
delegate method
Hope this is useful.
Upvotes: 0