简体   繁体   中英

How to Find and Highlight Text in WebView using Swift

I'm working on my first app, and it involves searching for a specific string of text from a large amount of text on a website. I want to be able to search for a specific string and highlight all instances, and as the user types more, it will automatically update the highlighted occurrences. Similar to a find function in any browser.

I found this question on here where they reference using UIWebViewSearch.js to implement this, but I'm not sure how to add that file to my project, or how to use it.

I've added a search bar to my app so far, but haven't done anything with it. Want to use this function along with the search bar to search for the text.

Here is my ViewController code:

import UIKit

class ViewController: UIViewController {

@IBOutlet weak var webView: UIWebView!
@IBOutlet weak var searchBar: UISearchBar!

override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view, typically from a nib.

    // Place custom HTML onto the screen
    let myURL = Bundle.main.url(forResource: "myHTML", withExtension: "html")
    let requestObj = URLRequest(url: myURL!)
    webView.loadRequest(requestObj)


    // Code that provides a string containing
    // JS code, ready to add to a webview.
    if let path = Bundle.main.path(forResource: "UIWebViewSearch", ofType: "js"),
        let jsString = try? String(contentsOfFile: path, encoding: .utf8) {
        print(jsString)
    } // end if


    func searchBarSearchButtonClicked() {
        let startSearch = "uiWebview_HighlightAllOccurencesOfString('\(str)')"

        self.newsWebView .stringByEvaluatingJavaScript(from: startSearch)
    }


} // end of viewDidLoad


override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
    // Dispose of any resources that can be recreated.
} // end of didReceiveMemoryWarning


} // end of class ViewController

As @jon-saw says: Welcome to StackOverflow :)

I'll just answer how you can add the JavaScript file to your project...that should get things started right :)

Add a File

You can add a file to Xcode in several ways, but here's one.

  1. In Xcode, in your project, navigate in the left hand Project navigator to where you would like to add your file.

选择地点

  1. Right click and select "New File..."

新文件

  1. Select "Empty" and click "Next"

在此处输入图片说明

  1. Name your file UIWebViewSearch.js and click "Create"

  2. You now have an empty file called UIWebViewSearch.js in your project in which you can paste in content.

Use a File

Now you have the file on your project, so now you just need to refer to it. To do so you use Bundle which can load resources in your project for you.

If you look at the answer you referred to, you can see these lines of ObjC code in - (NSInteger)highlightAllOccurencesOfString:(NSString*)str

NSString *path = [[NSBundle mainBundle] pathForResource:@"UIWebViewSearch" ofType:@"js"];
NSString *jsCode = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil];

Translated to Swift that would look like this:

if let path = Bundle.main.path(forResource: "UIWebViewSearch", ofType: "js"),
   let jsString = try? String(contentsOfFile: path, encoding: .utf8) {
    print(jsString)
}

And that should give you a String containing all your JS code, ready to add to a WebView for instance.

Hope that gives you something to start with.

Update (after you added a comment to this post)

OK, so you say:

I added my ViewController code to better communicate where I'm at. Kind of confused on how to implement the second part of his code you're referring to. Also not sure how to call the search from the searchBar. Is there a method I need to call within 'searchBarSearchButtonClicked()' method? Just found that method on the apple developer documentation, don't know if it's the correct one, though.

Lets break it down into pieces, I'll start with your ViewController , as there are some problems in your viewDidLoad :

override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view, typically from a nib.

    // Place custom HTML onto the screen
    let myURL = Bundle.main.url(forResource: "myHTML", withExtension: "html")
    let requestObj = URLRequest(url: myURL!)
    webView.loadRequest(requestObj)
    // Code that provides a string containing
    // JS code, ready to add to a webview.
    if let path = Bundle.main.path(forResource: "UIWebViewSearch", ofType: "js"),
        let jsString = try? String(contentsOfFile: path, encoding: .utf8) {
        print(jsString)
    } // end if


    func searchBarSearchButtonClicked() {
        let startSearch = "uiWebview_HighlightAllOccurencesOfString('\(str)')"

        self.newsWebView .stringByEvaluatingJavaScript(from: startSearch)
    }
}

You've added your searchBarSearchButtonClicked inside viewDidLoad , but it should be declared as a function by itself (we'll get back to it later).

Furthermore, as I wrote in the comments below:

...One part that is run when the view is loaded and which loads the JavaScript from the bundle.

So lets fix your viewDidLoad :

