Scrollable custom view inside a UIWebView
Displaying HTML content is very easy on iOS. Just grab a UIWebView
and call loadHTMLString:baseURL
or loadData:MIMEType:textEncodingName:baseURL:
and enjoy the results. This component would fit your needs 90% of the time. But in some cases, having a simple UIWebView
is not enough.
For professional reasons, I had to implement a mail reader like screen. On such a screen, there is a fixed portion on the screen that scrolls along with the portion rendering the HTML. I did not build such a view before and start thinking how I could make it.
After thinking a little bit, I first thought about implementing the fixed portion in HTML/CSS. No offense to those technologies, but they are not really my cup of tea.
I start searching on the Internet to see if someone founds a nice way implementing this without using private APIS.
Tweaking the internal UIWebBrowserView
Each UIWebView
contains a UIScrolView
. Inside this scroll view, the HTML is rendered by a Apple internal private UIView
subclass named UIWebBrowserView
. It is possible to get a reference to this subview.
To have a reference to it, simply loop over the subviews contained within the UIScrollView
of the UIWebView
:
for(UIView *subview in self.webView.scrollView.subviews)
{
NSString * className = NSStringFromClass([subview class]);
if([className isEqualToString:@"UIWebBrowserView"])
{
//YES ! Just grab a reference to it :)
self.HTMLRender = subview;
//Set the frame of the view to fit your needs
break;
}
}
Once you grab it, you can use and place this view by setting its frame
.
This works great, but it has the smell of a big workaround :( Would it be possible to do it in a easier way ?
Do it thanks to the documentation
I read the article in the great objc.io concerning scroll views and the start of a solution appears by reading the part Tweaking the Window with Content Insets.
The main idea is to use the contentInset
property of the UIScrollView
of the UIWebView
to create a space at the top of the UIWebView
and fill it with some view.
It is better to see with an example. Let's create a UIWebView
subclass named AwesomeWebView
. This will be a web view with a 100 points red view at this top.
#define TOP_SPACE_HEIGHT 100.0f
@interface AwesomeWebView : UIWebView
@end
All the work could be done in the initializer. The only thing to consider, is that adding a contentInset create a zone in the view where the coordinates are negatives.
- (id) initWithFrame:(CGRect)frame
{
if(self = [super initWithFrame:frame])
{
[self setupScrollView:TOP_SPACE_HEIGHT]; // Setting the contentInset
}
return self;
}
- (void) setupScrollView:(CGFloat)headerHeight
{
// Set the contentInset to create space for the top View
self.scrollView.contentInset = UIEdgeInsetsMake(headerHeight, 0, 0, 0);
// Set the contentSize
self.scrollView.contentSize = (CGSize){CGRectGetWidth(self.frame), CGRectGetHeight(self.frame) + headerHeight};
//Make the scroll indicator insets starts at the bottom of the top view
self.scrollView.scrollIndicatorInsets = UIEdgeInsetsMake(headerHeight, 0, 0, 0);
//Initialize the redTopView
//-> Keep in mind that the zone up to the scroll view is negative.
UIView * redView = [[UIView alloc] initWithFrame:CGRectMake(0, -headerHeight, CGRectGetWidth(self.frame), headerHeight)];
redView.backgroundColor = [UIColor redColor];
//Finally add it to the view;
[self.scrollView addSubview:redView];
}
This methods seems to be better than the first one.
You can find an example of this method on github