如何使用 Swift 和 JavaScript 在 WKWebView 中滚动到文本

如何解决如何使用 Swift 和 JavaScript 在 WKWebView 中滚动到文本

我正在关注 this post 的答案,并尝试在带有 SearchBar 的 WKWebView 中实现 JavaScript 搜索引擎(查找、突出显示和滚动到)。它几乎可以正常工作,但 uiWebview_ScrollTo(idx) 函数存在一些问题:

  1. 当我从 idx = 0 开始时,它会滚动到页面末尾的最后一个匹配项。
  2. 当我迭代 +1 时,它会上升或返回,我的意思是实际上它显示的是前一场比赛,而不是下一场比赛。
  3. 当我迭代 -1 时什么也没有发生。

按照我的理解,索引 0 应该显示第一个匹配项(文档顶部),下一个迭代 +1,上一个迭代 -1。我不明白为什么它不能这样工作。

此外,scrollTo 完全不滚动,它只是在比赛之间立即传送您。有没有办法做得更微妙一点?并在亮点上添加动画?

iOS 14.1 目标(如果相关)。我也试过内置的 find on page 函数。与 PDFView 的选项相比,它非常糟糕,这就是我选择使用 JavaScript 的原因。

这是我的 WKWebView:

class WV: UIViewController,UISearchBarDelegate,WKNavigationDelegate {
    
    var webView = WKWebView()
    
    var request: URL?
    
    var index = 0
    
    lazy var searchBar: UISearchBar = {
        let bar = UISearchBar()
        bar.translatesAutoresizingMaskIntoConstraints = false
        return bar
    }()
    
    var bottomConstraint: NSLayoutConstraint?
    let searchContainer: UIView = {
        let view = UIView()
        return view
    }()
    
    let nextButton: UIButton = {
        let button = UIButton(type: .system)
        button.setTitle("Next",for: .normal)
        let titleColor = UIColor(red: 0,green: 137/255,blue: 249/255,alpha: 1)
        button.setTitleColor(titleColor,for: .normal)
        button.titleLabel?.font = UIFont.preferredFont(forTextStyle: .headline)
        button.addTarget(self,action: #selector(searchDocument),for: .touchUpInside)
        return button
    }()
    
    let previousButton: UIButton = {
        let button = UIButton(type: .system)
        button.setTitle("Previous",action: #selector(searchDocumentPrevious),for: .touchUpInside)
        return button
    }()
    
    let selectionLabel: UILabel = {
        let label = UILabel(frame: CGRect(x: 0,y: 0,width: 60,height: 50))
        label.textAlignment = .center
        label.font = UIFont.preferredFont(forTextStyle: .body)
        label.textColor = .systemGray
        return label
    }()
    
    let doneButton: UIButton = {
        let button = UIButton(type: .system)
        button.setTitle("Done",action: #selector(cancelFindString),for: .touchUpInside)
        return button
    }()
    
    override func viewDidLoad() {
        
        super.viewDidLoad()
        webView.isOpaque = false
        webView.navigationDelegate = self
        
        searchBar.delegate = self
        searchBar.returnKeyType = UIReturnKeyType.search
        searchBar.placeholder = "Search in the text"
        
        view.addSubview(searchContainer)
        view.addConstraintsWithFormat("H:|[v0]|",views: searchContainer)
        view.addConstraintsWithFormat("V:[v0(50)]",views: searchContainer)
        
        bottomConstraint = NSLayoutConstraint(item: searchContainer,attribute: .bottom,relatedBy: .equal,toItem: view,multiplier: 1,constant: 0)
        view.addConstraint(bottomConstraint!)
        
        searchContainer.addSubview(searchBar)
        searchContainer.addSubview(nextButton)
        searchContainer.addSubview(previousButton)
        searchContainer.addSubview(selectionLabel)
        searchContainer.addSubview(doneButton)
        
        nextButton.isHidden = true
        previousButton.isHidden = true
        selectionLabel.isHidden = true
        doneButton.isHidden = true
        searchContainer.addConstraintsWithFormat("H:|[v0]|",views: searchBar)
        searchContainer.addConstraintsWithFormat("H:|-10-[v0(80)]-20-[v1(60)]-(>=20)-[v2(120)]-(>=20)-[v3(60)]-10-|",views: previousButton,nextButton,selectionLabel,doneButton)
        searchContainer.addConstraintsWithFormat("V:|[v0]|",views: searchBar)
        searchContainer.addConstraintsWithFormat("V:|[v0]|",views: previousButton)
        searchContainer.addConstraintsWithFormat("V:|[v0]|",views: nextButton)
        searchContainer.addConstraintsWithFormat("V:|[v0]|",views: selectionLabel)
        searchContainer.addConstraintsWithFormat("V:|[v0]|",views: doneButton)
        
        webView.translatesAutoresizingMaskIntoConstraints = false
        
        view.addSubview(webView)
        
        webView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
        webView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
        webView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
        webView.bottomAnchor.constraint(equalTo: searchContainer.topAnchor).isActive = true
        
        guard let request = request else {return}
        webView.loadFileURL(request,allowingReadAccessTo: request.deletingLastPathComponent())
        
        if let path = Bundle.main.path(forResource: "UIWebViewSearch",ofType: "js"),let jsString = try? String(contentsOfFile: path,encoding: .utf8) {
                webView.evaluateJavaScript(jsString)
                
            }
    }
    
    
    
    func webView(_ webView: WKWebView,didFinish navigation: WKNavigation!) {
        insertCSSString(into: webView)

    }

    func insertCSSString(into webView: WKWebView) {
        let cssString = ":root { color-scheme: light dark; --special-text-color: hsla(60,100%,50%,0.5); --border-color: black; } @media (prefers-color-scheme: dark) { :root { --special-text-color: hsla(60,70%,0.75); --border-color: white; } } .special { color: var(--special-text-color); border: 1px solid var(--border-color); } a { color: inherit; text-decoration: inherit; }"
        let jsString = "var style = document.createElement('style'); style.innerHTML = '\(cssString)'; document.head.appendChild(style);"
        webView.evaluateJavaScript(jsString,completionHandler: nil)
    }
    


    @objc func searchDocument() {
        
        
        index += 1
        
        let getToThePoint = "uiWebview_ScrollTo('\(index)')"
        self.webView.evaluateJavaScript(getToThePoint)
        
    }
    
    @objc func searchDocumentPrevious() {
        
       // guard let searchedText = searchBar.text else { return }
        
        var index = 0
        
        index -= 1
        let getToThePoint = "uiWebview_ScrollTo('\(index)')"
        self.webView.evaluateJavaScript(getToThePoint)
        
        
    }
    
    @objc func cancelFindString() {
        nextButton.isHidden = true
        previousButton.isHidden = true
        selectionLabel.isHidden = true

        doneButton.isHidden = true
        searchBar.isHidden = false
        searchBar.becomeFirstResponder()
        selectionLabel.text = ""
    }
    
    func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
        searchBar.text = ""
        searchBar.showsCancelButton = false
        searchBar.resignFirstResponder()
        nextButton.isHidden = true
        previousButton.isHidden = true
        selectionLabel.isHidden = true
        doneButton.isHidden = true
        
        let removeHighlights = "uiWebview_RemoveAllHighlights()"
        self.webView.evaluateJavaScript(removeHighlights)
    }
    
    func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
        if searchBar.text == nil || searchBar.text == "" {
            view.endEditing(true)

        } else {
          
            searchBar.resignFirstResponder()
            
            searchBar.isHidden = true
            
            nextButton.isHidden = false
            previousButton.isHidden = false
            selectionLabel.isHidden = false
            doneButton.isHidden = false

            guard let searchedText = searchBar.text else { return }
            let startSearch = "uiWebview_HighlightAllOccurencesOfString('\(searchedText)')"
            self.webView.evaluateJavaScript(startSearch)
            
            
            let getToThePoint = "uiWebview_ScrollTo('\(index)')"
            self.webView.evaluateJavaScript(getToThePoint)

        }
    }
    
    func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) {
        searchBar.showsCancelButton = true
        nextButton.isHidden = true
        previousButton.isHidden = true
        selectionLabel.isHidden = true
        doneButton.isHidden = true
        
    }
    
    func searchBar(_ searchBar: UISearchBar,textDidChange searchText: String) {
        let removeHighlights = "uiWebview_RemoveAllHighlights()"
        self.webView.evaluateJavaScript(removeHighlights)
    }
    
    override func didReceiveMemoryWarning() {
            super.didReceiveMemoryWarning()
        }
}

这是 JavaScript 代码:

var uiWebview_SearchResultCount = 0;

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);
            }
        }
    }
}
}
function uiWebview_HighlightAllOccurencesOfString(keyword) {
uiWebview_RemoveAllHighlights();
uiWebview_HighlightAllOccurencesOfStringForElement(document.body,keyword.toLowerCase());
}
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;
 }

 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();
 }

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams[&#39;font.sans-serif&#39;] = [&#39;SimHei&#39;] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -&gt; systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping(&quot;/hires&quot;) public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate&lt;String
使用vite构建项目报错 C:\Users\ychen\work&gt;npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-
参考1 参考2 解决方案 # 点击安装源 协议选择 http:// 路径填写 mirrors.aliyun.com/centos/8.3.2011/BaseOS/x86_64/os URL类型 软件库URL 其他路径 # 版本 7 mirrors.aliyun.com/centos/7/os/x86
报错1 [root@slave1 data_mocker]# kafka-console-consumer.sh --bootstrap-server slave1:9092 --topic topic_db [2023-12-19 18:31:12,770] WARN [Consumer clie
错误1 # 重写数据 hive (edu)&gt; insert overwrite table dwd_trade_cart_add_inc &gt; select data.id, &gt; data.user_id, &gt; data.course_id, &gt; date_format(
错误1 hive (edu)&gt; insert into huanhuan values(1,&#39;haoge&#39;); Query ID = root_20240110071417_fe1517ad-3607-41f4-bdcf-d00b98ac443e Total jobs = 1
报错1:执行到如下就不执行了,没有显示Successfully registered new MBean. [root@slave1 bin]# /usr/local/software/flume-1.9.0/bin/flume-ng agent -n a1 -c /usr/local/softwa
虚拟及没有启动任何服务器查看jps会显示jps,如果没有显示任何东西 [root@slave2 ~]# jps 9647 Jps 解决方案 # 进入/tmp查看 [root@slave1 dfs]# cd /tmp [root@slave1 tmp]# ll 总用量 48 drwxr-xr-x. 2
报错1 hive&gt; show databases; OK Failed with exception java.io.IOException:java.lang.RuntimeException: Error in configuring object Time taken: 0.474 se
报错1 [root@localhost ~]# vim -bash: vim: 未找到命令 安装vim yum -y install vim* # 查看是否安装成功 [root@hadoop01 hadoop]# rpm -qa |grep vim vim-X11-7.4.629-8.el7_9.x
修改hadoop配置 vi /usr/local/software/hadoop-2.9.2/etc/hadoop/yarn-site.xml # 添加如下 &lt;configuration&gt; &lt;property&gt; &lt;name&gt;yarn.nodemanager.res