override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view, typically from a nib.

    // Place custom HTML onto the screen
    let myURL = Bundle.main.url(forResource: "myHTML", withExtension: "html")
    let requestObj = URLRequest(url: myURL!)
    webView.loadRequest(requestObj)
    // Code that provides a string containing
    // JS code, ready to add to a webview.
    if let path = Bundle.main.path(forResource: "UIWebViewSearch", ofType: "js"),
        let jsString = try? String(contentsOfFile: path, encoding: .utf8) {
        print(jsString)
        //Use your jsString on the web view here.
        newsWebView.stringByEvaluatingJavaScript(from: jsString) 
    }
}

As you can see, the searchBarSearchButtonClicked function has been removed and we now use out jsString on the newsWebView .

OK, next part:

Also not sure how to call the search from the searchBar. Is there a method I need to call within 'searchBarSearchButtonClicked()' method? Just found that method on the apple developer documentation, don't know if it's the correct one, though.

You have your searchBarSearchButtonClicked() method which you have found in the Apple Developer documentation, I'm guessing you're meaning this one

As you can see in the documentation (top bar), this function is from the UISearchBarDelete class.

Delegation is a design pattern used in many of Apples frameworks. It allows you to write some generic components and let "someone else" (the delegate) decide what to do with the information from the component (bad explanation I know).

Think of your UISearchBar for instance. If you were to implement a component that other developers could use as a search bar, how would you handle when the user searches for something? I'm thinking, how should your "customers" (the developers using your components) handle this? One way of doing this could be that the developers should subclass UISearchBar and override a "user did search method". You could also use NSNotifications and have the developer register for these notifications. Both of these methods are not pretty or clever.

Enter...the delegate.

UISearchBar has a UISearchBarDelegate and UISearchBarDelegate is just a protocol as you can see. Now the UISearchBar can ask its delegate for certain things, or inform the delegate about important events. So for instance, when the user taps the "Search" button UISearchBar can call delegate.searchBarSearchButtonClicked(self) which means "hey delegate, the user has clicked the search button on me...just thought you should know". And the delegate can then respond as it sees fit.

This means that any class can conform to UISearchBarDelegate and handle the various tasks as it suits in their current situation.

OK, long story, hope you get the gist of it and I think you should read a bit more on delegation as it is a pattern used all over in Apples frameworks :)

So, in your case, you have a UISearchBar , you should give that a delegate like so:

searchBar.delegate = self

And you need to tell the compiler that you intend to implement the UISearchBarDelegate protocol. The Swift convention is to do this in an extension after your ViewController , just to keep things separated:

extension ViewController: UISearchBarDelegate {

}

And then you must also implement the methods of UISearchBarDelegate that you are interested in listening to (note that some protocols have required methods, meaning that you MUST implement them but I don't think UISearchBar has (otherwise you'll find out when the app crashes the first time you run it :)).

So in your case, you mentioned searchBarSearchButtonPressed . As you can see it needs a UISearchBar as a parameter. So your function ends out looking like this:

func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
    guard let currentSearchText = searchBar.text else {
        return
    }
    let startSearch = "uiWebview_HighlightAllOccurencesOfString('\(currentSearchText)')"

    newsWebView.stringByEvaluatingJavaScript(from: startSearch)
}

So... you fetch the current text from your searchBar, add it as a parameter to your startSearch string and hand that startSearch string to your newsWebView .

The entire extensions ends out looking like this.

.... last part of your ViewController class
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    } // end of didReceiveMemoryWarning
} // end of class ViewController

extension ViewController: UISearchBarDelegate {
    func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
        guard let currentSearchText = searchBar.text else {
            return
        }
        let startSearch = "uiWebview_HighlightAllOccurencesOfString('\(currentSearchText)')"

        newsWebView.stringByEvaluatingJavaScript(from: startSearch)
    }
}

And your viewDidLoad ends out looking like this (you must add yourself as a delegate to the searchBar)

override func viewDidLoad() {
    super.viewDidLoad()
    searchBar.delegate = self //delegate set up
    // Do any additional setup after loading the view, typically from a nib.

    // Place custom HTML onto the screen
    let myURL = Bundle.main.url(forResource: "myHTML", withExtension: "html")
    let requestObj = URLRequest(url: myURL!)
    webView.loadRequest(requestObj)
    // Code that provides a string containing
    // JS code, ready to add to a webview.
    if let path = Bundle.main.path(forResource: "UIWebViewSearch", ofType: "js"),
        let jsString = try? String(contentsOfFile: path, encoding: .utf8) {
        print(jsString)
        //Use your jsString on the web view here.
        newsWebView.stringByEvaluatingJavaScript(from: jsString) 
    }
} 

Wow...that was a lot of writing...hope it helps you.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM