iOS开发常用控件之UIWebView

向晚意不适,驱车登古原。
夕阳无限好,只是近黄昏。

李商隐 《乐游原 / 登乐游原》

关于UIWebView

  • UIWebView是iOS内置的浏览器控件
    • 系统自带的Safari浏览器就是通过UIWebView实现的
  • UIWebView不但能加载远程的网页资源,还能加载绝大部分的常见文件
    • html\htm
    • pdf、doc、ppt、txt
    • mp4
    • … …
  • UIWebView常用的加载资源的方法
1
- (void)loadRequest:(NSURLRequest *)request;

常用属性和方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 重新加载(刷新)
- (void)reload;
// 停止加载
- (void)stopLoading;
// 回退
- (void)goBack;
// 前进
- (void)goForward;
// 需要进行检测的数据类型
@property(nonatomic) UIDataDetectorTypes dataDetectorTypes
// 是否能回退
@property(nonatomic,readonly,getter=canGoBack) BOOL canGoBack;
// 是否能前进
@property(nonatomic,readonly,getter=canGoForward) BOOL canGoForward;
// 是否正在加载中
@property(nonatomic,readonly,getter=isLoading) BOOL loading;
// 是否伸缩内容至适应屏幕当前尺寸
@property(nonatomic) BOOL scalesPageToFit;

UIWebViewd代理

  • 成为UIWebView的代理,遵守UIWebViewDelegate协议,就能监听UIWebView的加载过程
1
2
3
4
5
6
7
8
// 开始发送请求(加载数据)时调用这个方法
- (void)webViewDidStartLoad:(UIWebView *)webView;

// 请求完毕(加载数据完毕)时调用这个方法
- (void)webViewDidFinishLoad:(UIWebView *)webView;

// 请求错误时调用这个方法
- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
* webView加载一个请求之前 会调用这个方法 返回yes 可以加载 返回no 不允许加载
* 可以着这里拦截请求 转化为方法名 调用oc方法
*/
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType{

// NSLog(@"%@",request);
// { URL: ios://openMyCamera }
// { URL: ios://openMyAlbum }

NSString *url = request.URL.absoluteString;
NSRange range = [url rangeOfString:@"ios://"];

if (range.length) {

// 截取需要得部分
NSString *method = [url substringFromIndex:range.location + range.length];
// 包装成SEL
SEL selector = NSSelectorFromString(method);

// 执行对应的方法
[self performSelector:selector withObject:nil];
}
return YES;
}

JS和OC互调

JavaScript

  • JavaScript是一门脚本语言,简称JS
  • JS的常见作用有:

    • 给HTML网页添加动态功能,比如响应用户的各种操作。
    • 操纵HTML元素,比如添加删除修改网页元素。
  • 常见的JavaScript函数:

    • alert(10); // 弹框
    • document.getElementById(‘test’); // 根据ID获得某个DOM元素

OC调用JS的方法

1
2
3
4
5
6
7
// 如何在OC中调用JavaScript代码
// 清除广告就是调用javascript代码 删除 header 和 footer
// document.getElementsByTagName('header')[0].remove();
// document.getElementsByTagName('footer')[0].remove()

[self.webView stringByEvaluatingJavaScriptFromString:@"document.getElementsByTagName('header')[0].remove();"];
[self.webView stringByEvaluatingJavaScriptFromString:@"document.getElementsByTagName('footer')[0].remove();"];

JS调用OC的方法

  • 网页信息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<html>
<!--描述网页信息-->
<head>
<meta charset="UTF-8"/>
<title>hello world</title>
<script>
function show()
{
alert(1);
}
function showTitle()
{
alert(document.title);
}
function repost()
{
location.href = "http://www.520it.com";
}
function sum()
{
return 1 + 1;
}
function btnClick()
{
location.href = "xmg://sendMessageWithNumber_andContent_?10086";
}
</script>
</head>
<!--网页具体内容-->
<body>
电话号码: 18888888888</br>
邮箱: 666666666@qq.com</br>

<button style = "background: red; height: 150px; width: 150px;" onclick = "btnClick();">哥是按钮</button>
</body>
</html>

无参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#pragma mark - UIWebViewDelegate

