SHCWebView is WebView compatible with NSTextFinderClient protocol. It supports find functionality pretty much like in the Apple Safari browser. Best results can be accomplished when NSTextFinder object is configured to use "Incremental searching" and "Dim Content View".
SHCWebView can properly handle a Unicode composite characters when the stripCombiningMarks
property is set to YES
, this property has also an another effect - offers a special mode of searching. When set to YES
parsing text nodes from the DOM tree use a kCFStringTransformStripCombiningMarks
transform of CFStringTransform
foundation method to ease searching of non-ASCII characters by ex."
- content text is "zażółć" (polish language),
stripCombiningMarks
set toYES
, to match content search string could be "zazolc" ifstripCombiningMarks
set toNO
you should enter exact string to match "zażółć".
Use it like normal WebView (in Interface Builder remember to change class to SHCWebView).
Configure textFinder
property to your NSTextFinder object - it's necessary to communicate between WebView and NSTextFinder (by ex. when the WebView change its size).
To achieve proper working with NSTextFinder interface we need to embed WebView into NSScrollView (because it already implements NSTextFinderBarContainer protocol).
It's possible to work only with WebView internal scroll object (webView.mainFrame.frameView.documentView.enclosingScrollView
) but it has some drawbacks - when NSTextFinder bar is showing at the top of WebView links coordinates inside WebView are not updated with new control size (shrunk down by the NSTextFinder bar height) and 'clickable' links appear to browser with offset equal to NSTextFinder bar height. I don't know remedy to this behavior. When NSTextFinder bar is displayed at the bottom of the control, it works as intended without any glitches.
So, common setup is to use SHCWebView inside NSScrollView. If you don't use Auto Layout, remember to set NSClipView (NSScrollView.contentView
) property autoresizeSubviews
to NO.
NSTextFinder is well suited for a non-layered text, but with WebView where today we have many layers (DIVs), some of them can be hidden or floated above other content or dynamic content updated with JavaScript, it shows its weakness.
NSTextFinder assumes that found text is visible all the time and in situation when we have a floating header (like menu by ex.) the 'holes' in dimming view will be displayed over our header (not exactly what we want) - but the same problem you can observe in the Apple Safari browser.
NSTextFinder uses client object implementing NSTextFinderClient protocol to achieve its functionality:
- Asks client for a string representation of the content. (
string
method) - Using a regular expression on that string NSTextFinder makes a text ranges of a matched phrase.
- For every matched text range it asks the client for a bounding rect of that range - to display 'holes' in dimming view (
rectsForCharacterRange:
method) - Asks client to draw the current text range (
drawCharactersInRange:forContentView:
) - When navigating next/prev the search result, it asks client to scroll client view to visible rect of the search result. (
scrollRangeToVisible:
method)
To make string representation of the content we have to walk DOM tree and extract all text nodes content, remembering DOM nodes and offset positions of the text content in that nodes (as an NSArray of SHCWebViewTextRange
objects). If the stripCombiningMarks
property is set to YES
also a transform on the text content is performed.
It's the NSTextFinder role, we don't need to do anything.
To get bounding rects of text range we use Java Script by WebView.evaluateWebScript
method.
- Firstly create the DOMRange object and configure it using remembered earlier data (from
SHCWebViewTextRange
objects) - Next for this DOMRange execute Java Script to get array of a bounding rects of that range.
As we can get bounding rects of the text range, to display that range we simply use WebView documentView drawRect: