如何解决如何使用 Swift 和 JavaScript 在 WKWebView 中滚动到文本
我正在关注 this post 的答案,并尝试在带有 SearchBar 的 WKWebView 中实现 JavaScript 搜索引擎(查找、突出显示和滚动到)。它几乎可以正常工作,但 uiWebview_ScrollTo(idx) 函数存在一些问题:
- 当我从 idx = 0 开始时,它会滚动到页面末尾的最后一个匹配项。
- 当我迭代 +1 时,它会上升或返回,我的意思是实际上它显示的是前一场比赛,而不是下一场比赛。
- 当我迭代 -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 举报,一经查实,本站将立刻删除。