How can I find text in uiwebview page and highlight it and go to its position in the page using swift ?
For example chrome find text :
But I'm using alert view instead :
How can I find text in uiwebview page and highlight it and go to its position in the page using swift ?
For example chrome find text :
But I'm using alert view instead :
You have to use JavaScript for this.
I done the same thing with code below (but in Objective C - you can rewrite it to Swift).
Usage (only in webViewDidFinishLoad - it's important!):
-(void)webViewDidFinishLoad:(UIWebView *)webView {
if (self.searchString != nil ) {
[self.webView highlightAllOccurencesOfString:self.searchString];
int position = self.foundedStringsCount - self.selectedStringNumber - 1;
[self.webView scrollTo:position];
}
}
Create and add this files to your code:
SearchWebView.h:
#import <Foundation/Foundation.h>
@interface SearchWebView : UIWebView
- (NSInteger)highlightAllOccurencesOfString:(NSString*)str;
- (void)scrollTo:(int)index;
- (void)removeAllHighlights;
@end
SearchWebView.m:
#import "SearchWebView.h"
@implementation SearchWebView
- (NSInteger)highlightAllOccurencesOfString:(NSString*)str
{
NSString *path = [[NSBundle mainBundle] pathForResource:@"UIWebViewSearch" ofType:@"js"];
NSString *jsCode = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil];
[self stringByEvaluatingJavaScriptFromString:jsCode];
NSString *startSearch = [NSString stringWithFormat:@"uiWebview_HighlightAllOccurencesOfString('%@')",str];
[self stringByEvaluatingJavaScriptFromString:startSearch];
NSString *result = [self stringByEvaluatingJavaScriptFromString:@"uiWebview_SearchResultCount"];
return [result integerValue];
}
- (void)scrollTo:(int)index {
[self stringByEvaluatingJavaScriptFromString:[NSString stringWithFormat:@"uiWebview_ScrollTo('%d')",index]];
}
- (void)removeAllHighlights
{
[self stringByEvaluatingJavaScriptFromString:@"uiWebview_RemoveAllHighlights()"];
}
@end
UIWebViewSearch.js:
var uiWebview_SearchResultCount = 0;
/*!
@method uiWebview_HighlightAllOccurencesOfStringForElement
@abstract // helper function, recursively searches in elements and their child nodes
@discussion // helper function, recursively searches in elements and their child nodes
element - HTML elements
keyword - string to search
*/
function uiWebview_HighlightAllOccurencesOfStringForElement(element,keyword) {
if (element) {
if (element.nodeType == 3) { // Text node
var count = 0;
var elementTmp = element;
while (true) {
var value = elementTmp.nodeValue; // Search for keyword in text node
var idx = value.toLowerCase().indexOf(keyword);
if (idx < 0) break;
count++;
elementTmp = document.createTextNode(value.substr(idx+keyword.length));
}
uiWebview_SearchResultCount += count;
var index = uiWebview_SearchResultCount;
while (true) {
var value = element.nodeValue; // Search for keyword in text node
var idx = value.toLowerCase().indexOf(keyword);
if (idx < 0) break; // not found, abort
//we create a SPAN element for every parts of matched keywords
var span = document.createElement("span");
var text = document.createTextNode(value.substr(idx,keyword.length));
span.appendChild(text);
span.setAttribute("class","uiWebviewHighlight");
span.style.backgroundColor="yellow";
span.style.color="black";
index--;
span.setAttribute("id", "SEARCH WORD"+(index));
//span.setAttribute("id", "SEARCH WORD"+uiWebview_SearchResultCount);
//element.parentNode.setAttribute("id", "SEARCH WORD"+uiWebview_SearchResultCount);
//uiWebview_SearchResultCount++; // update the counter
text = document.createTextNode(value.substr(idx+keyword.length));
element.deleteData(idx, value.length - idx);
var next = element.nextSibling;
//alert(element.parentNode);
element.parentNode.insertBefore(span, next);
element.parentNode.insertBefore(text, next);
element = text;
}
} else if (element.nodeType == 1) { // Element node
if (element.style.display != "none" && element.nodeName.toLowerCase() != 'select') {
for (var i=element.childNodes.length-1; i>=0; i--) {
uiWebview_HighlightAllOccurencesOfStringForElement(element.childNodes[i],keyword);
}
}
}
}
}
// the main entry point to start the search
function uiWebview_HighlightAllOccurencesOfString(keyword) {
uiWebview_RemoveAllHighlights();
uiWebview_HighlightAllOccurencesOfStringForElement(document.body, keyword.toLowerCase());
}
// helper function, recursively removes the highlights in elements and their childs
function uiWebview_RemoveAllHighlightsForElement(element) {
if (element) {
if (element.nodeType == 1) {
if (element.getAttribute("class") == "uiWebviewHighlight") {
var text = element.removeChild(element.firstChild);
element.parentNode.insertBefore(text,element);
element.parentNode.removeChild(element);
return true;
} else {
var normalize = false;
for (var i=element.childNodes.length-1; i>=0; i--) {
if (uiWebview_RemoveAllHighlightsForElement(element.childNodes[i])) {
normalize = true;
}
}
if (normalize) {
element.normalize();
}
}
}
}
return false;
}
// the main entry point to remove the highlights
function uiWebview_RemoveAllHighlights() {
uiWebview_SearchResultCount = 0;
uiWebview_RemoveAllHighlightsForElement(document.body);
}
function uiWebview_ScrollTo(idx) {
var scrollTo = document.getElementById("SEARCH WORD" + idx);
if (scrollTo) scrollTo.scrollIntoView();
}
Here is the swift code:
1) add UIWebViewSearch.js file to your project
func highlightAllOccurencesOfString(str:String) -> Int {
let path = Bundle.main.path(forResource: "UIWebViewSearch", ofType: "js")
var jsCode = ""
do{
jsCode = try String(contentsOfFile: path!)
}catch
{
// do something
}
self.newsWebView.stringByEvaluatingJavaScript(from: jsCode)
let startSearch = "uiWebview_HighlightAllOccurencesOfString('\(str)')"
self.newsWebView .stringByEvaluatingJavaScript(from: startSearch)
let result = self.newsWebView.stringByEvaluatingJavaScript(from: "uiWebview_SearchResultCount")
return Int(result!)!
}
func scrollTo(index:Int) {
self.newsWebView.stringByEvaluatingJavaScript(from: "uiWebview_ScrollTo('\(index)')")
}
func removeAllHighlights() {
self.newsWebView.stringByEvaluatingJavaScript(from: "uiWebview_RemoveAllHighlights()")
}
A javascript file may be injected using WKUserContentController, as described in https://medium.com/capital-one-tech/javascript-manipulation-on-ios-using-webkit-2b1115e7e405
Since uiWebview_HighlightAllOccurencesOfStringForElement(element,keyword) is called recursively, the index for the matched text may not be unique. As seen in https://github.com/sstahurski/SearchWKWebView/blob/master/WKWebView%20Extention/SearchWebView.js, we can replace it with a global variable. So uiWebview_ScrollTo(idx) can work correctly.
And javascript functions can be run as needed by subscribing to publishers corresponding to the search text and the index for matched text.
Here is my implementation of makeNSView(context:) in NSViewRepresentable, wherein the javascript file is added to WKWebView and subscribers are attached to search text and index.
func makeNSView(context: NSViewRepresentableContext<WebView>) -> WKWebView {
let configuration = WKWebViewConfiguration()
// Add a javascript file to WKUserContentController
if let path = Bundle.main.path(forResource: "UIWebViewSearch", ofType: "js"), let jsString = try? String(contentsOfFile: path, encoding: .utf8) {
let userContentController = WKUserContentController()
let userScript = WKUserScript(source: jsString, injectionTime: .atDocumentEnd, forMainFrameOnly: true)
userContentController.addUserScript(userScript)
configuration.userContentController = userContentController
}
let request = URLRequest(url: url)
let webView = WKWebView(frame: CGRect.zero, configuration: configuration)
webView.load(request)
webView.uiDelegate = context.coordinator
webView.navigationDelegate = context.coordinator
// Observe the text to search
viewModel.$searchString
.sink {
if !$0.isEmpty {
let startSearch = "uiWebview_HighlightAllOccurencesOfString('\($0)')"
webView.find($0) { result in
if result.matchFound {
webView.evaluateJavaScript(startSearch) { result, error in
if error != nil {
print("uiWebview_HighlightAllOccurencesOfString: \(String(describing: error))")
return
}
// Get the total number of matches
webView.evaluateJavaScript("uiWebview_SearchResultTotalCount") { result, error in
if error != nil {
print("uiWebview_SearchResultTotalCount: \(String(describing: error))")
return
}
if let result = result as? Int {
viewModel.searchResultTotalCount = result
viewModel.searchResultCounter = 1
webView.evaluateJavaScript("uiWebview_ScrollTo(\(result))")
}
}
}
}
}
} else {
viewModel.searchResultTotalCount = 0
viewModel.searchResultCounter = 0
webView.evaluateJavaScript("uiWebview_RemoveAllHighlights()")
}
}
.store(in: &viewModel.subscriptions)
// Observe the index to scroll to
viewModel.$searchResultCounter
.sink {
let index = viewModel.searchResultTotalCount - $0 + 1
webView.evaluateJavaScript("uiWebview_ScrollTo(\(index))")
}
.store(in: &viewModel.subscriptions)
return webView
}