-
Notifications
You must be signed in to change notification settings - Fork 97
/
RXRViewController.m
339 lines (274 loc) · 10.5 KB
/
RXRViewController.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
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
//
// RXRViewController.m
// Rexxar
//
// Created by Tony Li on 11/4/15.
// Copyright © 2015 Douban Inc. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "RXRViewController.h"
#import "RXRCacheFileInterceptor.h"
#import "RXRRouteManager.h"
#import "RXRLogger.h"
#import "RXRConfig.h"
#import "RXRConfig+Rexxar.h"
#import "RXRWidget.h"
#import "UIColor+Rexxar.h"
#import "NSURL+Rexxar.h"
#import "RXRErrorHandler.h"
@interface RXRViewController ()
@property (nonatomic, copy) NSURL *htmlFileURL;
@property (nonatomic, copy) NSURL *requestURL;
@property (nonatomic, strong) NSMutableDictionary *reloadRecord;
@property (nonatomic, assign) BOOL isWebViewOnceLoaded;
@end
@implementation RXRViewController
#pragma mark - LifeCycle
- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
NSAssert(NO, @"Use initWithURI:htmlFileURL:");
return [self initWithURI:[NSURL URLWithString:@"http"] htmlFileURL:nil];
}
- (instancetype)initWithCoder:(NSCoder *)aDecoder
{
NSAssert(NO, @"Use initWithURI:htmlFileURL:");
return [self initWithURI:[NSURL URLWithString:@"http"] htmlFileURL:nil];
}
- (instancetype)initWithURI:(NSURL *)uri htmlFileURL:(NSURL *)htmlFileURL
{
self = [super initWithNibName:nil bundle:nil];
if (self) {
_uri = [uri copy];
_htmlFileURL = [htmlFileURL copy];
_reloadRecord = [NSMutableDictionary dictionary];
[RXRCacheFileInterceptor registerRXRProtocolClass:[RXRCacheFileInterceptor class]];
}
return self;
}
- (instancetype)initWithURI:(NSURL *)uri
{
return [self initWithURI:uri htmlFileURL:nil];
}
- (void)viewDidLoad
{
[super viewDidLoad];
[self reloadWebView];
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
if (self.isWebViewOnceLoaded && (!self.webView.URL || [self.webView.URL isEqual:[NSURL URLWithString:@"about:blank"]])) {
[self reloadWebView];
}
[self onPageVisible];
}
- (void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
[self onPageInvisible];
}
- (void)dealloc
{
[RXRCacheFileInterceptor unregisterRXRProtocolClass:[RXRCacheFileInterceptor class]];
[self _rxr_onPageDestroy];
}
#pragma mark - Public methods
- (void)reloadWebView
{
if (!_requestURL) {
_requestURL = [self _rxr_htmlURLWithUri:self.uri htmlFileURL:self.htmlFileURL];
}
if (_requestURL) {
[self loadRequest:[NSURLRequest requestWithURL:_requestURL]];
}
}
#pragma mark - Native Call WebView JavaScript interfaces.
- (void)onPageVisible
{
// Call the WebView's visiblity change hook for javascript.
[self callJavaScript:@"window.Rexxar.Lifecycle.onPageVisible" jsonParameter:nil];
}
- (void)onPageInvisible
{
// Call the WebView's visiblity change hook for javascript.
[self callJavaScript:@"window.Rexxar.Lifecycle.onPageInvisible" jsonParameter:nil];
}
- (void)callJavaScript:(NSString *)function jsonParameter:(NSString *)jsonParameter
{
NSString *jsCall;
if (jsonParameter) {
jsonParameter = [jsonParameter stringByReplacingOccurrencesOfString:@"\\" withString:@"\\\\"];
jsonParameter = [jsonParameter stringByReplacingOccurrencesOfString:@"\"" withString:@"\\\""];
jsonParameter = [jsonParameter stringByReplacingOccurrencesOfString:@"\'" withString:@"\\\'"];
jsonParameter = [jsonParameter stringByReplacingOccurrencesOfString:@"\n" withString:@"\\n"];
jsonParameter = [jsonParameter stringByReplacingOccurrencesOfString:@"\r" withString:@"\\r"];
jsonParameter = [jsonParameter stringByReplacingOccurrencesOfString:@"\f" withString:@"\\f"];
jsonParameter = [jsonParameter stringByReplacingOccurrencesOfString:@"\u2028" withString:@"\\u2028"];
jsonParameter = [jsonParameter stringByReplacingOccurrencesOfString:@"\u2029" withString:@"\\u2029"];
jsCall = [NSString stringWithFormat:@"%@('%@')", function, jsonParameter];
} else {
jsCall = [NSString stringWithFormat:@"%@()", function];
}
[self.webView evaluateJavaScript:jsCall completionHandler:nil];
RXRDebugLog(@"jsCall: function:%@, parameter %@", function, jsonParameter);
}
#pragma mark - RXRWebViewDelegate
- (BOOL)webView:(WKWebView *)webView
shouldStartLoadWithRequest:(NSURLRequest *)request
navigationType:(WKNavigationType)navigationType
{
NSURL *reqURL = request.URL;
if ([reqURL isEqual:_requestURL]) {
return YES;
}
// http:// or https:// 开头,则打开网页
if ([reqURL rxr_isHttpOrHttps] && navigationType == WKNavigationTypeLinkActivated) {
return ![self _rxr_openWebPage:reqURL];
}
NSString *scheme = [RXRConfig rxrProtocolScheme];
NSString *host = [RXRConfig rxrProtocolHost];
if ([request.URL.scheme isEqualToString:scheme]
&& [request.URL.host isEqualToString:host] ) {
NSURL *URL = request.URL;
for (id<RXRWidget> widget in self.widgets) {
if ([widget canPerformWithURL:URL]) {
[widget prepareWithURL:URL];
[widget performWithController:self];
RXRDebugLog(@"Rexxar callback handle: %@", URL);
return NO;
}
}
RXRDebugLog(@"Rexxar callback can not handle: %@", URL);
}
return [super webView:webView shouldStartLoadWithRequest:request navigationType:navigationType];
}
- (void)webView:(WKWebView *)webView
decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse
decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler
{
if (![navigationResponse.response isKindOfClass:[NSHTTPURLResponse class]]) {
decisionHandler(WKNavigationResponsePolicyAllow);
return;
}
// Log when not 200 and not 404
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)navigationResponse.response;
if (httpResponse.statusCode != 200
&& httpResponse.statusCode != 404
&& [RXRConfig rxr_canLog]) {
RXRLogObject *logObj = [[RXRLogObject alloc] initWithLogType:RXRLogTypeWebViewLoadNot200
error:nil
requestURL:httpResponse.URL
localFilePath:nil
otherInformation:@{logOtherInfoStatusCodeKey: @(httpResponse.statusCode)}];
[RXRConfig rxr_logWithLogObject:logObj];
}
// Deal with 404
if (!httpResponse.URL.absoluteString) {
decisionHandler(WKNavigationResponsePolicyAllow);
return;
}
NSInteger reloadCount = [_reloadRecord[httpResponse.URL.absoluteString] integerValue];
if (httpResponse.statusCode == 404 && reloadCount < RXRConfig.reloadLimitWhen404) {
decisionHandler(WKNavigationResponsePolicyCancel);
_reloadRecord[httpResponse.URL.absoluteString] = @(++reloadCount);
[[RXRRouteManager sharedInstance] updateRoutesWithCompletion:^(RXRRouteUpdateState state) {
if (state != RXRRouteUpdateStateFailed) {
self.requestURL = nil;
[self reloadWebView];
}
}];
return;
}
else if (httpResponse.statusCode == 404) {
decisionHandler(WKNavigationResponsePolicyCancel);
// Log 404 error when reload not work
[RXRConfig rxr_logWithType:RXRLogTypeWebViewLoad404 error:nil requestURL:httpResponse.URL localFilePath:nil userInfo:nil];
if ([RXRConfig rxr_canHandleError]) {
NSDictionary *userInfo = httpResponse.URL ? @{rxrErrorUserInfoURLKey: httpResponse.URL} : nil;
NSError *error = [NSError errorWithDomain:rxrHttpErrorDomain code:rxrHttpResponseErrorNotFound userInfo:userInfo];
[RXRConfig rxr_handleError:error fromReporter:self];
}
return;
}
decisionHandler(WKNavigationResponsePolicyAllow);
}
- (void)webViewDidFinishLoad:(WKWebView *)webView
{
[super webViewDidFinishLoad:webView];
self.isWebViewOnceLoaded = YES;
}
- (void)webViewDidTerminate:(WKWebView *)webView
{
[self reloadWebView];
}
#pragma mark - Private Methods
- (NSURL *)_rxr_htmlURLWithUri:(NSURL *)uri htmlFileURL:(NSURL *)htmlFileURL
{
if (!htmlFileURL) {
// 没有设置 htmlFileURL,则使用本地 html 文件或者服务器读取 html 文件。
htmlFileURL = [[RXRRouteManager sharedInstance] remoteHtmlURLForURI:self.uri];
if (!htmlFileURL && [RXRConfig rxr_canLog]) {
[RXRConfig rxr_logWithType:RXRLogTypeNoRemoteHTMLForURI error:nil requestURL:self.uri localFilePath:nil userInfo:nil];
}
if ([RXRConfig isCacheEnable]) {
// 如果缓存启用,尝试读取本地文件。如果没有本地文件(本地文件包括缓存,和资源文件夹),则从服务器读取。
NSURL *localHtmlURL = [[RXRRouteManager sharedInstance] localHtmlURLForURI:self.uri];
if (localHtmlURL) {
htmlFileURL = localHtmlURL;
}
else if (!localHtmlURL && [RXRConfig rxr_canLog]) {
[RXRConfig rxr_logWithType:RXRLogTypeNoLocalHTMLForURI error:nil requestURL:self.uri localFilePath:nil userInfo:nil];
}
}
}
if (!htmlFileURL) {
NSAssert(NO, @"Should not be here");
return nil;
}
// add uri query
NSURLComponents *comp = [NSURLComponents componentsWithURL:htmlFileURL resolvingAgainstBaseURL:YES];
if (!comp) {
NSAssert(NO, @"Should not be here");
return nil;
}
NSURLQueryItem *uriItem = [NSURLQueryItem queryItemWithName:@"uri" value:uri.absoluteString];
NSMutableArray *queryItems = [comp.queryItems mutableCopy];
if (queryItems.count && uriItem) {
[queryItems addObject:uriItem];
comp.queryItems = queryItems;
}
else if (uriItem) {
comp.queryItems = @[uriItem];
}
if ([RXRConfig useCustomScheme]) {
return [comp.URL rxr_urlByReplacingHttpWithRexxarScheme];
}
return comp.URL;
}
- (BOOL)_rxr_openWebPage:(NSURL *)url
{
// 让 App 打开网页,通常 `UIApplicationDelegate` 都会实现 open url 相关的 delegate 方法。
id<UIApplicationDelegate> delegate = [[UIApplication sharedApplication] delegate];
BOOL isIOS9Above = NO;
if (@available(iOS 9.0, *)) {
isIOS9Above = YES;
}
if (isIOS9Above && [delegate respondsToSelector:@selector(application:openURL:options:)]) {
[delegate application:[UIApplication sharedApplication]
openURL:url
options:@{}];
} else if ([delegate respondsToSelector:@selector(application:openURL:sourceApplication:annotation:)]) {
[delegate application:[UIApplication sharedApplication]
openURL:url
sourceApplication:nil
annotation:@""];
} else if ([delegate respondsToSelector:@selector(application:handleOpenURL:)]) {
[delegate application:[UIApplication sharedApplication] handleOpenURL:url];
}
return YES;
}
- (void)_rxr_onPageDestroy
{
[self callJavaScript:@"window.Rexxar.Lifecycle.onPageDestroy" jsonParameter:nil];
}
@end