// 每次发送请求前都会调用
// 利用该方法作为JS和OC之间的桥梁
// 在JS跳转网页
// 在OC代理方法中通过判断自定义协议头, 决定是否是JS调用OC方法
// 在OC代理方法中通过截取字符串, 获取JS想调用的OC方法名称
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
NSString *schem = @"xmg://";
NSString *urlStr = request.URL.absoluteString;
if ([urlStr hasPrefix:schem]) {
NSLog(@"需要调用OC方法");
// 1.从URL中获取方法名称
// xmg://call
NSString *methodName = [urlStr substringFromIndex:schem.length];
NSLog(@"%@", methodName);
// 2.调用方法
SEL sel = NSSelectorFromString(methodName);
// 忽略警告信息的作用范围开始
#pragma clang diagnostic push
// 下面这一行代码是用于指定需要忽略的警告信息
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[self performSelector:sel withObject:nil];
// 忽略警告信息的作用范围结束
#pragma clang diagnostic pop
return NO;
}
return YES;
}

有参数(基本方法)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#pragma mark - UIWebViewDelegate

// 每次发送请求前都会调用
// 利用该方法作为JS和OC之间的桥梁
// 在JS跳转网页
// 在OC代理方法中通过判断自定义协议头, 决定是否是JS调用OC方法
// 在OC代理方法中通过截取字符串, 获取JS想调用的OC方法名称
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
NSString *schem = @"xmg://";
NSString *urlStr = request.URL.absoluteString;
if ([urlStr hasPrefix:schem]) {
NSLog(@"需要调用OC方法");
// 1.从URL中获取方法名称
// xmg://sendMessageWithNumber_andContent_?10086&love
NSString *subPath = [urlStr substringFromIndex:schem.length];
// 注意: 如果指定的用于切割的字符串不存在, 那么就会返回整个字符串
NSArray *subPaths = [subPath componentsSeparatedByString:@"?"];
// 2.获取方法名称
NSString *methodName = [subPaths firstObject];
methodName = [methodName stringByReplacingOccurrencesOfString:@"_" withString:@":"];
NSLog(@"%@", methodName);
// 2.调用方法
SEL sel = NSSelectorFromString(methodName);

// 3.处理参数
NSString *parma = nil;
if (subPaths.count == 2) {
parma = [subPaths lastObject];
// 3.截取参数
NSArray *parmas = [parma componentsSeparatedByString:@"&"];
[self performSelector:sel withObject:[parmas firstObject] withObject:[parmas lastObject]];
return NO;
}
[self performSelector:sel withObject:parma];
return NO;
}
return YES;
}

有参数(NSInvocation封装方法)(推荐)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#pragma mark - UIWebViewDelegate

// 每次发送请求前都会调用
// 利用该方法作为JS和OC之间的桥梁
// 在JS跳转网页
// 在OC代理方法中通过判断自定义协议头, 决定是否是JS调用OC方法
// 在OC代理方法中通过截取字符串, 获取JS想调用的OC方法名称
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
// xmg://
NSString *schem = @"xmg://";
NSString *urlStr = request.URL.absoluteString;
if ([urlStr hasPrefix:schem]) {
NSLog(@"需要调用OC方法");
// 1.从URL中获取方法名称
// sendMessageWithNumber_andContent_?10086&love
NSString *subPath = [urlStr substringFromIndex:schem.length];
// 注意: 如果指定的用于切割的字符串不存在, 那么就会返回整个字符串
// sendMessageWithNumber_andContent_
// 10086&love
NSArray *subPaths = [subPath componentsSeparatedByString:@"?"];
// 2.获取方法名称
NSString *methodName = [subPaths firstObject];
methodName = [methodName stringByReplacingOccurrencesOfString:@"_" withString:@":"];
NSLog(@"%@", methodName);
// 2.调用方法
SEL sel = NSSelectorFromString(methodName);
// 3.处理参数
NSArray *parmas = nil;
if (subPaths.count == 2) {
parmas = [[subPaths lastObject] componentsSeparatedByString:@"&"];
}
[self performSelector:sel withObjects:parmas];
return NO;
}

return YES;
}
// 测试的方法如下:
- (void)call
{
NSLog(@"%s", __func__);
}
- (void)callWithNumber:(NSString *)number
{
NSLog(@"打电话给%@", number);
}
- (void)sendMessageWithNumber:(NSString *)number andContent:(NSString *)content
{
NSLog(@"发信息给%@, 内容是%@", number, content);
}

