搜索关键字、显示高亮关键字
参考:https://www.jianshu.com/p/67f37e1723da
在使用FLEX
(https://github.com/FLEXTool/FLEX,版本4.7.0)抓包看Response
时,默认显示是个WKWebView
,如果内容比较多则无法快速找到,添加搜索栏,搜索框、上一个、下一个、搜索匹配数,高亮黄色,当前橙色
打开网络抓包
FLEXManager.sharedManager.networkDebuggingEnabled = YES;
路径:menu
-> Network History
-> 找到请求
-> Response Body(如果有)
JS代码
WLSearchWebView.js
基于参考稍微重命名而已
//链接:https://www.jianshu.com/p/67f37e1723da
var web_searchResultCount = 0;
var web_searchResults = [];
var web_selectSpan;
var web_switchIndex = 0;
//根据关键字查找
function web_highlightAllOccurencesOfStringForElement(element, keyword) {
if (element) {
if (element.nodeType == 3) { // Text node
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
var span = document.createElement("span");
var text = document.createTextNode(value.substr(idx,keyword.length));
span.appendChild(text);
span.setAttribute("class","WebHighlight");
span.style.backgroundColor="yellow";
span.style.color="black";
text = document.createTextNode(value.substr(idx+keyword.length));
element.deleteData(idx, value.length - idx);
var next = element.nextSibling;
element.parentNode.insertBefore(span, next);
element.parentNode.insertBefore(text, next);
element = text;
web_searchResultCount++; // update the counter
web_searchResults.push(span);
}
} 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--) {
web_highlightAllOccurencesOfStringForElement(element.childNodes[i], keyword);
}
}
}
}
}
// 根据关键字查找 供WKWebView调用
function web_highlightAllOccurencesOfString(keyword) {
web_removeAllHighlights();
web_highlightAllOccurencesOfStringForElement(document.body, keyword.toLowerCase());
}
// 关键关键字 移除高亮显示
function web_removeAllHighlightsForElement(element) {
if (element) {
if (element.nodeType == 1) {
if (element.getAttribute("class") == "WebHighlight") {
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 (web_removeAllHighlightsForElement(element.childNodes[i])) {
normalize = true;
}
}
if (normalize) {
element.normalize();
}
}
}
}
return false;
}
//WKWebView调用移除高亮
function web_removeAllHighlights() {
web_searchResultCount = 0;
web_searchResults = [];
web_removeAllHighlightsForElement(document.body);
}
//滚动到搜索到的第一个关键字
function web_scrollToFiristResult() {
web_scrollToIndex(0);
}
//滚动到指定下标的关键字
function web_scrollToIndex(index) {
if (web_selectSpan) {
web_selectSpan.style.backgroundColor = "yellow";
}
var span = web_searchResults[web_searchResultCount - index - 1];
span.style.backgroundColor = "orange";
web_selectSpan = span;
var h = document.body.clientHeight; //获取设备的高度
window.scrollTo(0,span.offsetTop-h/2); //滚动到屏幕中央
}
// 上移
function web_switchToUp() {
web_switchIndex--;
if (web_switchIndex < 0) {
web_switchIndex = web_searchResultCount-1;
}
web_scrollToIndex(web_switchIndex);
return web_switchIndex;
}
// 下移
function web_switchToDown() {
web_switchIndex++;
if (web_switchIndex >= web_searchResultCount) {
web_switchIndex = 0;
}
web_scrollToIndex(web_switchIndex);
return web_switchIndex;
}
OC代码
JS调用类
建了分类WKWebView+WLSearchWebView
,WKWebView+WLSearchWebView.h
文件如下
//
// WKWebView+WLSearchWebView.h - WKWebView关键字搜索JS调用
// FLEXample
//
// Created by WeeverLu on 2022/9/4.
// Copyright © 2022 Flipboard. All rights reserved.
//
// https://www.jianshu.com/p/67f37e1723da
#import
NS_ASSUME_NONNULL_BEGIN
typedef void(^WLSearchResultBlock)(NSInteger searchCount);
@interface WKWebView (WLSearchWebView)
/// 搜索关键字
/// @param str 关键字
/// @param searchResultBlock 查找的总数
- (void)search_highlightAllOccurencesOfString:(NSString*)str searchResultBlock:(WLSearchResultBlock)searchResultBlock;
/// 滚动到指定的下标
/// @param index 指定的下标
- (void)search_scrollToIndex:(NSInteger)index;
/// 上移
- (void)search_scrollToUp;
/// 下移
- (void)search_scrollDown;
/// 移除高亮
- (void)search_removeAllHighlights;
@end
NS_ASSUME_NONNULL_END
WKWebView+WLSearchWebView.m
文件如下
//
// WKWebView+WLSearchWebView.m - WKWebView关键字搜索JS调用
// FLEXample
//
// Created by WeeverLu on 2022/9/4.
// Copyright © 2022 Flipboard. All rights reserved.
//
#import "WKWebView+WLSearchWebView.h"
#define kWLJSFileName @"WLSearchWebView"
@implementation WKWebView (WLSearchWebView)
- (void)loadSearchJavaScript {
static NSString *searchJS = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSString *path = [[NSBundle bundleForClass:NSClassFromString(@"FLEXWebViewController")] pathForResource:kWLJSFileName ofType:@"js"];
searchJS = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil];
//NSLog(@"js == %@", js);
});
if (searchJS) {
[self evaluateJavaScript:searchJS completionHandler:nil];
}
}
- (void)search_highlightAllOccurencesOfString:(NSString *)string searchResultBlock:(WLSearchResultBlock)searchResultBlock {
[self loadSearchJavaScript];
__weak typeof(self) weakSelf = self;
NSString *startSearch = [NSString stringWithFormat:@"web_highlightAllOccurencesOfString('%@')", string];
[self evaluateJavaScript:startSearch completionHandler:^(id _Nullable result, NSError * _Nullable error) {
[weakSelf evaluateJavaScript:@"web_scrollToFiristResult()" completionHandler:nil];
}];
[self evaluateJavaScript:@"web_searchResultCount" completionHandler:^(id _Nullable result, NSError * _Nullable error) {
searchResultBlock([result integerValue]);
}];
}
- (void)search_scrollToIndex:(NSInteger)index {
NSString *js = [NSString stringWithFormat:@"web_scrollToIndex(%ld)", (long)index];
[self evaluateJavaScript:js completionHandler:nil];
}
- (void)search_scrollToUp {
[self evaluateJavaScript:@"web_switchToUp()" completionHandler:nil];
}
- (void)search_scrollDown {
[self evaluateJavaScript:@"web_switchToDown()" completionHandler:nil];
}
- (void)search_removeAllHighlights {
[self evaluateJavaScript:@"web_removeAllHighlights()" completionHandler:nil];
}
@end
FLEX相关结合
Podfile
使用framework
,加入use_frameworks!
FLEX.podspec
添加资源,加入spec.resource = "Classes/**/*.{png,plist,xcassets,json,js}"
搜索栏
WLWebViewSearchToolbar.h
文件如下:
//
// WLSearchWebView.h - WKWebView关键字搜索Toolbar封装
// FLEX
//
// Created by WeeverLu on 2022/9/4.
//
#import
#import
NS_ASSUME_NONNULL_BEGIN
@interface WLWebViewSearchToolbar : UIView
@property (nonatomic, strong) UIView *parentView;
@property (nonatomic, strong) UINavigationController *navigationController;
@property (nonatomic, assign, getter=isShow) BOOL show;
- (instancetype)initWithWebView:(WKWebView *)webView;
- (void)showSearchView;
- (void)hideSearchView;
@end
NS_ASSUME_NONNULL_END
WLWebViewSearchToolbar.m
文件如下:
//
// WLSearchWebView.m - WKWebView关键字搜索Toolbar封装
// FLEX
//
// Created by WeeverLu on 2022/9/4.
//
// -----------------------------
// | textField | ← → 0
// -----------------------------
#import "WLWebViewSearchToolbar.h"
#import "WKWebView+WLSearchWebView.h"
@interface WLWebViewSearchToolbar ()
@property (nonatomic, strong) UIToolbar *toolbar;
@property (nonatomic, strong) UITextField *textField;
@property (nonatomic, weak) WKWebView *webView;
@property (nonatomic, assign) NSInteger numberOfSearchCount;
@property (nonatomic, strong) UILabel *numberOfSearchCountLbl;
@end
@implementation WLWebViewSearchToolbar
- (void)dealloc {
[NSNotificationCenter.defaultCenter removeObserver:self];
}
- (instancetype)initWithWebView:(WKWebView *)webView {
self = [super init];
if (self) {
self.autoresizingMask = UIViewAutoresizingFlexibleWidth;
self.webView = webView;
self.webView.scrollView.delegate = self;
[NSNotificationCenter.defaultCenter addObserver:self selector:@selector(textFieldTextDidChange:) name:UITextFieldTextDidChangeNotification object:nil];
}
return self;
}
- (void)layoutSubviews {
[super layoutSubviews];
//旋转处理
CGFloat height = 44;
CGFloat y = UIApplication.sharedApplication.statusBarFrame.size.height + self.navigationController.navigationBar.frame.size.height;
self.frame = CGRectMake(0, y, self.parentView.bounds.size.width, height);
CGRect textFieldRect = self.textField.frame;
textFieldRect.size.width = self.toolbar.bounds.size.width - 45*(self.toolbar.items.count-1);
self.textField.frame = textFieldRect;
}
#pragma mark - Action
- (void)previousItemDidTap {
if (self.numberOfSearchCount > 0) {
[self.webView search_scrollToUp];
}
}
- (void)nextItemDidTap {
if (self.numberOfSearchCount > 0) {
[self.webView search_scrollDown];
}
}
- (void)handleTextDidChange {
NSString *text = self.textField.text;
if (!text || text.length <= 0) {
[self resetSearchView];
return;
}
__weak typeof(self) weakSelf = self;
[self.webView search_highlightAllOccurencesOfString:text searchResultBlock:^(NSInteger searchCount) {
weakSelf.numberOfSearchCount = searchCount;
}];
}
- (void)resetSearchView {
self.textField.text = @"";
self.numberOfSearchCount = 0;
[self.webView search_removeAllHighlights];
}
- (void)showSearchView {
if (self.isShow) {
return;
}
self.show = YES;
CGFloat height = 44;
CGFloat y = UIApplication.sharedApplication.statusBarFrame.size.height + self.navigationController.navigationBar.frame.size.height;
self.frame = CGRectMake(0, y, self.parentView.bounds.size.width, height);
[self.parentView addSubview:self];
[self addSubview:self.toolbar];
[self resetSearchView];
self.alpha = 0;
[UIView animateWithDuration:0.25 animations:^{
self.webView.scrollView.contentOffset = CGPointMake(0, self.webView.scrollView.contentOffset.y-height);
self.webView.scrollView.contentInset = UIEdgeInsetsMake(height, 0, 0, 0);
self.webView.scrollView.scrollIndicatorInsets = UIEdgeInsetsMake(height, 0, 0, 0);
self.alpha = 1;
} completion:^(BOOL finished) {
[self.textField becomeFirstResponder];
}];
}
- (void)hideSearchView {
self.show = NO;
[self resetSearchView];
[UIView animateWithDuration:0.25 animations:^{
self.webView.scrollView.contentInset = UIEdgeInsetsZero;
self.webView.scrollView.scrollIndicatorInsets = UIEdgeInsetsZero;
self.alpha = 0;
} completion:^(BOOL finished) {
[self removeFromSuperview];
}];
}
- (void)scrollToFiristResult {
[self.webView search_scrollToIndex:0];
}
- (void)hideKeyboard {
[self.textField resignFirstResponder];
}
#pragma mark - UITextFieldDelegate
- (BOOL)textFieldShouldClear:(UITextField *)textField {
[self handleTextDidChange];
return YES;
}
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
[self handleTextDidChange];
[self hideKeyboard];
return YES;
}
- (void)textFieldTextDidChange:(UITextField *)textField {
[self handleTextDidChange];
}
#pragma mark - UIScrollViewDelegate
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
[self hideKeyboard];
}
#pragma mark - Setter/Getter
- (UIToolbar *)toolbar {
if (!_toolbar) {
_toolbar = [[UIToolbar alloc] initWithFrame:self.bounds];
_toolbar.autoresizingMask = UIViewAutoresizingFlexibleWidth;
UIBarButtonItem *textItem = [[UIBarButtonItem alloc] initWithCustomView:self.textField]; //textField
UIBarButtonItem *previousItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemRewind target:self action:@selector(previousItemDidTap)]; //上一个
UIBarButtonItem *nextItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFastForward target:self action:@selector(nextItemDidTap)]; //下一个
UIBarButtonItem *matchesItem = [[UIBarButtonItem alloc] initWithCustomView:self.numberOfSearchCountLbl]; //搜索匹配数
_toolbar.items = @[ textItem, previousItem, nextItem, matchesItem ];
}
return _toolbar;
}
- (UITextField *)textField {
if (!_textField) {
_textField = [[UITextField alloc] initWithFrame:CGRectMake(0, 0, 0, 30)];
_textField.placeholder = @"请输入搜索文本";
_textField.delegate = self;
_textField.clearButtonMode = UITextFieldViewModeWhileEditing;
_textField.returnKeyType = UIReturnKeyDone;
_textField.autoresizingMask = UIViewAutoresizingFlexibleWidth;
}
return _textField;
}
- (UILabel *)numberOfSearchCountLbl {
if (!_numberOfSearchCountLbl) {
UILabel *countLbl = ({
UILabel *lbl = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 40, 40)];
lbl.textAlignment = NSTextAlignmentCenter;
lbl.userInteractionEnabled = YES;
lbl.adjustsFontSizeToFitWidth = YES;
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(scrollToFiristResult)]; //双击回到第一个
tap.numberOfTapsRequired = 2;
[lbl addGestureRecognizer:tap];
lbl;
});
_numberOfSearchCountLbl = countLbl;
}
return _numberOfSearchCountLbl;
}
- (void)setNumberOfSearchCount:(NSInteger)numberOfSearchCount {
_numberOfSearchCount = numberOfSearchCount;
self.numberOfSearchCountLbl.text = [NSString stringWithFormat:@"%ld", (long)numberOfSearchCount];
}
@end
修改FLEXWebViewController.m
添加引入
#import "WLWebViewSearchToolbar.h"
#import "FLEXActivityViewController.h"
添加搜索栏
@property (nonatomic, strong) WLWebViewSearchToolbar *searchView;
- (WLWebViewSearchToolbar *)searchView {
if (!_searchView) {
_searchView = [[WLWebViewSearchToolbar alloc] initWithWebView:self.webView];
_searchView.parentView = self.view;
_searchView.navigationController = self.navigationController;
}
return _searchView;
}
修改右上角的Copy
,改为Sheet选择,复制、分享、搜索
UIBarButtonItem *shareItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAction target:self action:@selector(shareButtonTapped:)];
self.navigationItem.rightBarButtonItem = shareItem;
- (void)shareButtonTapped:(id)sender {
[FLEXAlert makeSheet:^(FLEXAlert * _Nonnull make) {
make.button(@"复制").handler(^(NSArray * _Nonnull strings) {
[UIPasteboard.generalPasteboard setString:self.originalText];
});
make.button(@"分享").handler(^(NSArray * _Nonnull strings) {
UIViewController *shareSheet = [FLEXActivityViewController sharing:@[self.originalText] source:sender];
[self presentViewController:shareSheet animated:true completion:nil];
});
NSString *searchTitle = self.searchView.isShow ? @"关闭搜索" : @"显示搜索";
make.button(searchTitle).handler(^(NSArray * _Nonnull strings) {
if (self.searchView.isShow) {
[self.searchView hideSearchView];
} else {
[self.searchView showSearchView];
}
});
make.button(@"取消").cancelStyle();
} showFrom:self source:sender];
}
FLEXExplorerViewController.m
弹出全屏,修改模式toPresent.modalPresentationStyle = UIModalPresentationFullScreen;
修改点如下:
- (void)presentViewController:(UIViewController *)toPresent
animated:(BOOL)animated
completion:(void (^)(void))completion {
// ....
// Show the view controller
toPresent.modalPresentationStyle = UIModalPresentationFullScreen;
[super presentViewController:toPresent animated:animated completion:completion];
}