📜 ⬆️ ⬇️

UIWebView Synchronous Download

Greetings, Habr!

It all started with finding a solution to the problem of displaying formatted text inside a UITableViewCell, and not a strictly specified format (then you could use the UILabel set with a given font) but an arbitrary one. Yes, so that the formatting can be set with simple html tags. This problem can be solved in different ways:


As the reader could have guessed from the title of the article, I stopped at the last method, but those of you who have tried this method know about a very unpleasant reef. How to get rid of it, I will tell under the cut.

UIWebView inside UITableViewCell


And so, in what a bit of this problem - UIWebView loads its content asynchronously. And if we place a UIWebView inside a UITableViewCell, and in cellForRowAtIndexPath: we will load the content itself, the actual download will be asynchronous after all the touch events related to scrolling have passed. It will not look very nice: the content will begin to update only after the scrolling stops.
To get around this limitation, you need to understand a little theory. Asynchrony in our case is implemented using RunLoops. Since the runloop is set for a thread, we can make a call asynchronously in the same thread. In addition, each call is made with its RunLoopMode - which is an analogue of priorities, so first of all the calls of the highest priority are selected from the current loop, and then downward. Also important is the ability to run nested runloop.
What happens when we call loadHTMLSTring: in UIWebView? Most likely, performSelector is done with an indication of not the highest priority RunLoopMode. In principle, this is correct, since uploading content to a UIWebView is not an easy task and takes time, if you do this as you roll the table, it can noticeably slow down the rollout itself. But if the content is light enough (the simplest html in two lines, let's say) - the download will go quite quickly.
In order to perform this load without waiting until all the event touch events are completed, after calling loadHTMLString, you need to start the nested run loop with a lower RunLoopMode priority - NSDefaultRunLoopMode.
CFRunLoopRunInMode ( ( CFStringRef ) NSDefaultRunLoopMode, 1 , NO ) ;

It is important that after WebView has loaded all the same, stop this RunLoop because otherwise the UI will just hang.
CFRunLoopRef runLoop = [ [ NSRunLoop currentRunLoop ] getCFRunLoop ] ;
CFRunLoopStop ( runLoop ) ;

UISynchedWebView


Let's take out everything related to running nested runloop and stopping it in the WebView itself. Make a subclass UIWebView, name it UISynchedWebView and override its loadHTMLString so that it starts the nested runloop after calling the base implementation. The question arises when to stop it? You need to stop it immediately after the content has loaded. To determine this, we need to make our new WebView our own delegate, but in order to preserve transparency for external user code. To do this, we will create a member variable of the class for the external delegate, redefine its setters and getters, and after calling the code of our delegate, we will call the appropriate methods of the external delegate. Like that:
')
@interface UISynchedWebView : UIWebView <UIWebViewDelegate>
{
id anotherDelegate;
}
@end


- ( void ) webView : ( UIWebView * ) webView didFailLoadWithError : ( NSError * ) error
{
[ self performSelector : @selector ( stopRunLoop ) withObject : nil afterDelay : .01 ] ;

if ( [ anotherDelegate respondsToSelector : @selector ( webView : didFailLoadWithError :) ] )
[ anotherDelegate webView : webView didFailLoadWithError : error ] ;
}

- ( BOOL ) webView : ( UIWebView * ) webView shouldStartLoadWithRequest : ( NSURLRequest * ) request navigationType : ( UIWebViewNavigationType ) navigationType
{
if ( [ anotherDelegate respondsToSelector : @selector ( webView : shouldStartLoadWithRequest : navigationType :) ] )
return [ anotherDelegate webView : webView shouldStartLoadWithRequest : request navigationType : navigationType ] ;
return YES ;
}

- ( void ) webViewDidFinishLoad : ( UIWebView * ) webView
{
[ self performSelector : @selector ( stopRunLoop ) withObject : nil afterDelay : .01 ] ;
if ( [ anotherDelegate respondsToSelector : @selector ( webViewDidFinishLoad :) ] )
[ anotherDelegate webViewDidFinishLoad : webView ] ;
}

- ( void ) stopRunLoop
{
CFRunLoopRef runLoop = [ [ NSRunLoop currentRunLoop ] getCFRunLoop ] ;
CFRunLoopStop ( runLoop ) ;

}

- ( void ) webViewDidStartLoad : ( UIWebView * ) webView
{
if ( [ anotherDelegate respondsToSelector : @selector ( webViewDidStartLoad :) ] )
[ anotherDelegate webViewDidStartLoad : webView ] ;
}


Warning


Lastly, I would like to note that this method allows you to get beautifully formatted text, where you need to be highlighted in bold, or highlighted by background - in table cells without much effort and has iOS3.x compatibility. However, as has been said, downloading such content blocks the UI and if the content is heavy enough, the blocking will become noticeable and it will be very bad. Not everywhere this method can be used, be careful!

PS Demoproject can be found here .

Source: https://habr.com/ru/post/125931/


All Articles