- (void)sendMessageWithNumber:(NSString *)number andContent:(NSString *)content status:(NSString *)status
{
NSLog(@"发信息给%@, 内容是%@, 发送的状态是%@", number, content, status);
}

NSInvocation

基本使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// Signature签名: 在创建NSInvocation的时候, 必须传递一个签名对象
// 签名对象的作用 : 用于获取参数的个数和方法的返回值
// 注意点: 创建签名对象的时候不是使用NSMethodSignature类创建 \ 而是方法属于谁就用谁来创建
NSMethodSignature *signature = [ViewController instanceMethodSignatureForSelector:@selector(sendMessageWithNumber:andContent:status:)];

// NSInvocation; 用来包装方法和对应的对象, 它可以存储方法的名称,对应的对象 ,对应的参数
// 1.创建一个NSInvocation对象
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];

invocation.target = self; // 保存方法所属的对象
// 给invocation设置的方法, 必须和签名中的方法一致
invocation.selector = @selector(sendMessageWithNumber:andContent:status:); // 保存方法名称

// 第一个参数: 需要给指定方法传递的值
// + 第一个参数需要接收一个指针, 也就是传递值的时候需要传递地址
// 第二个参数: 需要给指定方法的第几个参数传值
NSString *number = @"10086";
// 注意: 设置参数的索引时不能从0开始, 因为0已经被self占用, 1已经被_cmd占用
[invocation setArgument:&number atIndex:2];

NSString *content = @"love";
[invocation setArgument:&content atIndex:3];

NSString *status = @"success";
[invocation setArgument:&status atIndex:4];

// 2.调用NSInvocation对象的invoke方法
// 只要调用invocation的invoke方法, 就代表需要执行 \
NSInvocation对象中指定对象的指定方法, 并且传递指定的参数
[invocation invoke];

NSInvocation方法封装

.h文件

1
2
3
4
5
6
#import <Foundation/Foundation.h>

@interface NSObject (performSelector)

- (id)performSelector:(SEL)aSelector withObjects:(NSArray *)objects;
@end

.m文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
#import "NSObject+performSelector.h"

@implementation NSObject (performSelector)

- (id)performSelector:(SEL)aSelector withObjects:(NSArray *)objects
{
// 1.创建签名对象
NSMethodSignature *signature = [[self class] instanceMethodSignatureForSelector:aSelector];

// 判断传入的方法是否存储, 如果方法不存在签名对象为nil
if (signature == nil) {
// 传入的方法不存在
NSString *info = [NSString stringWithFormat:@" -[%@ %@]: unrecognized selector sent to instance", [self class], NSStringFromSelector(aSelector)];

@throw [[NSException alloc] initWithName:@"一个牛B的错误" reason:info userInfo:nil];
}

// 2.创建一个NSInvocation对象
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];

// 3.保存方法所属的对象
invocation.target = self;

// 给invocation设置的方法, 必须和签名中的方法一致
// 4.保存方法名称
invocation.selector = aSelector;

// 5.设置参数
/*
当前如果直接遍历参数数组来设置参数, 会存在问题
如果参数数组元素多余参数个数, 那么就会报错
*/
NSUInteger arguments = signature.numberOfArguments - 2;
/*
如果直接遍历参数值的个数, 会存在问题
如果参数的个数大于了参数值的个数, 那么数组会越界
*/
NSUInteger objectsCount = objects.count;
/*
参数和参数值, 谁少就遍历谁
*/
NSUInteger count = MIN(arguments, objectsCount);

for (int i = 0; i < count; i++) {
NSObject *obj = objects[i];
// 处理数组参数中NSNull问题
if ([obj isKindOfClass:[NSNull class]]) {
obj = nil;
}
[invocation setArgument:&obj atIndex:i + 2];
}
// 6.调用NSInvocation对象的invoke方法
[invocation invoke];

id res = nil;
// 判断当前方法是否有返回值
// NSLog(@"ReturnType = %zd", signature.methodReturnLength);
if ( signature.methodReturnLength != 0) {
// 7.获取返回值
// getReturnValue方法会将会去到的方法返回值赋值给传入的对象
[invocation getReturnValue:&res];
}
return res;
}
@end
要不要鼓励一下😘