├── Image └── PAWebview.png ├── LICENSE ├── PAWebView.podspec ├── PAWebView ├── PAURLProtocol │ ├── NSURLProtocol+WKWebView.h │ ├── NSURLProtocol+WKWebView.m │ ├── PAURLProtocol.h │ └── PAURLProtocol.m ├── PAWKNative │ ├── PAWKPanGestureRecognizer.h │ ├── PAWKPanGestureRecognizer.m │ ├── PAWebNavigationController.h │ ├── PAWebNavigationController.m │ ├── WKBaseWebView.h │ ├── WKBaseWebView.m │ └── views │ │ ├── PAWebBack.png │ │ ├── UIAlertController+WKWebAlert.h │ │ ├── UIAlertController+WKWebAlert.m │ │ ├── navigationbar_more@2x.png │ │ └── navigationbar_more@3x.png ├── PAWebView+UIDelegate.h ├── PAWebView+UIDelegate.m ├── PAWebView.h ├── PAWebView.m ├── PAWebViewCache │ ├── NSObject+PARuntime.h │ ├── NSObject+PARuntime.m │ ├── WKWebView+PAWebCache.h │ ├── WKWebView+PAWebCache.m │ ├── WKWebView+PAWebCookie.h │ └── WKWebView+PAWebCookie.m ├── PAWebViewExtern │ ├── PAPhotoBrowser │ │ ├── PAPhotoBrowser.h │ │ └── PAPhotoBrowser.m │ └── TYSnapshot │ │ ├── PreviewVc.h │ │ ├── PreviewVc.m │ │ ├── TYSnapshot.h │ │ ├── TYSnapshot.m │ │ ├── UIImage+TYSnapshot.h │ │ ├── UIImage+TYSnapshot.m │ │ ├── UIScrollView+TYSnapshot.h │ │ ├── UIScrollView+TYSnapshot.m │ │ ├── UITableView+TYSnapshot.h │ │ ├── UITableView+TYSnapshot.m │ │ ├── WKWebView+TYSnapshot.h │ │ └── WKWebView+TYSnapshot.m ├── PAWebViewTool │ ├── NSURL+PATool.h │ ├── NSURL+PATool.m │ ├── PAWebViewMenu.h │ ├── PAWebViewMenu.m │ ├── WKWebView+LongPress.h │ ├── WKWebView+LongPress.m │ ├── WKWebView+POST.h │ ├── WKWebView+POST.m │ ├── registerURLSchemes.h │ ├── registerURLSchemes.m │ ├── urlschemeModel.h │ └── urlschemeModel.m └── WKJSFunction.m ├── PAWebViewDemo.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── xcuserdata │ └── llyouss.xcuserdatad │ ├── xcdebugger │ └── Breakpoints_v2.xcbkptlist │ └── xcschemes │ └── xcschememanagement.plist ├── PAWebViewDemo ├── AppDelegate.h ├── AppDelegate.m ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── Info.plist ├── ViewController.h ├── ViewController.m ├── iOS.html ├── main.html └── main.m ├── PAWebViewDemoTests ├── Info.plist └── PAWebViewDemoTests.m ├── PAWebViewDemoUITests ├── Info.plist └── PAWebViewDemoUITests.m └── README.md /Image/PAWebview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/llyouss/PAWebView/d045f474a49200170e52d7894c920e8014046b2a/Image/PAWebview.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 llyouss 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /PAWebView.podspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | Pod::Spec.new do |s| 4 | 5 | s.name = "PAWebView" 6 | s.version = "0.2.1" 7 | s.summary = " An component WebView for iOS base on WKWebView ." 8 | s.description = <<-DESC 9 | PAWeView is an extensible WebView which is built on top of WKWebView, the modern WebKit framework debuted in iOS 8.0. It provides fast Web for developing sophisticated iOS native or hybrid applications. 10 | DESC 11 | 12 | s.homepage = "https://github.com/llyouss/PAWeView" 13 | s.license = { :type => "MIT", :file => "LICENSE" } 14 | s.author = { "Luoshengyou" => "13798686545@163.com" } 15 | s.platform = :ios, "8.0" 16 | s.requires_arc = true 17 | s.source = { :git => "https://github.com/llyouss/PAWeView.git", :tag => "0.2.1" } 18 | s.source_files ="PAWebView/**/*.{m,h}" 19 | s.resource_bundles = { 20 | 'PAWebView' => ['PAWebView/PAWKNative/views/*.png'] 21 | } 22 | s.resources = "PAWebView/PAWKNative/views/*.png" 23 | 24 | end 25 | -------------------------------------------------------------------------------- /PAWebView/PAURLProtocol/NSURLProtocol+WKWebView.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSURLProtocol+WKWebView.h 3 | // Pkit 4 | // 5 | // Created by llyouss on 2018/9/7. 6 | // Copyright © 2018年 llyouss. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface NSURLProtocol (WKWebView) 12 | 13 | + (void)wk_registerScheme:(NSString*)scheme; 14 | 15 | + (void)wk_unregisterScheme:(NSString*)scheme; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /PAWebView/PAURLProtocol/NSURLProtocol+WKWebView.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSURLProtocol+WKWebView.m 3 | // Pkit 4 | // 5 | // Created by llyouss on 2018/9/7. 6 | // Copyright © 2018年 llyouss. All rights reserved. 7 | // 8 | 9 | #import "NSURLProtocol+WKWebView.h" 10 | #import 11 | 12 | FOUNDATION_STATIC_INLINE Class ContextControllerClass() { 13 | static Class cls; 14 | if (!cls) { 15 | cls = [[[WKWebView new] valueForKey:@"browsingContextController"] class]; 16 | } 17 | return cls; 18 | } 19 | 20 | FOUNDATION_STATIC_INLINE SEL RegisterSchemeSelector() { 21 | return NSSelectorFromString(@"registerSchemeForCustomProtocol:"); 22 | } 23 | 24 | FOUNDATION_STATIC_INLINE SEL UnregisterSchemeSelector() { 25 | return NSSelectorFromString(@"unregisterSchemeForCustomProtocol:"); 26 | } 27 | 28 | @implementation NSURLProtocol (WKWebView) 29 | 30 | 31 | + (void)wk_registerScheme:(NSString *)scheme { 32 | Class cls = ContextControllerClass(); 33 | SEL sel = RegisterSchemeSelector(); 34 | if ([(id)cls respondsToSelector:sel]) { 35 | // 放弃编辑器警告 36 | #pragma clang diagnostic push 37 | #pragma clang diagnostic ignored "-Warc-performSelector-leaks" 38 | [(id)cls performSelector:sel withObject:scheme]; 39 | #pragma clang diagnostic pop 40 | } 41 | } 42 | 43 | + (void)wk_unregisterScheme:(NSString *)scheme { 44 | Class cls = ContextControllerClass(); 45 | SEL sel = UnregisterSchemeSelector(); 46 | if ([(id)cls respondsToSelector:sel]) { 47 | // 放弃编辑器警告 48 | #pragma clang diagnostic push 49 | #pragma clang diagnostic ignored "-Warc-performSelector-leaks" 50 | [(id)cls performSelector:sel withObject:scheme]; 51 | #pragma clang diagnostic pop 52 | } 53 | } 54 | 55 | @end 56 | -------------------------------------------------------------------------------- /PAWebView/PAURLProtocol/PAURLProtocol.h: -------------------------------------------------------------------------------- 1 | // 2 | // PAURLProtocol.h 3 | // Pkit 4 | // 5 | // Created by llyouss on 2018/9/7. 6 | // Copyright © 2018年 llyouss. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface PAURLProtocol : NSURLProtocol 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /PAWebView/PAURLProtocol/PAURLProtocol.m: -------------------------------------------------------------------------------- 1 | // 2 | // PAURLProtocol.m 3 | // Pkit 4 | // 5 | // Created by llyouss on 2018/9/7. 6 | // Copyright © 2018年 llyouss. All rights reserved. 7 | // 8 | 9 | #import "PAURLProtocol.h" 10 | #import 11 | 12 | static NSString* const PAURLProtocolKey = @"PAURLProtocol"; 13 | 14 | @interface PAURLProtocol () 15 | 16 | @property (nonnull,strong) NSURLSessionDataTask *task; 17 | 18 | @end 19 | 20 | @implementation PAURLProtocol 21 | 22 | + (BOOL)canInitWithRequest:(NSURLRequest *)request 23 | { 24 | NSLog(@"request.URL.absoluteString = %@",request.URL.absoluteString); 25 | NSString *scheme = [[request URL] scheme]; 26 | //过滤交互请求 27 | if ( ([scheme caseInsensitiveCompare:@"http"] == NSOrderedSame || 28 | [scheme caseInsensitiveCompare:@"https"] == NSOrderedSame )) 29 | { 30 | //看看是否已经处理过了,防止无限循环 31 | if ([NSURLProtocol propertyForKey:PAURLProtocolKey inRequest:request]) 32 | return NO; 33 | return YES; 34 | } 35 | return NO; 36 | } 37 | 38 | + (NSMutableURLRequest *)canonicalRequestForRequest:(NSMutableURLRequest *)request 39 | { 40 | // request截取重定向 41 | 42 | // if ([request.URL host].length == 0) { 43 | // return request; 44 | // } 45 | // 46 | // NSString *originUrlString = [request.URL absoluteString]; 47 | // NSString *originHostString = [request.URL host]; 48 | // NSRange hostRange = [originUrlString rangeOfString:originHostString]; 49 | // if (hostRange.location == NSNotFound) { 50 | // return request; 51 | // } 52 | // //定向薄荷喵到主页 53 | // NSString *ip = @"bohemiao.com"; 54 | // 55 | // // 替换域名 56 | // NSString *urlString = [originUrlString stringByReplacingCharactersInRange:hostRange withString:ip]; 57 | // NSURL *url = [NSURL URLWithString:urlString]; 58 | // request.URL = url; 59 | 60 | return request; 61 | } 62 | 63 | + (BOOL)requestIsCacheEquivalent:(NSURLRequest *)a toRequest:(NSURLRequest *)b 64 | { 65 | return [super requestIsCacheEquivalent:a toRequest:b]; 66 | } 67 | 68 | - (void)startLoading 69 | { 70 | NSMutableURLRequest *mutableReqeust = [[self request] mutableCopy]; 71 | 72 | //给我们处理过的请求设置一个标识符, 防止无限循环, 73 | [NSURLProtocol setProperty:@YES forKey:PAURLProtocolKey inRequest:mutableReqeust]; 74 | 75 | //这里最好加上缓存判断,加载本地离线文件 76 | NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:nil]; 77 | self.task = [session dataTaskWithRequest:self.request]; 78 | [self.task resume]; 79 | } 80 | - (void)stopLoading 81 | { 82 | if (self.task != nil) 83 | { 84 | [self.task cancel]; 85 | } 86 | } 87 | 88 | - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler { 89 | [[self client] URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageAllowed]; 90 | 91 | completionHandler(NSURLSessionResponseAllow); 92 | } 93 | 94 | - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data { 95 | 96 | NSString *html = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding]; 97 | [[self client] URLProtocol:self didLoadData:data]; 98 | } 99 | 100 | - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(nullable NSError *)error { 101 | [self.client URLProtocolDidFinishLoading:self]; 102 | } 103 | 104 | - (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential * _Nullable))completionHandler{ 105 | 106 | NSString *hostName = self.request.URL.host; 107 | NSString *authenticationMethod = [[challenge protectionSpace] authenticationMethod]; 108 | if ([authenticationMethod isEqualToString:NSURLAuthenticationMethodDefault] 109 | || [authenticationMethod isEqualToString:NSURLAuthenticationMethodHTTPBasic] 110 | || [authenticationMethod isEqualToString:NSURLAuthenticationMethodHTTPDigest]) { 111 | 112 | NSString *title = @"Authentication Challenge"; 113 | NSString *message = [NSString stringWithFormat:@"%@ requires user name and password", hostName]; 114 | UIAlertController *alertController = [UIAlertController alertControllerWithTitle:title message:message preferredStyle:UIAlertControllerStyleAlert]; 115 | [alertController addTextFieldWithConfigurationHandler:^(UITextField *textField) { 116 | textField.placeholder = @"User"; 117 | //textField.secureTextEntry = YES; 118 | }]; 119 | [alertController addTextFieldWithConfigurationHandler:^(UITextField *textField) { 120 | textField.placeholder = @"Password"; 121 | textField.secureTextEntry = YES; 122 | }]; 123 | [alertController addAction:[UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { 124 | 125 | NSString *userName = ((UITextField *)alertController.textFields[0]).text; 126 | NSString *password = ((UITextField *)alertController.textFields[1]).text; 127 | 128 | NSURLCredential *credential = [[NSURLCredential alloc] initWithUser:userName password:password persistence:NSURLCredentialPersistenceNone]; 129 | 130 | completionHandler(NSURLSessionAuthChallengeUseCredential, credential); 131 | 132 | }]]; 133 | [alertController addAction:[UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) { 134 | completionHandler(NSURLSessionAuthChallengeUseCredential, nil); 135 | }]]; 136 | dispatch_async(dispatch_get_main_queue(), ^{ 137 | [[UIApplication sharedApplication].keyWindow.rootViewController presentViewController:alertController animated:YES completion:^{}]; 138 | }); 139 | 140 | } 141 | else if ([authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) { 142 | // needs this handling on iOS 9 143 | NSURLCredential *card = [[NSURLCredential alloc]initWithTrust:challenge.protectionSpace.serverTrust]; 144 | challenge.sender ? completionHandler(NSURLSessionAuthChallengeUseCredential,card) : NULL; 145 | } 146 | else { 147 | completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil); 148 | } 149 | } 150 | 151 | @end 152 | -------------------------------------------------------------------------------- /PAWebView/PAWKNative/PAWKPanGestureRecognizer.h: -------------------------------------------------------------------------------- 1 | // 2 | // PAWKPanGestureRecognizer.h 3 | // Pkit 4 | // 5 | // Created by llyouss on 2017/12/15. 6 | // Copyright © 2017年 llyouss. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface PAWKPanGestureRecognizer : UIPanGestureRecognizer 12 | 13 | @property (readonly, nonatomic) UIEvent *event; //屏幕的手势事件 14 | 15 | - (CGPoint)startPointWithView:(UIView *)view; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /PAWebView/PAWKNative/PAWKPanGestureRecognizer.m: -------------------------------------------------------------------------------- 1 | // 2 | // PAWKPanGestureRecognizer.m 3 | // Pkit 4 | // 5 | // Created by llyouss on 2017/12/15. 6 | // Copyright © 2017年 llyouss. All rights reserved. 7 | // 8 | 9 | #import "PAWKPanGestureRecognizer.h" 10 | #import 11 | 12 | @implementation PAWKPanGestureRecognizer 13 | { 14 | CGPoint _startLocation; 15 | } 16 | 17 | 18 | - (CGPoint)startPointWithView:(UIView *)view 19 | { 20 | return [view convertPoint:_startLocation toView:self.view]; 21 | } 22 | 23 | 24 | - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event 25 | { 26 | UITouch *touch = [touches anyObject]; 27 | _startLocation = [touch locationInView:self.view]; 28 | _event = event; 29 | [super touchesBegan:touches withEvent:event]; 30 | } 31 | 32 | - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event 33 | { 34 | if (self.state == UIGestureRecognizerStatePossible || event.timestamp - _event.timestamp > 0.3) { 35 | self.state = UIGestureRecognizerStateFailed; 36 | return; 37 | } 38 | [super touchesMoved:touches withEvent:event]; 39 | } 40 | 41 | - (void)reset 42 | { 43 | _startLocation = CGPointZero; 44 | _event = nil; 45 | [super reset]; 46 | } 47 | 48 | @end 49 | -------------------------------------------------------------------------------- /PAWebView/PAWKNative/PAWebNavigationController.h: -------------------------------------------------------------------------------- 1 | // 2 | // PAWebNavigationController.h 3 | // Pkit 4 | // 5 | // Created by llyouss on 2017/12/19. 6 | // Copyright © 2017年 llyouss. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface PAWebNavigationController : UINavigationController 12 | 13 | @property (readonly, nonatomic) UIPanGestureRecognizer *panGestureRecognizer; //侧滑手势 14 | @property (nonatomic, assign) BOOL isEnableScroll; 15 | 16 | 17 | @end 18 | 19 | @protocol LCPanBackProtocol 20 | 21 | /** 22 | 能否侧滑 23 | 24 | @param panNavigationController panNavigationController 25 | @return BooL 26 | */ 27 | - (BOOL)enablePanBack:(PAWebNavigationController *)panNavigationController; 28 | 29 | /** 30 | 开始侧滑手势 31 | 32 | @param panNavigationController panNavigationController 33 | */ 34 | - (void)startPanBack:(PAWebNavigationController *)panNavigationController; 35 | 36 | /** 37 | 完成侧滑 38 | 39 | @param panNavigationController panNavigationController 40 | */ 41 | - (void)finshPanBack:(PAWebNavigationController *)panNavigationController; 42 | 43 | /** 44 | 重置侧滑手势 45 | 46 | @param panNavigationController panNavigationController 47 | */ 48 | - (void)resetPanBack:(PAWebNavigationController *)panNavigationController; 49 | 50 | @end 51 | 52 | 53 | -------------------------------------------------------------------------------- /PAWebView/PAWKNative/PAWebNavigationController.m: -------------------------------------------------------------------------------- 1 | // 2 | // PAWebNavigationController.m 3 | // Pkit 4 | // 5 | // Created by llyouss on 2017/12/19. 6 | // Copyright © 2017年 llyouss. All rights reserved. 7 | // 8 | 9 | #import "PAWebNavigationController.h" 10 | #import "PAWKPanGestureRecognizer.h" 11 | #import 12 | 13 | @interface PAWebNavigationController () 14 | 15 | @property (nonatomic,retain)PAWKPanGestureRecognizer *pan; 16 | 17 | @property (assign, nonatomic) BOOL animatedFlag; 18 | 19 | @end 20 | 21 | @implementation PAWebNavigationController 22 | 23 | - (void)setViewControllers:(NSArray *)viewControllers { 24 | [super setViewControllers:viewControllers]; 25 | } 26 | 27 | - (void)viewDidLoad { 28 | [super viewDidLoad]; 29 | 30 | self.interactivePopGestureRecognizer.enabled = NO; //禁用系统侧滑 31 | self.view.backgroundColor = [UIColor whiteColor]; 32 | 33 | _pan = [[PAWKPanGestureRecognizer alloc]initWithTarget:self action:@selector(pan:)]; 34 | _pan.delegate = self; 35 | 36 | _pan.maximumNumberOfTouches = 1; 37 | [self.view addGestureRecognizer:_pan]; 38 | self.isEnableScroll = YES; //默认开启侧滑 39 | 40 | } 41 | 42 | #pragma mark Pan 43 | - (void)pan:(UIPanGestureRecognizer *)pan 44 | { 45 | UIGestureRecognizerState state = pan.state; 46 | switch (state){ 47 | 48 | case UIGestureRecognizerStatePossible: 49 | 50 | break; 51 | case UIGestureRecognizerStateBegan: 52 | 53 | break; 54 | case UIGestureRecognizerStateChanged: 55 | { 56 | CGPoint translationPoint = [self.pan translationInView:self.view]; 57 | self.visibleViewController.view.transform = CGAffineTransformTranslate(CGAffineTransformIdentity, translationPoint.x, 0); 58 | 59 | break; 60 | } 61 | case UIGestureRecognizerStateEnded: 62 | 63 | break; 64 | case UIGestureRecognizerStateCancelled: 65 | 66 | break; 67 | default: 68 | break; 69 | } 70 | } 71 | 72 | #pragma mark GestureRecognizer Delegate 73 | #define MIN_TAN_VALUE tan(M_PI / 6) 74 | 75 | - (BOOL)gestureRecognizerShouldBegin:(UIPanGestureRecognizer *)gestureRecognizer { 76 | 77 | if (self.viewControllers.count < 2) return NO; 78 | if (self.animatedFlag) return NO; 79 | if (![self enablePanBack]) return NO; // 询问当前viewconroller 是否允许右滑返回 80 | 81 | CGPoint touchPoint = [gestureRecognizer locationInView:self.visibleViewController.view.superview]; 82 | if (touchPoint.x < 0 || touchPoint.y < 10 || touchPoint.x > 220) return NO; 83 | 84 | CGPoint translation = [gestureRecognizer translationInView:self.view]; 85 | if (translation.x <= 0) return NO; 86 | 87 | // 是否是右滑 88 | BOOL succeed = fabs(translation.y / translation.x) < MIN_TAN_VALUE; 89 | if (!self.isEnableScroll) { //个别页面不允许侧滑 90 | succeed = NO; 91 | } 92 | 93 | return succeed; 94 | } 95 | 96 | - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer { 97 | if (gestureRecognizer != self.pan) return NO; 98 | if (self.pan.state != UIGestureRecognizerStateBegan) return NO; 99 | 100 | if (otherGestureRecognizer.state != UIGestureRecognizerStateBegan) { 101 | 102 | return YES; 103 | } 104 | 105 | CGPoint touchPoint = [_pan startPointWithView:self.visibleViewController.view.superview]; 106 | 107 | // 点击区域判断 如果在左边 30 以内, 强制手势后退 108 | if (touchPoint.x < 30) { 109 | 110 | [self cancelOtherGestureRecognizer:otherGestureRecognizer]; 111 | return YES; 112 | } 113 | 114 | // 如果是scrollview 判断scrollview contentOffset 是否为0,是 cancel scrollview 的手势,否cancel自己 115 | if ([[otherGestureRecognizer view] isKindOfClass:[UIScrollView class]]) { 116 | UIScrollView *scrollView = (UIScrollView *)[otherGestureRecognizer view]; 117 | if (scrollView.contentOffset.x <= 0) { 118 | 119 | [self cancelOtherGestureRecognizer:otherGestureRecognizer]; 120 | return YES; 121 | } 122 | } 123 | 124 | return NO; 125 | } 126 | 127 | - (void)cancelOtherGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer { 128 | NSSet *touchs = [self.pan.event touchesForGestureRecognizer:otherGestureRecognizer]; 129 | [otherGestureRecognizer touchesCancelled:touchs withEvent:self.pan.event]; 130 | } 131 | 132 | 133 | #pragma mark - push 134 | - (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated { 135 | 136 | UIViewController *previousViewController = [self.viewControllers lastObject]; 137 | if (previousViewController) { 138 | viewController.hidesBottomBarWhenPushed = YES; 139 | } 140 | // 动画标识,在动画的情况下,禁掉右滑手势 141 | [self startAnimated:animated]; 142 | [super pushViewController:viewController animated:animated]; 143 | } 144 | 145 | - (void)startAnimated:(BOOL)animated { 146 | 147 | _animatedFlag = YES; 148 | NSTimeInterval delay = animated ? 0.8 : 0.1; 149 | [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(finishedAnimated) object:nil]; 150 | [self performSelector:@selector(finishedAnimated) withObject:nil afterDelay:delay]; 151 | } 152 | - (void)finishedAnimated { 153 | _animatedFlag = NO; 154 | } 155 | 156 | 157 | #pragma mark - pop 158 | - (UIViewController *)popViewControllerAnimated:(BOOL)animated { 159 | 160 | [self startAnimated:animated]; 161 | return [super popViewControllerAnimated:animated]; 162 | } 163 | 164 | - (NSArray *)popToViewController:(UIViewController *)viewController animated:(BOOL)animated { 165 | // TODO: shotStack handle 166 | return [super popToViewController:viewController animated:animated]; 167 | } 168 | 169 | - (NSArray *)popToRootViewControllerAnimated:(BOOL)animated { 170 | return [super popToRootViewControllerAnimated:animated]; 171 | } 172 | 173 | 174 | #pragma mark LCPanBackProtocol 175 | 176 | - (BOOL)enablePanBack { 177 | BOOL enable = YES; 178 | if ([self.visibleViewController respondsToSelector:@selector(enablePanBack:)]) { 179 | UIViewController *viewController = (UIViewController *)self.visibleViewController; 180 | enable = [viewController enablePanBack:self]; 181 | } 182 | if ([self.visibleViewController isKindOfClass:[UITabBarController class]]) { 183 | enable = NO; 184 | } 185 | return enable; 186 | } 187 | 188 | 189 | - (void)startPanBack { 190 | if ([self.visibleViewController respondsToSelector:@selector(startPanBack:)]) { 191 | UIViewController *viewController = (UIViewController *)self.visibleViewController; 192 | [viewController startPanBack:self]; 193 | } 194 | } 195 | 196 | - (void)finshPanBackWithReset:(BOOL)reset { 197 | if (reset) { 198 | [self resetPanBack]; 199 | } else { 200 | [self finshPanBack]; 201 | } 202 | } 203 | 204 | - (void)finshPanBack { 205 | if ([self.visibleViewController respondsToSelector:@selector(finshPanBack:)]) { 206 | UIViewController *viewController = (UIViewController *)self.visibleViewController; 207 | [viewController finshPanBack:self]; 208 | } 209 | } 210 | 211 | - (void)resetPanBack { 212 | if ([self.visibleViewController respondsToSelector:@selector(resetPanBack:)]) { 213 | UIViewController *viewController = (UIViewController *)self.visibleViewController; 214 | [viewController resetPanBack:self]; 215 | } 216 | } 217 | 218 | 219 | #pragma mark - ChildViewController 220 | 221 | - (UIViewController *)currentViewController { 222 | UIViewController *result = nil; 223 | if ([self.viewControllers count] > 0) { 224 | result = [self.viewControllers lastObject]; 225 | } 226 | return result; 227 | } 228 | 229 | #pragma mark - ParentViewController 230 | 231 | - (UIViewController *)previousViewController { 232 | UIViewController *result = nil; 233 | if ([self.viewControllers count] > 1) { 234 | result = [self.viewControllers objectAtIndex:self.viewControllers.count - 2]; 235 | } 236 | return result; 237 | } 238 | 239 | - (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated { 240 | // 删除系统自带的tabBarButton 241 | for (UIView *tabBar in self.tabBarController.tabBar.subviews) { 242 | if ([tabBar isKindOfClass:[UIControl class]]) { 243 | [tabBar removeFromSuperview]; 244 | } 245 | } 246 | } 247 | 248 | - (void)didReceiveMemoryWarning { 249 | [super didReceiveMemoryWarning]; 250 | // Dispose of any resources that can be recreated. 251 | } 252 | 253 | /* 254 | #pragma mark - Navigation 255 | 256 | // In a storyboard-based application, you will often want to do a little preparation before navigation 257 | - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { 258 | // Get the new view controller using [segue destinationViewController]. 259 | // Pass the selected object to the new view controller. 260 | } 261 | */ 262 | 263 | @end 264 | -------------------------------------------------------------------------------- /PAWebView/PAWKNative/WKBaseWebView.h: -------------------------------------------------------------------------------- 1 | // 2 | // WKBaseWebView.h 3 | // Pkit 4 | // 5 | // Created by llyouss on 2017/12/15. 6 | // Copyright © 2017年 llyouss. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface WKBaseWebView : UIViewController 12 | 13 | @property (nonatomic, strong) UIBarButtonItem *backItem; //返回按钮 14 | @property (nonatomic, strong) UIBarButtonItem *closeItem; //关闭按钮 15 | 16 | /* 实现单例网页从此初始位置退出 */ 17 | @property (nonatomic, assign) NSInteger previousIndex; 18 | 19 | @end 20 | 21 | 22 | -------------------------------------------------------------------------------- /PAWebView/PAWKNative/WKBaseWebView.m: -------------------------------------------------------------------------------- 1 | // 2 | // WKBaseWebView.m 3 | // Pkit 4 | // 5 | // Created by llyouss on 2017/12/15. 6 | // Copyright © 2017年 llyouss. All rights reserved. 7 | // 8 | 9 | #import "WKBaseWebView.h" 10 | #import "PAWKPanGestureRecognizer.h" 11 | #import 12 | 13 | #import 14 | 15 | @interface WKBaseWebView () 16 | 17 | @property (nonatomic, retain) PAWKPanGestureRecognizer *pan; 18 | @property (assign, nonatomic) BOOL animatedFlag; 19 | 20 | @end 21 | 22 | @implementation WKBaseWebView 23 | 24 | - (void)viewDidLoad { 25 | [super viewDidLoad]; 26 | 27 | NSDictionary * dict = [NSDictionary dictionaryWithObject:[UIColor whiteColor] forKey:NSForegroundColorAttributeName]; 28 | self.navigationController.navigationBar.titleTextAttributes = dict; 29 | self.navigationController.navigationBar.translucent = YES; 30 | 31 | if ([[[UIDevice currentDevice] systemVersion] doubleValue] >= 7.0) { 32 | self.edgesForExtendedLayout = UIRectEdgeNone; 33 | } 34 | self.automaticallyAdjustsScrollViewInsets = NO; 35 | self.navigationController.interactivePopGestureRecognizer.delegate = (id)self; 36 | } 37 | 38 | - (void)viewWillAppear:(BOOL)animated 39 | { 40 | [super viewWillAppear:animated]; 41 | [UIApplication sharedApplication].statusBarStyle = UIStatusBarStyleLightContent; 42 | } 43 | 44 | - (void)viewWillDisappear:(BOOL)animated 45 | { 46 | [super viewWillDisappear:animated]; 47 | } 48 | 49 | - (void)popToBackForwardListItem:(WKBackForwardListItem *)item WebView:(WKWebView *)webview{ 50 | 51 | [webview goToBackForwardListItem:item]; 52 | } 53 | 54 | #pragma mark -- navigation/NaviBarItem setting------- 55 | /** 状态栏样式 */ 56 | - (UIStatusBarStyle)preferredStatusBarStyle 57 | { 58 | //???:设置状态栏颜色(字体和背景) 59 | __weak typeof(self)weakSelf = self; 60 | static dispatch_once_t onceToken; 61 | dispatch_once(&onceToken, ^{ 62 | weakSelf.view.backgroundColor = [UIColor colorWithRed:4/255.0 green:176/255.0 blue:250/255.0 alpha:1]; 63 | }); 64 | return UIStatusBarStyleLightContent; 65 | } 66 | 67 | #pragma NaviBarItem setting------ 68 | #pragma mark - 添加关闭按钮 69 | 70 | #pragma mark - init 71 | - (UIBarButtonItem *)backItem 72 | { 73 | if (!_backItem) 74 | { _backItem = [[UIBarButtonItem alloc] init]; 75 | UIButton *btn = [UIButton buttonWithType:UIButtonTypeCustom]; 76 | //这是一张“<”的图片,可以让美工给切一张 77 | UIImage *image = [UIImage imageNamed:@"PAWebBack.png"]; 78 | [btn setImage:image forState:UIControlStateNormal]; 79 | [btn setTitle:@" 返回" forState:UIControlStateNormal]; 80 | [btn.titleLabel setFont:[UIFont boldSystemFontOfSize:17]]; 81 | [btn setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal]; 82 | //字体的多少为btn的大小 83 | [btn sizeToFit]; 84 | //左对齐 85 | btn.contentHorizontalAlignment = UIControlContentHorizontalAlignmentLeft; 86 | //让返回按钮内容继续向左边偏移15,如果不设置的话,就会发现返回按钮离屏幕的左边的距离有点儿大,不美观 87 | btn.contentEdgeInsets = UIEdgeInsetsMake(0, -5, 0, 0); 88 | btn.frame = CGRectMake(0, 0, 58, 40); 89 | _backItem.customView = btn; 90 | } 91 | return _backItem; 92 | } 93 | 94 | - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer 95 | { 96 | if (self.childViewControllers.count == 1) { //当只有一个自控制器时不可滑动 97 | 98 | return NO; 99 | } 100 | 101 | return YES; 102 | } 103 | 104 | - (void)didReceiveMemoryWarning { 105 | [super didReceiveMemoryWarning]; 106 | // Dispose of any resources that can be recreated. 107 | } 108 | 109 | /* 110 | #pragma mark - Navigation 111 | 112 | // In a storyboard-based application, you will often want to do a little preparation before navigation 113 | - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { 114 | // Get the new view controller using [segue destinationViewController]. 115 | // Pass the selected object to the new view controller. 116 | } 117 | */ 118 | 119 | @end 120 | -------------------------------------------------------------------------------- /PAWebView/PAWKNative/views/PAWebBack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/llyouss/PAWebView/d045f474a49200170e52d7894c920e8014046b2a/PAWebView/PAWKNative/views/PAWebBack.png -------------------------------------------------------------------------------- /PAWebView/PAWKNative/views/UIAlertController+WKWebAlert.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIAlertController+WKWebAlert.h 3 | // Pkit 4 | // 5 | // Created by llyouss on 2017/12/18. 6 | // Copyright © 2017年 llyouss. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | /** 13 | 按钮点击事件 block 14 | 15 | @param alertController alertController 16 | @param action UIAlertAction 17 | @param buttonIndex buttonIndex 18 | */ 19 | typedef void (^BAKit_AlertControllerButtonActionBlock) (UIAlertController * __nonnull alertController, UIAlertAction * __nonnull action, NSInteger buttonIndex); 20 | 21 | #if TARGET_OS_IOS 22 | typedef void (^UIAlertControllerPopoverPresentationControllerBlock) (UIPopoverPresentationController * __nonnull popover); 23 | #endif 24 | 25 | typedef void (^BAKit_AlertControllerTextFieldConfigurationActionBlock)(UITextField * _Nullable textField, NSInteger index); 26 | 27 | @interface UIAlertController (WKWebAlert) 28 | 29 | + (BOOL)isAlert; 30 | /** 31 | * 返回当前类的所有成员变量数组 32 | * 33 | * @return 当前类的所有成员变量! 34 | * 35 | * Tips:用于调试, 可以尝试查看所有不开源的类的ivar 36 | */ 37 | + (NSArray *)ba_ivarList; 38 | 39 | #pragma clang diagnostic push 40 | #pragma clang diagnostic ignored "-Wstrict-prototypes" 41 | 42 | + (void)PAlertWithTitle:(NSString *)title message:(NSString *)message completion:(void (^)())completion; 43 | + (void)PAlertWithTitle:(NSString *)title 44 | message:(NSString *)message 45 | action1Title:(NSString *)action1Title 46 | action2Title:(NSString *)action2Title 47 | action1:(void (^)())action1 48 | action2:(void (^)())action2; 49 | 50 | 51 | 52 | /** 53 | 快速创建一个系统 普通 UIAlertController-ActionSheet 54 | 55 | @param viewController 显示的VC 56 | @param title title 57 | @param message message 58 | @param buttonTitleArray 按钮数组 59 | @param buttonTitleColorArray 按钮颜色数组,默认:系统蓝色,如果颜色数组个数小于title数组个数,则全部为默认蓝色 60 | @param popoverPresentationControllerBlock popoverPresentationControllerBlock description 61 | @param block block 62 | @return UIAlertController-ActionSheet 63 | */ 64 | + (nonnull instancetype)ba_actionSheetShowInViewController:(nonnull UIViewController *)viewController 65 | title:(nullable NSString *)title 66 | message:(nullable NSString *)message 67 | buttonTitleArray:(nullable NSArray *)buttonTitleArray 68 | buttonTitleColorArray:(nullable NSArray *)buttonTitleColorArray 69 | #if TARGET_OS_IOS 70 | popoverPresentationControllerBlock:(nullable UIAlertControllerPopoverPresentationControllerBlock)popoverPresentationControllerBlock 71 | #endif 72 | block:(nullable BAKit_AlertControllerButtonActionBlock)block; 73 | 74 | 75 | #pragma clang diagnostic pop 76 | 77 | @end 78 | -------------------------------------------------------------------------------- /PAWebView/PAWKNative/views/UIAlertController+WKWebAlert.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIAlertController+WKWebAlert.m 3 | // Pkit 4 | // 5 | // Created by llyouss on 2017/12/18. 6 | // Copyright © 2017年 llyouss. All rights reserved. 7 | // 8 | 9 | #import "UIAlertController+WKWebAlert.h" 10 | #import "NSObject+PARuntime.h" 11 | 12 | 13 | @interface UIViewController (PAKit) 14 | 15 | - (UIViewController *)ba_currentViewController; 16 | 17 | @end 18 | 19 | @implementation UIAlertController (WKWebAlert) 20 | 21 | #pragma clang diagnostic push 22 | #pragma clang diagnostic ignored "-Wstrict-prototypes" 23 | 24 | + (BOOL)isAlert{ 25 | 26 | for (UIWindow* window in [UIApplication sharedApplication].windows) { 27 | NSArray* subviews = window.subviews; 28 | if ([subviews count] > 0) 29 | if ([[subviews objectAtIndex:0] isKindOfClass:[UIAlertView class]] 30 | || [[subviews objectAtIndex:0] isKindOfClass:[UIAlertController class]]) 31 | return YES; 32 | } 33 | return NO; 34 | } 35 | 36 | 37 | + (void)PAlertWithTitle:(NSString *)title message:(NSString *)message completion:(void (^)())completion 38 | { 39 | UIAlertController* showSecreetDefault = [UIAlertController alertControllerWithTitle:title message:message preferredStyle:UIAlertControllerStyleAlert]; 40 | UIAlertAction *ActionTrue = [UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nullable action) { 41 | completion ? completion() : NULL; 42 | }]; 43 | 44 | [showSecreetDefault addAction:ActionTrue]; 45 | 46 | dispatch_async(dispatch_get_main_queue(), ^{ 47 | [[UIApplication sharedApplication].keyWindow.rootViewController presentViewController:showSecreetDefault animated:YES completion:nil]; 48 | }); 49 | } 50 | 51 | 52 | + (void)PAlertWithTitle:(NSString *)title 53 | message:(NSString *)message 54 | action1Title:(NSString *)action1Title 55 | action2Title:(NSString *)action2Title 56 | action1:(void (^)())action1 57 | action2:(void (^)())action2 58 | { 59 | UIAlertController* showSecreetDefault = [UIAlertController alertControllerWithTitle:title message:message preferredStyle:UIAlertControllerStyleAlert]; 60 | UIAlertAction *ActionOne = [UIAlertAction actionWithTitle:action1Title style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { 61 | action1 ? action1() : NULL; 62 | }]; 63 | UIAlertAction *ActionTwo = [UIAlertAction actionWithTitle:action2Title style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { 64 | action2 ? action2() : NULL; 65 | }]; 66 | [showSecreetDefault addAction:ActionOne]; 67 | [showSecreetDefault addAction:ActionTwo]; 68 | 69 | dispatch_async(dispatch_get_main_queue(), ^{ 70 | [[UIApplication sharedApplication].keyWindow.rootViewController presentViewController:showSecreetDefault animated:YES completion:nil]; 71 | }); 72 | } 73 | 74 | 75 | 76 | /** 77 | 快速创建一个系统 普通 UIAlertController-ActionSheet 78 | 79 | @param viewController 显示的VC 80 | @param title title 81 | @param message message 82 | @param buttonTitleArray 按钮数组 83 | @param buttonTitleColorArray 按钮颜色数组,默认:系统蓝色,如果颜色数组个数小于title数组个数,则全部为默认蓝色 84 | @param popoverPresentationControllerBlock popoverPresentationControllerBlock description 85 | @param block block 86 | @return UIAlertController-ActionSheet 87 | */ 88 | + (nonnull instancetype)ba_actionSheetShowInViewController:(nonnull UIViewController *)viewController 89 | title:(nullable NSString *)title 90 | message:(nullable NSString *)message 91 | buttonTitleArray:(nullable NSArray *)buttonTitleArray 92 | buttonTitleColorArray:(nullable NSArray *)buttonTitleColorArray 93 | #if TARGET_OS_IOS 94 | popoverPresentationControllerBlock:(nullable UIAlertControllerPopoverPresentationControllerBlock)popoverPresentationControllerBlock 95 | #endif 96 | block:(nullable BAKit_AlertControllerButtonActionBlock)block 97 | { 98 | return [self ba_alertControllerShowInViewController:viewController 99 | title:title 100 | attributedTitle:nil 101 | message:message 102 | attributedMessage:nil 103 | preferredStyle:UIAlertControllerStyleActionSheet 104 | buttonTitleArray:buttonTitleArray 105 | buttonTitleColorArray:buttonTitleColorArray 106 | buttonEnabledNoWithTitleArray:nil 107 | textFieldPlaceholderArray:nil 108 | textFieldConfigurationActionBlock:nil 109 | #if TARGET_OS_IOS 110 | popoverPresentationControllerBlock:popoverPresentationControllerBlock 111 | #endif 112 | block:block]; 113 | } 114 | 115 | 116 | 117 | + (instancetype)ba_alertControllerShowInViewController:(UIViewController *)viewController 118 | title:(NSString *)title 119 | attributedTitle:(nullable NSMutableAttributedString *)attributedTitle 120 | message:(NSString *)message 121 | attributedMessage:(nullable NSMutableAttributedString *)attributedMessage 122 | preferredStyle:(UIAlertControllerStyle)preferredStyle 123 | buttonTitleArray:(nullable NSArray *)buttonTitleArray 124 | buttonTitleColorArray:(nullable NSArray *)buttonTitleColorArray 125 | buttonEnabledNoWithTitleArray:(NSArray *_Nullable)buttonEnabledNoWithTitleArray 126 | textFieldPlaceholderArray:(NSArray *_Nullable)textFieldPlaceholderArray 127 | textFieldConfigurationActionBlock:(nullable BAKit_AlertControllerTextFieldConfigurationActionBlock)textFieldConfigurationActionBlock 128 | #if TARGET_OS_IOS 129 | popoverPresentationControllerBlock:(void(^)(UIPopoverPresentationController *popover))popoverPresentationControllerBlock 130 | #endif 131 | block:(BAKit_AlertControllerButtonActionBlock)block 132 | { 133 | UIAlertController *strongController = [self alertControllerWithTitle:title 134 | message:message 135 | preferredStyle:preferredStyle]; 136 | 137 | __weak UIAlertController *alertController = strongController; 138 | 139 | if (buttonTitleArray) 140 | { 141 | for (NSUInteger i = 0; i < buttonTitleArray.count; i++) 142 | { 143 | NSString *buttonTitle = buttonTitleArray[i]; 144 | 145 | UIAlertAction *action = [UIAlertAction actionWithTitle:buttonTitle 146 | style:UIAlertActionStyleDefault 147 | handler:^(UIAlertAction *action){ 148 | if (block) 149 | { 150 | block(alertController, action, i); 151 | } 152 | }]; 153 | [alertController addAction:action]; 154 | 155 | for (NSInteger j = 0; j < buttonEnabledNoWithTitleArray.count; j ++) 156 | { 157 | if ([buttonEnabledNoWithTitleArray[j] isEqualToString:buttonTitle]) 158 | { 159 | action.enabled = NO; 160 | } 161 | } 162 | 163 | if (buttonTitleColorArray) 164 | { 165 | [strongController setAlertWithAlert:strongController 166 | mutableAttributedTitle:attributedTitle 167 | mutableAttributedMessage:attributedMessage 168 | Action:action 169 | buttonTitleColor:buttonTitleColorArray[i]]; 170 | } 171 | } 172 | } 173 | if (textFieldPlaceholderArray) 174 | { 175 | for (NSInteger i = 0; i < textFieldPlaceholderArray.count; i++) 176 | { 177 | [alertController addTextFieldWithConfigurationHandler:^(UITextField * _Nonnull textField) { 178 | 179 | textField.placeholder = textFieldPlaceholderArray[i]; 180 | if (textFieldConfigurationActionBlock) 181 | { 182 | textFieldConfigurationActionBlock(textField, i); 183 | } 184 | }]; 185 | } 186 | } 187 | 188 | if (preferredStyle == UIAlertControllerStyleActionSheet) 189 | { 190 | UIAlertAction *action = [UIAlertAction actionWithTitle:@"取 消" 191 | style:UIAlertActionStyleCancel 192 | handler:nil]; 193 | [alertController addAction:action]; 194 | } 195 | 196 | #if TARGET_OS_IOS 197 | if (popoverPresentationControllerBlock) 198 | { 199 | popoverPresentationControllerBlock(alertController.popoverPresentationController); 200 | } 201 | #endif 202 | 203 | [viewController.ba_currentViewController presentViewController:alertController animated:YES completion:nil]; 204 | 205 | return alertController; 206 | } 207 | 208 | 209 | - (void)setAlertWithAlert:(UIAlertController * __nonnull )alert 210 | mutableAttributedTitle:(NSMutableAttributedString *)mutableAttributedTitle 211 | mutableAttributedMessage:(nullable NSMutableAttributedString *)mutableAttributedMessage 212 | Action:(UIAlertAction * __nonnull )action 213 | buttonTitleColor:(UIColor *)buttonTitleColor 214 | { 215 | /*! 1、首先获得对应的属性 */ 216 | // NSArray *propertysListArray = [[UIAlertController class] ba_propertysList]; 217 | // NSLog(@"1、获取【UIAlertController】所有的属性名:%@", propertysListArray); 218 | 219 | /*! 2、获得成员变量 */ 220 | NSArray *ivarListArray = [[UIAlertAction class] ba_ivarList]; 221 | // NSLog(@"2、获取【UIAlertController】所有的成员变量:%@", ivarListArray); 222 | 223 | for (NSInteger i = 0; i < ivarListArray.count; i++) 224 | { 225 | NSString *ivarName = ivarListArray[i]; 226 | if ([ivarName isEqualToString:@"_titleTextColor"]) 227 | { 228 | [action setValue:buttonTitleColor forKey:@"titleTextColor"]; 229 | } 230 | } 231 | 232 | /*! 3、改变显示提示字体颜色 */ 233 | NSArray *propertysListArray2 = [[UIAlertController class] ba_ivarList]; 234 | // NSLog(@"3、获取【UIAlertController】所有的成员变量:%@", propertysListArray2); 235 | for (NSInteger i = 0; i < propertysListArray2.count; i++) 236 | { 237 | NSString *ivarName = propertysListArray2[i]; 238 | if ([ivarName isEqualToString:@"_attributedTitle"]) 239 | { 240 | [alert setValue:mutableAttributedTitle forKey:@"attributedTitle"]; 241 | } 242 | if ([ivarName isEqualToString:@"_attributedMessage"]) 243 | { 244 | [alert setValue:mutableAttributedMessage forKey:@"attributedMessage"]; 245 | } 246 | } 247 | } 248 | 249 | #pragma clang diagnostic pop 250 | 251 | @end 252 | 253 | @implementation UIViewController (PAKit) 254 | 255 | - (UIViewController *)ba_currentViewController 256 | { 257 | UIViewController *topVC = self; 258 | 259 | UIViewController *above; 260 | while ((above = topVC.presentedViewController)) { 261 | topVC = above; 262 | } 263 | 264 | return topVC; 265 | } 266 | 267 | @end 268 | 269 | 270 | -------------------------------------------------------------------------------- /PAWebView/PAWKNative/views/navigationbar_more@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/llyouss/PAWebView/d045f474a49200170e52d7894c920e8014046b2a/PAWebView/PAWKNative/views/navigationbar_more@2x.png -------------------------------------------------------------------------------- /PAWebView/PAWKNative/views/navigationbar_more@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/llyouss/PAWebView/d045f474a49200170e52d7894c920e8014046b2a/PAWebView/PAWKNative/views/navigationbar_more@3x.png -------------------------------------------------------------------------------- /PAWebView/PAWebView+UIDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // PAWebView+UIDelegate.h 3 | // Pkit 4 | // 5 | // Created by llyouss on 2017/12/25. 6 | // Copyright © 2017年 llyouss. All rights reserved. 7 | // 8 | 9 | #import "PAWebView.h" 10 | 11 | @interface PAWebView (UIDelegate) 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /PAWebView/PAWebView+UIDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // PAWebView+UIDelegate.m 3 | // Pkit 4 | // 5 | // Created by llyouss on 2017/12/25. 6 | // Copyright © 2017年 llyouss. All rights reserved. 7 | // 8 | 9 | #import "PAWebView+UIDelegate.h" 10 | 11 | @implementation PAWebView (UIDelegate) 12 | 13 | 14 | #pragma mark - 15 | #pragma mark WKUIDelegate 16 | 17 | - (void)webView:(WKWebView *)webView runOpenPanelWithParameters:(WKOpenPanelParameters *)parameters initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSArray * _Nullable URLs))completionHandler API_AVAILABLE(macosx(10.12)); 18 | { 19 | 20 | } 21 | - (void)webView:(WKWebView *)webView commitPreviewingViewController:(UIViewController *)previewingViewController API_AVAILABLE(ios(10.0)){ 22 | 23 | 24 | } 25 | 26 | - (nullable UIViewController *)webView:(WKWebView *)webView previewingViewControllerForElement:(WKPreviewElementInfo *)elementInfo defaultActions:(NSArray> *)previewActions{ 27 | 28 | return nil; 29 | } 30 | - (BOOL)webView:(WKWebView *)webView shouldPreviewElement:(WKPreviewElementInfo *)elementInfo{ 31 | 32 | return YES; 33 | } 34 | 35 | /** 创建新的webview 36 | * 可以指定配置对象、导航动作对象、window特性 37 | */ 38 | - (WKWebView *)webView:(WKWebView *)webView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures 39 | { 40 | if (!navigationAction.targetFrame.isMainFrame) { 41 | [webView loadRequest:navigationAction.request]; 42 | } 43 | return nil; 44 | } 45 | 46 | /** 调用JS的alert()方法 */ 47 | - (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(nonnull NSString *)message initiatedByFrame:(nonnull WKFrameInfo *)frame completionHandler:(nonnull void (^)(void))completionHandler 48 | { 49 | UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"提示" message:message ? message : @"" preferredStyle:UIAlertControllerStyleAlert]; 50 | [alertController addAction:([UIAlertAction actionWithTitle:@"确认" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { 51 | completionHandler(); 52 | }])]; 53 | 54 | [self PApresentViewController:alertController animated:YES]; 55 | } 56 | 57 | /** 调用JS的confirm()方法 */ 58 | - (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(nonnull NSString *)message initiatedByFrame:(nonnull WKFrameInfo *)frame completionHandler:(nonnull void (^)(BOOL))completionHandler 59 | { 60 | UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"提示" message:message ? message : @"" preferredStyle:UIAlertControllerStyleAlert]; 61 | [alertController addAction:([UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) { 62 | completionHandler(NO); 63 | }])]; 64 | [alertController addAction:([UIAlertAction actionWithTitle:@"确认" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { 65 | completionHandler(YES); 66 | }])]; 67 | 68 | [self PApresentViewController:alertController animated:YES]; 69 | } 70 | 71 | /** 调用JS的prompt()方法 */ 72 | - (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(nonnull NSString *)prompt defaultText:(nullable NSString *)defaultText initiatedByFrame:(nonnull WKFrameInfo *)frame completionHandler:(nonnull void (^)(NSString * _Nullable))completionHandler 73 | { 74 | UIAlertController *alertController = [UIAlertController alertControllerWithTitle:prompt message:@"" preferredStyle:UIAlertControllerStyleAlert]; 75 | [alertController addTextFieldWithConfigurationHandler:^(UITextField * _Nonnull textField) { 76 | textField.text = defaultText; 77 | }]; 78 | [alertController addAction:([UIAlertAction actionWithTitle:@"完成" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { 79 | completionHandler(alertController.textFields[0].text?:@""); 80 | }])]; 81 | 82 | [self PApresentViewController:alertController animated:YES]; 83 | } 84 | 85 | /** webview关闭时回调 */ 86 | - (void)webViewDidClose:(WKWebView *)webView 87 | { 88 | 89 | } 90 | - (void)PApresentViewController:(UIViewController *)viewControllerToPresent animated:(BOOL)flag{ 91 | 92 | __weak typeof(self)weekSelf = self; 93 | [[UIApplication sharedApplication].keyWindow.rootViewController dismissViewControllerAnimated:YES completion:nil]; 94 | dispatch_async(dispatch_get_main_queue(), ^{ 95 | [weekSelf presentViewController:viewControllerToPresent animated:YES completion:nil]; 96 | }); 97 | } 98 | 99 | @end 100 | -------------------------------------------------------------------------------- /PAWebView/PAWebView.h: -------------------------------------------------------------------------------- 1 | // 2 | // PAWebView.h 3 | // Pkit 4 | // 5 | // Created by llyouss on 2017/12/15. 6 | // Copyright © 2017年 llyouss. All rights reserved. 7 | // 8 | 9 | #import "WKBaseWebView.h" 10 | #import 11 | 12 | @class registerURLSchemes; 13 | 14 | @protocol PAWKScriptMessageHandler 15 | 16 | @optional 17 | 18 | /** JS 调用OC回调 */ 19 | - (void)PAUserContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message; 20 | 21 | @end 22 | 23 | typedef void (^QRCodeInfoBlock)(NSString *info); 24 | typedef void (^MessageBlock)(WKUserContentController *userContentController,WKScriptMessage *message); 25 | typedef void (^MenuBlock)(UIAlertController * alertController, UIAlertAction * action, NSInteger buttonIndex); 26 | 27 | @interface PAWebView : WKBaseWebView 28 | 29 | @property (nonatomic, retain) WKWebView *webView; 30 | @property (nonatomic, copy) NSString *currentURLString; //当前页面的URL 31 | @property (nonatomic, weak) id messageHandlerDelegate; 32 | @property (nonatomic, retain) UIColor *paprogressTintColor; //进度条颜色 33 | @property (nonatomic, retain) UIColor *paprogressTrackTintColor; 34 | @property (nonatomic, assign) BOOL openCache; //缓存 35 | @property (nonatomic, assign) BOOL showLog; //执行日志 36 | 37 | + (instancetype)shareInstance; 38 | 39 | /** 加载网页 加载网页时注入 cookies 把链接更改为 NSMutableURLRequest ,自定义缓存的方式和其他的一些具体的设置*/ 40 | - (void)loadRequestURL:(NSMutableURLRequest *)request; 41 | - (void)loadRequestURL:(NSMutableURLRequest *)request params:(NSDictionary*)params; 42 | - (void)loadLocalHTMLWithFileName:(NSString *)htmlName; 43 | 44 | /** 重新加载webview */ 45 | - (void)reload; 46 | /** 重新加载网页,忽略缓存 */ 47 | - (void)reloadFromOrigin; 48 | 49 | /** 返回上一级 */ 50 | - (void)goback; 51 | /** 下一级 */ 52 | - (void)goForward; 53 | 54 | /** 55 | 添加自定义的菜单栏 56 | 57 | @param buttonTitle 菜单按钮的标题 58 | @param block 反馈点击信息 59 | */ 60 | - (void)addMenuWithButtonTitle:(NSArray *)buttonTitle block:(MenuBlock)block; 61 | 62 | /** 63 | * 接收QRCode 的内容通知作相关处理 64 | * @param block QRCode的内容(包括手势长按或扫码) 65 | */ 66 | - (void)notificationInfoFromQRCode:(QRCodeInfoBlock)block; 67 | 68 | /** JS 调用OC 添加 messageHandler 69 | 添加 js 调用 OC,addScriptMessageHandler:name:有两个参数,第一个参数是 userContentController的代理对象,第二个参数是 JS 里发送 postMessage 的对象。添加一个脚本消息的处理器,同时需要在 JS 中添加,window.webkit.messageHandlers..postMessage()才能起作用。 70 | @param nameArr JS 里发送 postMessage 的对象数组,可同时添加多个对象 71 | */ 72 | - (void)addScriptMessageHandlerWithName:(NSArray *)nameArr; 73 | - (void)addScriptMessageHandlerWithName:(NSArray *)nameArr observeValue:(MessageBlock)callback; 74 | - (void)removeScriptMessageHandlerForName:(NSString *)name; 75 | 76 | /** OC调用JS方法 */ 77 | - (void)callJS:(NSString *)jsMethod; 78 | - (void)callJS:(NSString *)jsMethod handler:(void (^)(id response, NSError *error))handler; 79 | 80 | /*清除backForwardList 列表*/ 81 | - (void)clearBackForwardList; 82 | 83 | /** 84 | 读取本地磁盘的cookies,包括WKWebview的cookies和sharedHTTPCookieStorage存储的cookies 85 | 86 | @return 返回包含所有的cookies的数组; 87 | 当系统低于 iOS11 时,cookies 将同步NSHTTPCookieStorage的cookies,当系统大于iOS11时,cookies 将同步 88 | */ 89 | - (NSMutableArray *)WKSharedHTTPCookieStorage; 90 | 91 | /** 92 | 提供cookies插入,用于loadRequest 网页之前 93 | 94 | @param cookie NSHTTPCookie 类型 95 | cookie 需要设置 cookie 的name,value,domain,expiresDate(过期时间,当不设置过期时间,cookie将不会自动清除); 96 | cookie 设置expiresDate时使用 [cookieProperties setObject:expiresDate forKey:NSHTTPCookieExpires];将不起作用,原因不明;使用 cookieProperties[expiresDate] = expiresDate; 设置cookies 设置时间。 97 | */ 98 | - (void)setCookie:(NSHTTPCookie *)cookie; 99 | 100 | /** 删除单个cookie */ 101 | - (void)deleteWKCookie:(NSHTTPCookie *)cookie completionHandler:(nullable void (^)(void))completionHandler; 102 | /** 删除域名下的所有的cookie */ 103 | - (void)deleteWKCookiesByHost:(NSURL *)host completionHandler:(nullable void (^)(void))completionHandler; 104 | 105 | /** 清除所有的cookies */ 106 | - (void)clearWKCookies; 107 | 108 | /** 清除所有缓存(cookie除外) */ 109 | - (void)clearWebCacheFinish:(void(^)(BOOL finish,NSError *error))block; 110 | 111 | /** 112 | 存储URLSchemes主要用于识别urlschemes的来源名字和appstore的下载链接。系统默认输入一部分url,如需额外自定义添加或覆盖,请到registerURLSchemes查看样板 113 | @params URLSchemes URLSchemes 信息 114 | */ 115 | - (void)registerURLSchemes:(NSDictionary *)URLSchemes; 116 | 117 | @end 118 | 119 | -------------------------------------------------------------------------------- /PAWebView/PAWebView.m: -------------------------------------------------------------------------------- 1 | // 2 | // PAWebView.m 3 | // Pkit 4 | // 5 | // Created by llyouss on 2017/12/15. 6 | // Copyright © 2017年 llyouss. All rights reserved. 7 | // 8 | 9 | #import "PAWebView.h" 10 | #import 11 | #import "WKWebView+PAWebCookie.h" 12 | #import "WKWebView+PAWebCache.h" 13 | #import "WKWebView+LongPress.h" 14 | 15 | #import "NSURL+PATool.h" 16 | #import "PAWebView+UIDelegate.h" 17 | #import "registerURLSchemes.h" 18 | #import "PAWebViewMenu.h" 19 | #import "TYSnapshot.h" 20 | #import "NSURLProtocol+WKWebVIew.h" 21 | 22 | //原生组件高度 23 | #define WKSTATUS_BAR_HEIGHT 0 24 | #define WKSEGMENT_HEIGHT 49 25 | #define WKNAVIGATION_BAR_HEIGHT 44 26 | #define WKTAB_BAR_HEIGHT 49 27 | #define WKTOOL_BAR_HEIGHT 49 28 | 29 | #define APPSTATUS_BAR_HEIGHT 20 30 | #define WKSCREEN_WIDTH [UIScreen mainScreen].bounds.size.width 31 | #define WKSCREEN_HEIGHT [UIScreen mainScreen].bounds.size.height 32 | 33 | NSString *const NotiName_LoadRequest = @"notiLoadRequest"; //通知跳转的通知名 34 | NSString *const Key_LoadQRCodeUrl = @"Key_LoadQRCodeUrl"; //二维码识别(包括扫码和长按识别等) 35 | 36 | static BOOL isReload = NO; 37 | static BOOL isloadSuccess = NO; 38 | static MessageBlock messageCallback = nil; 39 | 40 | @interface PAWebView () 41 | 42 | @property (nonatomic, retain) PAWebViewMenu *menu; 43 | @property (nonatomic, copy) MenuBlock menuBlock; 44 | @property (nonatomic, copy) QRCodeInfoBlock qrcodeBlock; 45 | @property (nonatomic, retain) NSArray *buttonTitle; 46 | @property (nonatomic, retain) WKWebViewConfiguration *config; 47 | @property (nonatomic, retain) UIActivityIndicatorView * activityIndicator; 48 | @property (nonatomic, retain) UIProgressView *wkProgressView; //进度条 49 | @property (nonatomic, retain) NSArray *messageHandlerName; 50 | @property (nonatomic, assign) BOOL longpress; 51 | 52 | @end 53 | 54 | @implementation PAWebView 55 | 56 | + (instancetype)shareInstance 57 | { 58 | static PAWebView *baseWebview = nil; 59 | static dispatch_once_t onceToken; 60 | dispatch_once(&onceToken, ^{ 61 | baseWebview = [[self alloc]init]; 62 | baseWebview.longpress = NO; 63 | }); 64 | 65 | return baseWebview; 66 | } 67 | 68 | - (instancetype)init 69 | { 70 | if (self = [super init]) { 71 | self.menu = [PAWebViewMenu shareInstance]; 72 | self.menu.defaultType = YES; 73 | self.showLog = NO; 74 | [self loadRequestURL:[NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"about:blank"]]]; //初始化,提前加载。 75 | 76 | //重写 NSURLProtocol 77 | // [NSURLProtocol wk_registerScheme:@"http"]; 78 | // [NSURLProtocol wk_registerScheme:@"https"]; 79 | } 80 | return self; 81 | } 82 | 83 | - (void)viewDidLoad { 84 | [super viewDidLoad]; 85 | 86 | self.view.backgroundColor = [UIColor whiteColor]; 87 | __weak typeof(self)weekSelf = self; 88 | dispatch_async(dispatch_get_main_queue(), ^{ 89 | [weekSelf.view addSubview:weekSelf.webView]; 90 | [weekSelf configMenuItem]; 91 | }); 92 | } 93 | 94 | - (void)viewWillAppear:(BOOL)animated{ 95 | 96 | [super viewWillAppear:animated]; 97 | [self addBackButton]; 98 | } 99 | 100 | #pragma mark - 101 | #pragma mark webView实例 102 | 103 | -(WKWebView *)webView 104 | { 105 | if (_webView == nil) { 106 | if (self.navigationController.navigationBar.hidden) { 107 | _webView = [[WKWebView alloc] initWithFrame:CGRectMake( 0, 0, WKSCREEN_WIDTH, WKSCREEN_HEIGHT - WKSTATUS_BAR_HEIGHT - WKNAVIGATION_BAR_HEIGHT) configuration:self.config]; 108 | }else{ 109 | _webView = [[WKWebView alloc] initWithFrame:CGRectMake( 0, 0, WKSCREEN_WIDTH, WKSCREEN_HEIGHT - WKSTATUS_BAR_HEIGHT - WKNAVIGATION_BAR_HEIGHT - APPSTATUS_BAR_HEIGHT) configuration:self.config]; 110 | } 111 | 112 | _webView.backgroundColor = [UIColor whiteColor]; 113 | _webView.UIDelegate = self; 114 | _webView.scrollView.delegate = self; 115 | _webView.navigationDelegate = self; 116 | _webView.scrollView.bounces = YES; 117 | _webView.multipleTouchEnabled = YES; 118 | _webView.userInteractionEnabled = YES; 119 | _webView.allowsBackForwardNavigationGestures = YES; 120 | _webView.scrollView.decelerationRate = UIScrollViewDecelerationRateNormal; 121 | _webView.scrollView.showsVerticalScrollIndicator = YES; 122 | _webView.scrollView.showsHorizontalScrollIndicator = NO; 123 | 124 | if (@available(iOS 11.0, *)) { 125 | WKHTTPCookieStore *cookieStore = _webView.configuration.websiteDataStore.httpCookieStore; 126 | [_webView syncCookiesToWKHTTPCookieStore:cookieStore]; 127 | } 128 | 129 | [_webView addObserver:self forKeyPath:@"estimatedProgress" options:NSKeyValueObservingOptionNew context:NULL]; 130 | [_webView addObserver:self forKeyPath:@"title" options:NSKeyValueObservingOptionNew context:NULL]; 131 | //添加页面跳转通知 132 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(loadRequestFromNotification:) name:NotiName_LoadRequest object:nil]; 133 | //添加长按手势 134 | __weak typeof(self)weakSelf = self; 135 | [_webView addGestureRecognizerObserverWebElements:^(BOOL longpress) { 136 | weakSelf.longpress = longpress; 137 | }]; 138 | } 139 | 140 | return _webView; 141 | } 142 | 143 | - (void)clearBackForwardList{ 144 | 145 | #pragma clang diagnostic push 146 | #pragma clang diagnostic ignored "-Warc-performSelector-leaks" 147 | [self.webView.backForwardList performSelector:NSSelectorFromString(@"_removeAllItems")]; 148 | #pragma clang diagnostic pop 149 | 150 | } 151 | 152 | - (void)registerURLSchemes:(NSDictionary *)URLSchemes 153 | { 154 | [registerURLSchemes registerURLSchemes:URLSchemes]; 155 | } 156 | 157 | #pragma mark - 158 | #pragma mark - 网络请求 159 | 160 | /** 161 | * 重新加载网页 162 | */ 163 | - (void)reload{ 164 | isReload = YES; 165 | [self.webView reload]; 166 | } 167 | 168 | /** 169 | * 重新加载网页,忽略缓存 170 | */ 171 | - (void)reloadFromOrigin{ 172 | isReload = YES; 173 | [self.webView reloadFromOrigin]; 174 | } 175 | 176 | /** 177 | * 请求网络资源 post 178 | * @param request 请求的具体地址和设置 179 | * @param params 参数 180 | */ 181 | - (void)loadRequestURL:(NSMutableURLRequest *)request params:(NSDictionary*)params 182 | { 183 | NSURL *URLString = [NSURL generateURL:request.URL.absoluteString params:params]; 184 | request.URL = URLString; 185 | [self loadRequestURL:request]; 186 | } 187 | 188 | /** 189 | * 请求网络资源 190 | * @param request 请求的具体地址和设置 191 | */ 192 | - (void)loadRequestURL:(NSMutableURLRequest *)request 193 | { 194 | _webView = _webView ? _webView : self.webView; 195 | NSString *Domain = request.URL.host; 196 | 197 | if (@available(iOS 11.0, *)){ 198 | 199 | }else{ 200 | 201 | /** 插入cookies JS */ 202 | if (Domain)[self.config.userContentController addUserScript:[_webView searchCookieForUserScriptWithDomain:Domain]]; 203 | /** 插入cookies PHP */ 204 | if (Domain)[request setValue:[_webView phpCookieStringWithDomain:Domain] forHTTPHeaderField:@"Cookie"]; 205 | } 206 | 207 | [_webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"about:blank"]]];//重置空白界面 208 | [_webView loadRequest:request]; 209 | } 210 | 211 | /** 212 | * 加载本地HTML页面 213 | * @param htmlName html页面文件名称 214 | */ 215 | - (void)loadLocalHTMLWithFileName:(nonnull NSString *)htmlName { 216 | 217 | NSString *path = [[NSBundle mainBundle] bundlePath]; 218 | NSURL *baseURL = [NSURL fileURLWithPath:path]; 219 | NSString * htmlPath = [[NSBundle mainBundle] pathForResource:htmlName 220 | ofType:@"html"]; 221 | NSString * htmlCont = [NSString stringWithContentsOfFile:htmlPath 222 | encoding:NSUTF8StringEncoding 223 | error:nil]; 224 | [self.webView loadHTMLString:htmlCont baseURL:baseURL]; 225 | } 226 | 227 | /** 228 | * 接收通知进行网页跳转 229 | * @param noti 通知内容 230 | */ 231 | -(void)loadRequestFromNotification:(NSNotification *)noti 232 | { 233 | NSString * urlStr = [NSString string]; 234 | for (NSString * key in [noti userInfo]){ 235 | if ([key isEqualToString:Key_LoadQRCodeUrl]) { 236 | urlStr = [noti userInfo][key]; 237 | } 238 | } 239 | NSLog(@"urlStr = %@ ",urlStr); 240 | 241 | _qrcodeBlock ? _qrcodeBlock(urlStr) : NULL; 242 | 243 | NSURL * url = [NSURL URLWithString:urlStr]; 244 | if ([urlStr containsString:@"http"] || [[UIApplication sharedApplication]canOpenURL:url]) { 245 | [self.webView loadRequest:[NSURLRequest requestWithURL:url]]; 246 | } 247 | } 248 | 249 | - (void)notificationInfoFromQRCode:(QRCodeInfoBlock)block 250 | { 251 | _qrcodeBlock = block; 252 | } 253 | 254 | #pragma mark - 255 | #pragma mark 配置webView 256 | 257 | -(WKWebViewConfiguration *)config 258 | { 259 | if (_config == nil) { 260 | _config = [[WKWebViewConfiguration alloc] init]; 261 | _config.userContentController = [[WKUserContentController alloc] init]; 262 | _config.preferences = [[WKPreferences alloc] init]; 263 | _config.preferences.minimumFontSize = 8; 264 | _config.preferences.javaScriptEnabled = YES; //是否支持 JavaScript 265 | _config.preferences.javaScriptCanOpenWindowsAutomatically = YES; 266 | _config.processPool = [[WKProcessPool alloc] init]; 267 | 268 | _config.allowsInlineMediaPlayback = YES; // 允许在线播放 269 | if (@available(iOS 9.0, *)) { 270 | _config.allowsAirPlayForMediaPlayback = YES; //允许视频播放 271 | } 272 | 273 | NSMutableString *javascript = [NSMutableString string]; 274 | [javascript appendString:@"document.documentElement.style.webkitTouchCallout='none';"];//禁止长按 275 | // [javascript appendString:@"document.documentElement.style.webkitUserSelect='none';"];//禁止选择 276 | WKUserScript *noneSelectScript = [[WKUserScript alloc] initWithSource:javascript injectionTime:WKUserScriptInjectionTimeAtDocumentEnd forMainFrameOnly:YES]; 277 | [_config.userContentController addUserScript:noneSelectScript]; 278 | } 279 | return _config; 280 | } 281 | 282 | #pragma mark - 283 | #pragma mark - JS交互 messageHandler 284 | 285 | /** 286 | * OC 调用 JS 287 | * @param jsMethod JS方法 288 | */ 289 | - (void)callJS:(NSString *)jsMethod { 290 | 291 | [self callJS:jsMethod handler:nil]; 292 | } 293 | 294 | - (void)callJS:(NSString *)jsMethod handler:(void (^)(id response, NSError *error))handler { 295 | 296 | NSLog(@"call js:%@",jsMethod); 297 | [self evaluateJavaScript:jsMethod completionHandler:^(id _Nullable response, NSError * _Nullable error) { 298 | handler ? handler(response,error) : NULL; 299 | }]; 300 | } 301 | 302 | /** 303 | * 注入 meaasgeHandler 304 | * @param nameArr 脚本 305 | */ 306 | - (void)addScriptMessageHandlerWithName:(NSArray *)nameArr 307 | { 308 | /* removeScriptMessageHandlerForName 同时使用,否则内存泄漏 */ 309 | for (NSString * objStr in nameArr) { 310 | @try{ 311 | [self.config.userContentController addScriptMessageHandler:self name:objStr]; 312 | }@catch (NSException *e){ 313 | NSLog(@"异常信息:%@",e); 314 | }@finally{ 315 | 316 | } 317 | } 318 | self.messageHandlerName = nameArr; 319 | } 320 | 321 | - (void)addScriptMessageHandlerWithName:(NSArray *)nameArr observeValue:(MessageBlock)callback 322 | { 323 | messageCallback = callback; 324 | [self addScriptMessageHandlerWithName:nameArr]; 325 | } 326 | 327 | /** 328 | * 注销 注册过的js回调oc通知方式,适用于 iOS8 之后 329 | */ 330 | - (void)removeScriptMessageHandlerForName:(NSString *)name 331 | { 332 | [_config.userContentController removeScriptMessageHandlerForName:name]; 333 | } 334 | 335 | /** 调用JS */ 336 | - (void)evaluateJavaScript:(NSString *)javaScriptString completionHandler:(void (^ _Nullable)(_Nullable id, NSError * _Nullable error))completionHandler 337 | { 338 | NSString *promptCode =javaScriptString; 339 | [_webView evaluateJavaScript:promptCode completionHandler:completionHandler]; 340 | } 341 | 342 | /** messageHandler 代理 - js调用oc */ 343 | - (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message 344 | { 345 | NSLog(@">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"); 346 | NSLog(@" message.body = %@ ",message.body); 347 | NSLog(@" message.name = %@ ",message.name); 348 | NSLog(@">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"); 349 | if (_messageHandlerDelegate && [_messageHandlerDelegate respondsToSelector:@selector(PAUserContentController:didReceiveScriptMessage:)]) { 350 | [_messageHandlerDelegate PAUserContentController:userContentController didReceiveScriptMessage:message]; 351 | } 352 | messageCallback ? messageCallback(userContentController,message) : NULL; 353 | } 354 | 355 | #pragma mark - 356 | #pragma mark - WKWebview 缓存 cookie/cache 357 | 358 | - (void)setCookie:(NSHTTPCookie *)cookie 359 | { 360 | [self.webView insertCookie:cookie]; 361 | } 362 | 363 | /** 获取本地磁盘的cookies */ 364 | - (NSMutableArray *)WKSharedHTTPCookieStorage 365 | { 366 | return [self.webView sharedHTTPCookieStorage]; 367 | } 368 | 369 | /** 删除某一个cookies */ 370 | - (void)deleteWKCookie:(NSHTTPCookie *)cookie completionHandler:(nullable void (^)(void))completionHandler 371 | { 372 | [self.webView deleteWKCookie:cookie completionHandler:completionHandler]; 373 | } 374 | 375 | /** 删除所有的cookies */ 376 | - (void)clearWKCookies{ 377 | 378 | [self.webView clearWKCookies]; 379 | } 380 | 381 | /** 删除所有缓存不包括cookies */ 382 | - (void)clearWebCacheFinish:(void(^)(BOOL finish,NSError *error))block{ 383 | 384 | [self.webView clearWebCacheFinish:block]; 385 | } 386 | 387 | - (void)deleteWKCookiesByHost:(NSURL *)host completionHandler:(nullable void (^)(void))completionHandler{ 388 | 389 | [self.webView deleteWKCookiesByHost:host completionHandler:completionHandler]; 390 | } 391 | 392 | #pragma mark - 393 | #pragma mark -- navigationDelegate 394 | 395 | - (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(null_unspecified WKNavigation *)navigation 396 | { 397 | isloadSuccess = NO; 398 | } 399 | 400 | /** 跳转处理 */ 401 | - (void)webView:(WKWebView *)webView decidePolicyForNavigationAction: 402 | (WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler 403 | { 404 | if (_longpress) { 405 | _longpress = NO; 406 | decisionHandler(WKNavigationActionPolicyCancel); 407 | return; 408 | } 409 | 410 | NSString *scheme = navigationAction.request.URL.scheme.lowercaseString; 411 | if (![scheme containsString:@"http"] && ![scheme containsString:@"about"] && ![scheme containsString:@"file"]) { 412 | // 对于跨域,需要手动跳转, 用系统浏览器(Safari)打开 413 | if ([navigationAction.request.URL.host.lowercaseString isEqualToString:@"itunes.apple.com"]) 414 | { 415 | [UIAlertController PAlertWithTitle:@"提示" message:@"是否打开appstore?" action1Title:@"返回" action2Title:@"去下载" action1:^{ 416 | [webView goBack]; 417 | } action2:^{ 418 | [NSURL safariOpenURL:navigationAction.request.URL]; 419 | }]; 420 | decisionHandler(WKNavigationActionPolicyCancel); 421 | return; 422 | } 423 | 424 | [NSURL openURL:navigationAction.request.URL]; 425 | // 不允许web内跳转 426 | decisionHandler(WKNavigationActionPolicyCancel); 427 | 428 | } else { 429 | 430 | if ([navigationAction.request.URL.host.lowercaseString isEqualToString:@"itunes.apple.com"]) 431 | { 432 | [NSURL openURL:navigationAction.request.URL]; 433 | decisionHandler(WKNavigationActionPolicyCancel); 434 | return; 435 | } 436 | 437 | decisionHandler(WKNavigationActionPolicyAllow); 438 | } 439 | } 440 | 441 | - (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler 442 | { 443 | 444 | __weak typeof(self)weakSelf = self; 445 | NSHTTPURLResponse *response = (NSHTTPURLResponse *)navigationResponse.response; 446 | if ([response.URL.scheme.lowercaseString containsString:@"http"]) { 447 | NSArray *cookies =[NSHTTPCookie cookiesWithResponseHeaderFields:[response allHeaderFields] forURL:response.URL]; 448 | if (@available(iOS 11.0, *)) { 449 | //浏览器自动存储cookie 450 | }else 451 | { 452 | //存储cookies 453 | dispatch_sync(dispatch_get_global_queue(0, 0), ^{ 454 | 455 | @try{ 456 | //存储cookies 457 | for (NSHTTPCookie *cookie in cookies) { 458 | [weakSelf.webView insertCookie:cookie]; 459 | } 460 | }@catch (NSException *e) { 461 | NSLog(@"failed: %@", e); 462 | } @finally { 463 | 464 | } 465 | }); 466 | } 467 | 468 | } 469 | decisionHandler(WKNavigationResponsePolicyAllow); 470 | } 471 | 472 | - (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation 473 | { 474 | 475 | NSLog(@"%@",webView.URL.absoluteString); 476 | 477 | if ([webView.URL.absoluteString.lowercaseString isEqualToString:@"about:blank"]) { 478 | 479 | #pragma clang diagnostic push 480 | #pragma clang diagnostic ignored "-Warc-performSelector-leaks" 481 | [webView.backForwardList performSelector:NSSelectorFromString(@"_removeAllItems")]; 482 | #pragma clang diagnostic pop 483 | 484 | } 485 | 486 | isloadSuccess = YES; 487 | //获取当前 URLString 488 | __weak typeof(self)weekSelf = self; 489 | [webView evaluateJavaScript:@"window.location.href" completionHandler:^(id _Nullable urlStr, NSError * _Nullable error) { 490 | if (error == nil) { 491 | weekSelf.currentURLString = urlStr; 492 | } 493 | }]; 494 | 495 | NSString *heightString4 = @"document.body.scrollHeight"; 496 | // webView 高度自适应 497 | [webView evaluateJavaScript:heightString4 completionHandler:^(id _Nullable result, NSError * _Nullable error) { 498 | // 获取页面高度,并重置 webview 的 frame 499 | NSLog(@"html 的高度:%@", result); 500 | }]; 501 | 502 | } 503 | 504 | /** 接收到重定向时会回调 */ 505 | - (void)webView:(WKWebView *)webView didReceiveServerRedirectForProvisionalNavigation:(null_unspecified WKNavigation *)navigation { 506 | 507 | } 508 | 509 | /** 导航失败时会回调 */ 510 | - (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(null_unspecified WKNavigation *)navigation withError:(NSError *)error { 511 | [self.activityIndicator stopAnimating]; 512 | 513 | } 514 | 515 | /** 页面内容到达main frame时回调 */ 516 | - (void)webView:(WKWebView *)webView didCommitNavigation:(null_unspecified WKNavigation *)navigation { 517 | 518 | } 519 | 520 | /** 失败回调 */ 521 | - (void)webView:(WKWebView *)webView didFailNavigation:(null_unspecified WKNavigation *)navigation withError:(NSError *)error 522 | { 523 | [self.activityIndicator stopAnimating]; 524 | } 525 | 526 | 527 | - (void)webView:(WKWebView *)webView didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * _Nullable credential))completionHandler{ 528 | 529 | NSLog(@"%@",challenge.protectionSpace.authenticationMethod.lowercaseString); 530 | 531 | NSString *hostName = webView.URL.host; 532 | NSString *authenticationMethod = [[challenge protectionSpace] authenticationMethod]; 533 | if ([authenticationMethod isEqualToString:NSURLAuthenticationMethodDefault] 534 | || [authenticationMethod isEqualToString:NSURLAuthenticationMethodHTTPBasic] 535 | || [authenticationMethod isEqualToString:NSURLAuthenticationMethodHTTPDigest]) { 536 | 537 | NSString *title = @"Authentication Challenge"; 538 | NSString *message = [NSString stringWithFormat:@"%@ requires user name and password", hostName]; 539 | UIAlertController *alertController = [UIAlertController alertControllerWithTitle:title message:message preferredStyle:UIAlertControllerStyleAlert]; 540 | [alertController addTextFieldWithConfigurationHandler:^(UITextField *textField) { 541 | textField.placeholder = @"User"; 542 | //textField.secureTextEntry = YES; 543 | }]; 544 | [alertController addTextFieldWithConfigurationHandler:^(UITextField *textField) { 545 | textField.placeholder = @"Password"; 546 | textField.secureTextEntry = YES; 547 | }]; 548 | [alertController addAction:[UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { 549 | 550 | NSString *userName = ((UITextField *)alertController.textFields[0]).text; 551 | NSString *password = ((UITextField *)alertController.textFields[1]).text; 552 | 553 | NSURLCredential *credential = [[NSURLCredential alloc] initWithUser:userName password:password persistence:NSURLCredentialPersistenceNone]; 554 | 555 | completionHandler(NSURLSessionAuthChallengeUseCredential, credential); 556 | 557 | }]]; 558 | [alertController addAction:[UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) { 559 | completionHandler(NSURLSessionAuthChallengeUseCredential, nil); 560 | }]]; 561 | dispatch_async(dispatch_get_main_queue(), ^{ 562 | [self presentViewController:alertController animated:YES completion:^{}]; 563 | }); 564 | 565 | } 566 | else if ([authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) { 567 | // needs this handling on iOS 9 568 | NSURLCredential *card = [[NSURLCredential alloc]initWithTrust:challenge.protectionSpace.serverTrust]; 569 | challenge.sender ? completionHandler(NSURLSessionAuthChallengeUseCredential,card) : NULL; 570 | } 571 | else { 572 | completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil); 573 | } 574 | } 575 | 576 | 577 | #pragma mark -- navigationBar customUI 578 | #pragma mark 导航栏的菜单按钮 579 | /** 添加返回按钮 */ 580 | - (void)addBackButton 581 | { 582 | self.navigationItem.leftBarButtonItem = self.backItem; 583 | [(UIButton *)self.backItem.customView addTarget:self action:@selector(backNative) forControlEvents:UIControlEventTouchUpInside]; 584 | } 585 | 586 | - (void)goback{ 587 | [self backNative]; 588 | } 589 | 590 | /** 点击返回按钮的返回方法 */ 591 | - (void)backNative { 592 | //判断是否有上一层H5页面 593 | if ([self.webView canGoBack]) 594 | { 595 | //如果有则返回 596 | [self.webView goBack]; 597 | 598 | //同时设置返回按钮和关闭按钮为导航栏左边的按钮 599 | self.navigationItem.leftBarButtonItems = @[self.backItem]; 600 | } 601 | else 602 | { 603 | [self.navigationController popViewControllerAnimated:YES]; 604 | } 605 | } 606 | 607 | - (void)goForward{ 608 | 609 | [self.webView canGoForward] ? [_webView goForward] : NULL; 610 | } 611 | 612 | /** 功能菜单按钮 */ 613 | - (void)configMenuItem 614 | { 615 | UIImage *menuImage = [UIImage imageNamed:@"navigationbar_more"]; 616 | menuImage = [menuImage imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; 617 | UIButton *menuBtn = [[UIButton alloc] init]; 618 | [menuBtn setImage:menuImage forState:UIControlStateNormal]; 619 | [menuBtn addTarget:self action:@selector(menuBtnAction:) forControlEvents:UIControlEventTouchUpInside]; 620 | [menuBtn sizeToFit]; 621 | 622 | UIBarButtonItem *menuItem = [[UIBarButtonItem alloc] initWithCustomView:menuBtn]; 623 | self.navigationItem.rightBarButtonItem = menuItem; 624 | } 625 | 626 | #pragma mark 菜单按钮点击 627 | 628 | - (void)addMenuWithButtonTitle:(NSArray *)buttonTitle block:(MenuBlock)block{ 629 | 630 | _menu.defaultType = NO; 631 | _buttonTitle = buttonTitle; 632 | _menuBlock = block; 633 | } 634 | 635 | - (void)menuBtnAction:(UIButton *)sender 636 | { 637 | __weak typeof(self)weekSelf = self; 638 | if (self.menu.defaultType) { 639 | NSMutableArray *buttonTitleArray = [NSMutableArray array]; 640 | [buttonTitleArray addObjectsFromArray:@[@"safari打开", @"复制链接", @"分享", @"截图", @"刷新"]]; 641 | if (self.showLog) [buttonTitleArray addObject:@"执行日志"]; 642 | [self.menu defaultMenuShowInViewController:self title:@"更多" message:nil buttonTitleArray:buttonTitleArray buttonTitleColorArray:nil popoverPresentationControllerBlock:^(UIPopoverPresentationController * _Nonnull popover) { 643 | 644 | } block:^(UIAlertController * _Nonnull alertController, UIAlertAction * _Nonnull action, NSInteger buttonIndex) 645 | { 646 | if (buttonIndex == 0) 647 | { 648 | if (weekSelf.currentURLString.length > 0) 649 | { 650 | /*! safari打开 */ 651 | [NSURL safariOpenURL:[NSURL URLWithString:weekSelf.currentURLString]]; 652 | return; 653 | } 654 | else 655 | { 656 | [UIAlertController PAlertWithTitle:@"提示" message:@"无法获取当前链接" completion:nil]; 657 | } 658 | } 659 | else if (buttonIndex == 1) 660 | { 661 | /*! 复制链接 */ 662 | if (weekSelf.currentURLString.length > 0) 663 | { 664 | [UIPasteboard generalPasteboard].string = weekSelf.currentURLString; 665 | return; 666 | } 667 | else 668 | { 669 | [UIAlertController PAlertWithTitle:@"提示" message:@"无法获取当前链接" completion:nil]; 670 | } 671 | } 672 | else if (buttonIndex == 2) 673 | { 674 | 675 | } 676 | else if (buttonIndex == 3) 677 | { 678 | [weekSelf snapshotBtn]; 679 | } 680 | else if (buttonIndex == 4) 681 | { 682 | /*! 刷新 */ 683 | [weekSelf.webView reloadFromOrigin]; 684 | } 685 | }]; 686 | }else 687 | { 688 | [weekSelf.menu customMenuShowInViewController:weekSelf 689 | title:@"更多" 690 | message:nil 691 | buttonTitleArray:weekSelf.buttonTitle 692 | buttonTitleColorArray:nil popoverPresentationControllerBlock:^(UIPopoverPresentationController * _Nonnull popover) { 693 | 694 | } block:^(UIAlertController * _Nonnull alertController, UIAlertAction * _Nonnull action, NSInteger buttonIndex) 695 | { 696 | weekSelf.menuBlock ? weekSelf.menuBlock(alertController, action, buttonIndex) : NULL; 697 | }]; 698 | } 699 | } 700 | 701 | //saveSnap 702 | - (void)snapshotBtn{ 703 | 704 | __weak typeof(self) weakSelf = self; 705 | [TYSnapshot screenSnapshot:self.webView finishBlock:^(UIImage *snapShotImage) { 706 | UIViewController *preVc = [[PreviewVc alloc] init:snapShotImage]; 707 | UINavigationController *nc = [[UINavigationController alloc] initWithRootViewController:preVc]; 708 | nc.modalTransitionStyle = UIModalTransitionStyleCrossDissolve; 709 | [weakSelf presentViewController:nc animated:YES completion:nil]; 710 | }]; 711 | } 712 | 713 | #pragma mark 关闭按钮点击 714 | - (void)colseBtnAction:(UIButton *)sender 715 | { 716 | [self.navigationController popViewControllerAnimated:YES]; 717 | } 718 | 719 | #pragma mark - 720 | #pragma mark --- 进度条 721 | /** 进度条 */ 722 | - (UIProgressView *)wkProgressView { 723 | if (!_wkProgressView) { 724 | CGFloat progressBarHeight = 2.f; 725 | CGRect barFrame = CGRectMake(0, 0, WKSCREEN_WIDTH, progressBarHeight); 726 | _wkProgressView = [[UIProgressView alloc] initWithFrame:barFrame]; 727 | _wkProgressView.tintColor = [UIColor colorWithRed:50.0/255 green:135.0/255 blue:255.0/255 alpha:1.0]; 728 | _wkProgressView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin; 729 | _wkProgressView.hidden = NO; 730 | [_wkProgressView setAlpha:0.0f]; 731 | [self.webView addSubview:_wkProgressView]; 732 | } 733 | return _wkProgressView; 734 | } 735 | 736 | - (void)setPAProgressTintColor:(UIColor *)paprogressTintColor 737 | { 738 | _paprogressTintColor = paprogressTintColor; 739 | self.wkProgressView.progressTintColor = paprogressTintColor; 740 | } 741 | 742 | - (void)setPAProgressTrackTintColor:(UIColor *)paprogressTrackTintColor 743 | { 744 | _paprogressTrackTintColor = paprogressTrackTintColor; 745 | self.wkProgressView.trackTintColor = paprogressTrackTintColor; 746 | } 747 | 748 | /** 监控html的title 和 进度 */ 749 | -(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context 750 | { 751 | if ([keyPath isEqualToString:@"estimatedProgress"]) 752 | { 753 | if (object == _webView) 754 | { 755 | [self.wkProgressView setAlpha:1.0f]; 756 | float progressValue = fabsf([change[@"new"] floatValue]); 757 | if (progressValue > _wkProgressView.progress) { 758 | 759 | [_wkProgressView setProgress:progressValue animated:YES]; 760 | }else{ 761 | 762 | [_wkProgressView setProgress:progressValue animated:NO]; 763 | } 764 | 765 | if(progressValue >= 1.0f) 766 | { 767 | __weak typeof(self)weekSelf = self; 768 | [UIView animateWithDuration:0.3 delay:0.3 options:UIViewAnimationOptionCurveEaseOut animations:^{ 769 | [weekSelf.wkProgressView setAlpha:0.0f]; 770 | } completion:^(BOOL finished) { 771 | [weekSelf.wkProgressView setProgress:0.0f animated:NO]; 772 | }]; 773 | } 774 | } 775 | else 776 | { 777 | [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; 778 | } 779 | } 780 | else if ([keyPath isEqualToString:@"title"]) 781 | { 782 | if (object == _webView) 783 | { 784 | self.title = _webView.title; 785 | } 786 | else 787 | { 788 | [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; 789 | } 790 | }else 791 | { 792 | NSLog(@"%@",keyPath); 793 | } 794 | } 795 | 796 | #pragma mark - 797 | #pragma mark --- 加载动画 798 | /** 加载动画 */ 799 | - (UIActivityIndicatorView *)activityIndicator 800 | { 801 | if (_activityIndicator == nil) { 802 | _activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray]; 803 | [_activityIndicator setCenter:self.webView.center]; 804 | } 805 | return _activityIndicator; 806 | } 807 | 808 | - (void)dealloc 809 | { 810 | [_webView clearHTMLCache]; 811 | if(self.webView.scrollView.delegate) self.webView.scrollView.delegate = nil; 812 | if(self.webView.navigationDelegate) self.webView.navigationDelegate = nil; 813 | if(self.webView.UIDelegate) self.webView.UIDelegate = nil; 814 | [self.webView removeObserver:self forKeyPath:@"estimatedProgress"]; 815 | [self.webView removeObserver:self forKeyPath:@"title"]; 816 | if(self.wkProgressView)[_wkProgressView removeFromSuperview]; 817 | self.wkProgressView = nil; 818 | [[NSNotificationCenter defaultCenter]removeObserver:self]; 819 | [self PARemoveScriptMessageHandlerForName]; 820 | } 821 | 822 | - (void)PARemoveScriptMessageHandlerForName{ 823 | if ([_messageHandlerName count] > 0) { 824 | for (NSString *name in _messageHandlerName) { 825 | if (name) { 826 | [_config.userContentController removeScriptMessageHandlerForName:name]; 827 | } 828 | } 829 | } 830 | } 831 | 832 | - (void)didReceiveMemoryWarning { 833 | [super didReceiveMemoryWarning]; 834 | // Dispose of any resources that can be recreated. 835 | } 836 | 837 | @end 838 | 839 | 840 | -------------------------------------------------------------------------------- /PAWebView/PAWebViewCache/NSObject+PARuntime.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSObject+PARuntime.h 3 | // Pkit 4 | // 5 | // Created by llyouss on 2017/12/25. 6 | // Copyright © 2017年 llyouss. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface NSObject (PARuntime) 12 | 13 | 14 | /** 15 | * 将 ‘字典数组‘ 转换成当前模型的对象数组 16 | * 17 | * @param array 字典数组 18 | * 19 | * @return 返回模型对象的数组 20 | */ 21 | + (NSArray *)ba_objectsWithArray:(NSArray *)array; 22 | 23 | /** 24 | * 返回当前类的所有属性列表 25 | * 26 | * @return 属性名称 27 | */ 28 | + (NSArray *)ba_propertysList; 29 | 30 | /** 31 | * 返回当前类的所有成员变量数组 32 | * 33 | * @return 当前类的所有成员变量! 34 | * 35 | * Tips:用于调试, 可以尝试查看所有不开源的类的ivar 36 | */ 37 | + (NSArray *)ba_ivarList; 38 | 39 | /** 40 | * 返回当前类的所有方法 41 | * 42 | * @return 当前类的所有成员变量! 43 | */ 44 | + (NSArray *)ba_methodList; 45 | 46 | /** 47 | * 返回当前类的所有协议 48 | * 49 | * @return 当前类的所有协议! 50 | */ 51 | + (NSArray *)ba_protocolList; 52 | 53 | 54 | @end 55 | -------------------------------------------------------------------------------- /PAWebView/PAWebViewCache/NSObject+PARuntime.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSObject+PARuntime.m 3 | // Pkit 4 | // 5 | // Created by llyouss on 2017/12/25. 6 | // Copyright © 2017年 llyouss. All rights reserved. 7 | // 8 | 9 | #import "NSObject+PARuntime.h" 10 | #import 11 | 12 | @implementation NSObject (PARuntime) 13 | 14 | 15 | #pragma mark - 根据传递进来的所有字典数组 字典转模型 16 | + (NSArray *)ba_objectsWithArray:(NSArray *)array 17 | { 18 | if (array.count == 0) 19 | { 20 | return nil; 21 | } 22 | 23 | // 判断是否是字典数组 24 | NSAssert([array[0] isKindOfClass:[NSDictionary class]], @"必须传入字典数组"); 25 | 26 | // 获取属性列表数组 27 | NSArray *propertyList = [self ba_propertysList]; 28 | 29 | NSMutableArray *arrayM = [NSMutableArray array]; 30 | 31 | for (NSDictionary *dict in array) 32 | { 33 | // 创建模型 34 | id model = [self new]; 35 | 36 | // 遍历数组 37 | for (NSString *key in dict) 38 | { 39 | // 判断属性列表数组中是否包含当前key 如果有, 意味着属性存在 40 | if ([propertyList containsObject:key]) { 41 | // 字典转模型 42 | [model setValue:dict[key] forKey:key]; 43 | } 44 | } 45 | // 添加到可变数组中 46 | [arrayM addObject:model]; 47 | } 48 | return arrayM.copy; 49 | } 50 | 51 | #pragma mark - 获取本类所有 ‘属性‘ 的数组 52 | /** 程序运行的时候动态的获取当前类的属性列表 53 | * 程序运行的时候,类的属性不会变化 54 | */ 55 | const void *ba_propertyListKey = @"ba_propertyListKey"; 56 | + (NSArray *)ba_propertysList 57 | { 58 | NSArray *result = objc_getAssociatedObject(self, ba_propertyListKey); 59 | 60 | if (result != nil) 61 | { 62 | return result; 63 | } 64 | 65 | NSMutableArray *arrayM = [NSMutableArray array]; 66 | // 获取当前类的属性数组 67 | // count -> 属性的数量 68 | unsigned int count = 0; 69 | objc_property_t *list = class_copyPropertyList([self class], &count); 70 | 71 | for (unsigned int i = 0; i < count; i++) { 72 | // 根据下标获取属性 73 | objc_property_t property = list[i]; 74 | 75 | // 获取属性的名字 76 | const char *cName = property_getName(property); 77 | 78 | // 转换成OC字符串 79 | NSString *name = [NSString stringWithUTF8String:cName]; 80 | [arrayM addObject:name]; 81 | } 82 | 83 | /*! ⚠️注意: 一定要释放数组 class_copyPropertyList底层为C语言,所以我们一定要记得释放properties */ 84 | free(list); 85 | 86 | // ---保存属性数组对象--- 87 | objc_setAssociatedObject(self, ba_propertyListKey, arrayM, OBJC_ASSOCIATION_COPY_NONATOMIC); 88 | 89 | return objc_getAssociatedObject(self, ba_propertyListKey); 90 | } 91 | 92 | #pragma mark - 获取本类所有 ‘方法‘ 的数组 93 | const void *ba_methodListKey = "ba_methodListKey"; 94 | + (NSArray *)ba_methodList 95 | { 96 | // 1. 使用运行时动态添加属性 97 | NSArray *methodsList = objc_getAssociatedObject(self, ba_methodListKey); 98 | 99 | // 2. 如果数组中直接返回方法数组 100 | if (methodsList != nil) 101 | { 102 | return methodsList; 103 | } 104 | 105 | // 3. 获取当前类的方法数组 106 | unsigned int count = 0; 107 | Method *list = class_copyMethodList([self class], &count); 108 | 109 | NSMutableArray *arrayM = [NSMutableArray array]; 110 | for (unsigned int i = 0; i < count; i++) 111 | { 112 | // 根据下标获取方法 113 | Method method = list[i]; 114 | 115 | SEL methodName = method_getName(method); 116 | 117 | NSString *methodName_OC = NSStringFromSelector(methodName); 118 | 119 | //IMP imp = method_getImplementation(method); 120 | const char *name_s =sel_getName(method_getName(method)); 121 | int arguments = method_getNumberOfArguments(method); 122 | const char* encoding = method_getTypeEncoding(method); 123 | NSLog(@"方法名:%@,参数个数:%d,编码方式:%@",[NSString stringWithUTF8String:name_s], 124 | arguments, 125 | [NSString stringWithUTF8String:encoding]); 126 | 127 | [arrayM addObject:methodName_OC]; 128 | } 129 | 130 | // 4. 释放数组 131 | free(list); 132 | 133 | // 5. 保存方法的数组对象 134 | objc_setAssociatedObject(self, ba_methodListKey, arrayM, OBJC_ASSOCIATION_COPY_NONATOMIC); 135 | 136 | return objc_getAssociatedObject(self, ba_methodListKey); 137 | } 138 | 139 | 140 | #pragma mark - 获取本类所有 ‘成员变量‘ 的数组 <用来调试> 141 | /** 获取当前类的所有成员变量 */ 142 | const char *ba_ivarListKey = "ba_ivarListKey"; 143 | + (NSArray *)ba_ivarList 144 | { 145 | 146 | // 1. 查询根据key 保存的成员变量数组 147 | NSArray *ivarList = objc_getAssociatedObject(self, ba_ivarListKey); 148 | 149 | // 2. 判断数组中是否有值, 如果有直接返回 150 | if (ivarList != nil) 151 | { 152 | return ivarList; 153 | } 154 | 155 | // 3. 如果数组中没有, 则根据当前类,获取当前类的所有 ‘成员变量‘ 156 | unsigned int count = 0; 157 | Ivar *ivars = class_copyIvarList([self class], &count); 158 | 159 | // 4. 遍历 成员变量 数组, 获取成员变量的名 160 | NSMutableArray *arrayM = [NSMutableArray array]; 161 | for (unsigned int i = 0; i < count; i++) { 162 | Ivar ivar = ivars[i]; 163 | // - C语言的字符串都是 ‘char *‘ 类型的 164 | const char *ivarName_C = ivar_getName(ivar); 165 | 166 | // - 将 C语言的字符串 转换成 OC字符串 167 | NSString *ivarName_OC = [NSString stringWithUTF8String:ivarName_C]; 168 | // - 将本类 ‘成员变量名‘ 添加到数组 169 | [arrayM addObject:ivarName_OC]; 170 | } 171 | 172 | // 5. 释放ivars 173 | free(ivars); 174 | 175 | // 6. 根据key 动态获取保存在关联对象中的数组 176 | objc_setAssociatedObject(self, ba_ivarListKey, arrayM, OBJC_ASSOCIATION_COPY_NONATOMIC); 177 | 178 | return objc_getAssociatedObject(self, ba_ivarListKey); 179 | } 180 | 181 | #pragma mark - 获取本类所有 ‘协议‘ 的数组 182 | /** 用来获取动态保存在关联对象中的协议数组 |运行时的关联对象根据key动态取值| */ 183 | const char *ba_protocolListKey = "ba_protocolListKey"; 184 | 185 | + (NSArray *)ba_protocolList { 186 | NSArray *protocolList = objc_getAssociatedObject(self, ba_protocolListKey); 187 | if (protocolList != nil) 188 | { 189 | return protocolList; 190 | } 191 | 192 | unsigned int count = 0; 193 | Protocol * __unsafe_unretained *protocolLists = class_copyProtocolList([self class], &count); 194 | 195 | NSMutableArray *arrayM = [NSMutableArray array]; 196 | for (unsigned int i = 0; i < count; i++) { 197 | // 获取 协议名 198 | Protocol *protocol = protocolLists[i]; 199 | const char *protocolName_C = protocol_getName(protocol); 200 | NSString *protocolName_OC = [NSString stringWithUTF8String:protocolName_C]; 201 | 202 | // 将 协议名 添加到数组 203 | [arrayM addObject:protocolName_OC]; 204 | } 205 | 206 | // 释放数组 207 | free(protocolLists); 208 | // 将保存 协议的数组动态添加到 关联对象 209 | objc_setAssociatedObject(self, ba_protocolListKey, arrayM, OBJC_ASSOCIATION_COPY_NONATOMIC); 210 | 211 | return objc_getAssociatedObject(self, ba_protocolListKey); 212 | } 213 | 214 | 215 | @end 216 | -------------------------------------------------------------------------------- /PAWebView/PAWebViewCache/WKWebView+PAWebCache.h: -------------------------------------------------------------------------------- 1 | // 2 | // WKWebView+PAWebCache.h 3 | // Pkit 4 | // 5 | // Created by llyouss on 2017/12/28. 6 | // Copyright © 2017年 llyouss. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface WKWebView (PAWebCache) 12 | 13 | /** 清除webView缓存 */ 14 | - (void)clearWebCacheFinish:(void(^)(BOOL finish,NSError *error))block; 15 | 16 | /** 清理缓存的方法,这个方法会清除缓存类型为HTML类型的文件*/ 17 | - (void)clearHTMLCache; 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /PAWebView/PAWebViewCache/WKWebView+PAWebCache.m: -------------------------------------------------------------------------------- 1 | // 2 | // WKWebView+PAWebCache.m 3 | // Pkit 4 | // 5 | // Created by llyouss on 2017/12/28. 6 | // Copyright © 2017年 llyouss. All rights reserved. 7 | // 8 | 9 | #import "WKWebView+PAWebCache.h" 10 | #import "UIAlertController+WKWebAlert.h" 11 | 12 | @implementation WKWebView (PAWebCache) 13 | 14 | #pragma mark - private method 15 | //拿到当前北京时间 16 | - (NSDate *)beijingTime { 17 | NSDate *date = [NSDate date]; 18 | NSTimeInterval inter = [[NSTimeZone systemTimeZone] secondsFromGMT]; 19 | return [date dateByAddingTimeInterval:inter]; 20 | } 21 | 22 | #pragma mark - 清除webView缓存 23 | - (void)clearWebCacheFinish:(void (^)(BOOL, NSError *))block 24 | { 25 | if ([[UIDevice currentDevice].systemVersion floatValue] >= 9.0) { 26 | NSSet *websiteDataTypes = [NSSet setWithArray: 27 | @[WKWebsiteDataTypeDiskCache, 28 | WKWebsiteDataTypeOfflineWebApplicationCache, 29 | WKWebsiteDataTypeMemoryCache, 30 | WKWebsiteDataTypeLocalStorage, 31 | //WKWebsiteDataTypeCookies, 32 | WKWebsiteDataTypeSessionStorage, 33 | WKWebsiteDataTypeIndexedDBDatabases, 34 | WKWebsiteDataTypeWebSQLDatabases 35 | ]]; 36 | //// All kinds of data 37 | //NSSet *websiteDataTypes = [WKWebsiteDataStore allWebsiteDataTypes]; 38 | //// Date from 39 | NSDate *dateFrom = [NSDate dateWithTimeIntervalSince1970:0]; 40 | //// Execute 41 | [[WKWebsiteDataStore defaultDataStore] removeDataOfTypes:websiteDataTypes modifiedSince:dateFrom completionHandler:^{ 42 | block ? block(YES,nil): NULL; 43 | }]; 44 | } else { 45 | NSString *libraryPath = [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) objectAtIndex:0]; 46 | NSString *cookiesFolderPath = [libraryPath stringByAppendingString:@"/Cookies"]; 47 | NSError *errors; 48 | [[NSFileManager defaultManager] removeItemAtPath:cookiesFolderPath error:&errors]; 49 | 50 | block ? block(YES,errors): NULL; 51 | } 52 | } 53 | 54 | /** 清理缓存的方法,这个方法会清除缓存类型为HTML类型的文件*/ 55 | - (void)clearHTMLCache 56 | { 57 | /* 取得Library文件夹的位置*/ 58 | NSString *libraryDir = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory,NSUserDomainMask, YES)[0]; 59 | /* 取得bundle id,用作文件拼接用*/ 60 | NSString *bundleId = [[[NSBundle mainBundle] infoDictionary]objectForKey:@"CFBundleIdentifier"]; 61 | /* 62 | * 拼接缓存地址,具体目录为App/Library/Caches/你的APPBundleID/fsCachedData 63 | */ 64 | NSString *webKitFolderInCachesfs = [NSString stringWithFormat:@"%@/Caches/%@/fsCachedData",libraryDir,bundleId]; 65 | 66 | NSError *error; 67 | /* 取得目录下所有的文件,取得文件数组*/ 68 | NSFileManager *fileManager = [NSFileManager defaultManager]; 69 | // NSArray *fileList = [[NSArray alloc] init]; 70 | //fileList便是包含有该文件夹下所有文件的文件名及文件夹名的数组 71 | NSArray *fileList = [fileManager contentsOfDirectoryAtPath:webKitFolderInCachesfs error:&error]; 72 | /* 遍历文件组成的数组*/ 73 | for(NSString * fileName in fileList){ 74 | /* 定位每个文件的位置*/ 75 | NSString * path = [[NSBundle bundleWithPath:webKitFolderInCachesfs] pathForResource:fileName ofType:@""]; 76 | /* 将文件转换为NSData类型的数据*/ 77 | NSData * fileData = [NSData dataWithContentsOfFile:path]; 78 | /* 如果FileData的长度大于2,说明FileData不为空*/ 79 | if(fileData.length >2){ 80 | /* 创建两个用于显示文件类型的变量*/ 81 | int char1 =0; 82 | int char2 =0; 83 | 84 | [fileData getBytes:&char1 range:NSMakeRange(0,1)]; 85 | [fileData getBytes:&char2 range:NSMakeRange(1,1)]; 86 | /* 拼接两个变量*/ 87 | NSString *numStr = [NSString stringWithFormat:@"%i%i",char1,char2]; 88 | /* 如果该文件前四个字符是6033,说明是Html文件,删除掉本地的缓存*/ 89 | if([numStr isEqualToString:@"6033"]){ 90 | [[NSFileManager defaultManager] removeItemAtPath:[NSString stringWithFormat:@"%@/%@",webKitFolderInCachesfs,fileName]error:&error]; 91 | continue; 92 | } 93 | } 94 | } 95 | } 96 | 97 | @end 98 | -------------------------------------------------------------------------------- /PAWebView/PAWebViewCache/WKWebView+PAWebCookie.h: -------------------------------------------------------------------------------- 1 | // 2 | // WKWebView+PAWebCookie.h 3 | // Pkit 4 | // 5 | // Created by llyouss on 2017/12/28. 6 | // Copyright © 2017年 llyouss. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface WKWebView (PAWebCookie) 12 | 13 | /** ios11 同步cookies */ 14 | - (void)syncCookiesToWKHTTPCookieStore:(WKHTTPCookieStore *)cookieStroe API_AVAILABLE(macosx(10.13), ios(11.0)); 15 | 16 | /** 插入cookies存储于磁盘 */ 17 | - (void)insertCookie:(NSHTTPCookie *)cookie; 18 | 19 | /** 获取本地磁盘的cookies */ 20 | - (NSMutableArray *)sharedHTTPCookieStorage; 21 | 22 | /** 删除所有的cookies */ 23 | - (void)clearWKCookies; 24 | 25 | /** 删除某一个cookies */ 26 | - (void)deleteWKCookie:(NSHTTPCookie *)cookie completionHandler:(nullable void (^)(void))completionHandler; 27 | - (void)deleteWKCookiesByHost:(NSURL *)host completionHandler:(nullable void (^)(void))completionHandler; 28 | 29 | /** js获取domain的cookie */ 30 | - (NSString *)jsCookieStringWithDomain:(NSString *)domain; 31 | - (WKUserScript *)searchCookieForUserScriptWithDomain:(NSString *)domain; 32 | 33 | /** PHP 获取domain的cookie */ 34 | - (NSString *)phpCookieStringWithDomain:(NSString *)domain; 35 | 36 | 37 | @end 38 | -------------------------------------------------------------------------------- /PAWebView/PAWebViewCache/WKWebView+PAWebCookie.m: -------------------------------------------------------------------------------- 1 | // 2 | // WKWebView+PAWebCookie.m 3 | // Pkit 4 | // 5 | // Created by llyouss on 2017/12/28. 6 | // Copyright © 2017年 llyouss. All rights reserved. 7 | // 8 | 9 | #import "WKWebView+PAWebCookie.h" 10 | 11 | static NSString* const PAWKCookiesKey = @"org.skyfox.PAWKShareInstanceCookies"; 12 | 13 | @implementation WKWebView (PAWebCookie) 14 | 15 | - (void)syncCookiesToWKHTTPCookieStore:(WKHTTPCookieStore *)cookieStore API_AVAILABLE(macosx(10.13), ios(11.0)) 16 | { 17 | NSMutableArray *cookieArr = [self sharedHTTPCookieStorage]; 18 | if (cookieArr.count == 0)return; 19 | for (NSHTTPCookie *cookie in cookieArr) { 20 | [cookieStore setCookie:cookie completionHandler:nil]; 21 | } 22 | } 23 | 24 | - (void)insertCookie:(NSHTTPCookie *)cookie 25 | { 26 | @autoreleasepool { 27 | 28 | if (@available(iOS 11.0, *)) { 29 | WKHTTPCookieStore *cookieStore = self.configuration.websiteDataStore.httpCookieStore; 30 | [cookieStore setCookie:cookie completionHandler:nil]; 31 | } 32 | 33 | NSHTTPCookieStorage * shareCookie = [NSHTTPCookieStorage sharedHTTPCookieStorage]; 34 | [shareCookie setCookie:cookie]; 35 | 36 | NSMutableArray *TempCookies = [NSMutableArray array]; 37 | NSMutableArray *localCookies =[NSKeyedUnarchiver unarchiveObjectWithData: [[NSUserDefaults standardUserDefaults] objectForKey: PAWKCookiesKey]]; 38 | for (int i = 0; i < localCookies.count; i++) { 39 | NSHTTPCookie *TempCookie = [localCookies objectAtIndex:i]; 40 | if ([cookie.name isEqualToString:TempCookie.name] && 41 | [cookie.domain isEqualToString:TempCookie.domain]) { 42 | [localCookies removeObject:TempCookie]; 43 | i--; 44 | break; 45 | } 46 | } 47 | [TempCookies addObjectsFromArray:localCookies]; 48 | [TempCookies addObject:cookie]; 49 | NSData *cookiesData = [NSKeyedArchiver archivedDataWithRootObject: TempCookies]; 50 | [[NSUserDefaults standardUserDefaults] setObject:cookiesData forKey:PAWKCookiesKey]; 51 | [[NSUserDefaults standardUserDefaults] synchronize]; 52 | } 53 | } 54 | 55 | - (NSMutableArray *)sharedHTTPCookieStorage 56 | { 57 | @autoreleasepool { 58 | NSMutableArray *cookiesArr = [NSMutableArray array]; 59 | /** 获取NSHTTPCookieStorage cookies WKHTTPCookieStore 的cookie 已经同步*/ 60 | NSHTTPCookieStorage * shareCookie = [NSHTTPCookieStorage sharedHTTPCookieStorage]; 61 | for (NSHTTPCookie *cookie in shareCookie.cookies){ 62 | [cookiesArr addObject:cookie]; 63 | } 64 | 65 | /** 获取自定义存储的cookies */ 66 | NSMutableArray *cookies = [NSKeyedUnarchiver unarchiveObjectWithData: [[NSUserDefaults standardUserDefaults] objectForKey: PAWKCookiesKey]]; 67 | 68 | //删除过期的cookies 69 | for (int i = 0; i < cookies.count; i++) { 70 | NSHTTPCookie *cookie = [cookies objectAtIndex:i]; 71 | if (!cookie.expiresDate) { 72 | [cookiesArr addObject:cookie]; //当cookie布设置国旗时间时,视cookie的有效期为长期有效。 73 | continue; 74 | } 75 | if ([cookie.expiresDate compare:self.currentTime]) { 76 | [cookiesArr addObject:cookie]; 77 | }else 78 | { 79 | [cookies removeObject:cookie]; //清除过期的cookie。 80 | i--; 81 | } 82 | } 83 | 84 | //存储最新有效的cookies 85 | NSData *cookiesData = [NSKeyedArchiver archivedDataWithRootObject: cookies]; 86 | [[NSUserDefaults standardUserDefaults] setObject:cookiesData forKey:PAWKCookiesKey]; 87 | [[NSUserDefaults standardUserDefaults] synchronize]; 88 | 89 | return cookiesArr; 90 | } 91 | } 92 | 93 | - (void)clearWKCookies 94 | { 95 | if (@available(iOS 11.0, *)) { 96 | NSSet *websiteDataTypes = [NSSet setWithObject:WKWebsiteDataTypeCookies]; 97 | NSDate *dateFrom = [NSDate dateWithTimeIntervalSince1970:0]; 98 | [[WKWebsiteDataStore defaultDataStore] removeDataOfTypes:websiteDataTypes modifiedSince:dateFrom completionHandler:^{ 99 | }]; 100 | } 101 | 102 | //删除NSHTTPCookieStorage中的cookies 103 | NSHTTPCookieStorage *NSCookiesStore = [NSHTTPCookieStorage sharedHTTPCookieStorage]; 104 | [NSCookiesStore removeCookiesSinceDate:[NSDate dateWithTimeIntervalSince1970:0]]; 105 | 106 | NSData *cookiesData = [NSKeyedArchiver archivedDataWithRootObject: @[]]; 107 | [[NSUserDefaults standardUserDefaults] setObject:cookiesData forKey:PAWKCookiesKey]; 108 | [[NSUserDefaults standardUserDefaults] synchronize]; 109 | } 110 | 111 | - (void)deleteWKCookie:(NSHTTPCookie *)cookie completionHandler:(nullable void (^)(void))completionHandler; 112 | { 113 | if (@available(iOS 11.0, *)) { 114 | 115 | //删除WKHTTPCookieStore中的cookies 116 | WKHTTPCookieStore *cookieStore = self.configuration.websiteDataStore.httpCookieStore; 117 | [cookieStore deleteCookie:cookie completionHandler:nil]; 118 | } 119 | 120 | //删除NSHTTPCookieStorage中的cookie 121 | NSHTTPCookieStorage *NSCookiesStore = [NSHTTPCookieStorage sharedHTTPCookieStorage]; 122 | [NSCookiesStore deleteCookie:cookie]; 123 | 124 | //删除磁盘中的cookie 125 | NSMutableArray *localCookies =[NSKeyedUnarchiver unarchiveObjectWithData: [[NSUserDefaults standardUserDefaults] objectForKey: PAWKCookiesKey]]; 126 | for (int i = 0; i < localCookies.count; i++) { 127 | NSHTTPCookie *TempCookie = [localCookies objectAtIndex:i]; 128 | if ([cookie.domain isEqualToString:TempCookie.domain] && 129 | [cookie.domain isEqualToString:TempCookie.domain] ) { 130 | [localCookies removeObject:TempCookie]; 131 | i--; 132 | break; 133 | } 134 | } 135 | 136 | NSData *cookiesData = [NSKeyedArchiver archivedDataWithRootObject: localCookies]; 137 | [[NSUserDefaults standardUserDefaults] setObject:cookiesData forKey:PAWKCookiesKey]; 138 | [[NSUserDefaults standardUserDefaults] synchronize]; 139 | completionHandler ? completionHandler() : NULL; 140 | } 141 | 142 | - (void)deleteWKCookiesByHost:(NSURL *)host completionHandler:(nullable void (^)(void))completionHandler{ 143 | 144 | if (@available(iOS 11.0, *)) { 145 | //删除WKHTTPCookieStore中的cookies 146 | WKHTTPCookieStore *cookieStore = self.configuration.websiteDataStore.httpCookieStore; 147 | [cookieStore getAllCookies:^(NSArray * cookies) { 148 | 149 | NSArray *WKCookies = cookies; 150 | for (NSHTTPCookie *cookie in WKCookies) { 151 | 152 | NSURL *domainURL = [NSURL URLWithString:cookie.domain]; 153 | if ([domainURL.host isEqualToString:host.host]) { 154 | [cookieStore deleteCookie:cookie completionHandler:nil]; 155 | } 156 | } 157 | }]; 158 | } 159 | 160 | //删除NSHTTPCookieStorage中的cookies 161 | NSHTTPCookieStorage *NSCookiesStore = [NSHTTPCookieStorage sharedHTTPCookieStorage]; 162 | NSArray *NSCookies = NSCookiesStore.cookies; 163 | for (NSHTTPCookie *cookie in NSCookies) { 164 | 165 | NSURL *domainURL = [NSURL URLWithString:cookie.domain]; 166 | if ([domainURL.host isEqualToString:host.host]) { 167 | [NSCookiesStore deleteCookie:cookie]; 168 | } 169 | } 170 | 171 | //删除磁盘中的cookies 172 | NSMutableArray *localCookies =[NSKeyedUnarchiver unarchiveObjectWithData: [[NSUserDefaults standardUserDefaults] objectForKey: PAWKCookiesKey]]; 173 | for (int i = 0; i < localCookies.count; i++) { 174 | 175 | NSHTTPCookie *TempCookie = [localCookies objectAtIndex:i]; 176 | NSURL *domainURL = [NSURL URLWithString:TempCookie.domain]; 177 | if ([host.host isEqualToString:domainURL.host]) { 178 | [localCookies removeObject:TempCookie]; 179 | i--; 180 | break; 181 | } 182 | } 183 | 184 | NSData *cookiesData = [NSKeyedArchiver archivedDataWithRootObject: localCookies]; 185 | [[NSUserDefaults standardUserDefaults] setObject:cookiesData forKey:PAWKCookiesKey]; 186 | [[NSUserDefaults standardUserDefaults] synchronize]; 187 | completionHandler ? completionHandler() : NULL; 188 | 189 | } 190 | 191 | /** js获取domain的cookie */ 192 | - (NSString *)jsCookieStringWithDomain:(NSString *)domain 193 | { 194 | @autoreleasepool { 195 | NSMutableString *cookieSting = [NSMutableString string]; 196 | NSArray *cookieArr = [self sharedHTTPCookieStorage]; 197 | for (NSHTTPCookie *cookie in cookieArr) { 198 | if ([cookie.domain containsString:domain]) { 199 | [cookieSting appendString:[NSString stringWithFormat:@"document.cookie = '%@=%@';",cookie.name,cookie.value]]; 200 | } 201 | } 202 | return cookieSting; 203 | } 204 | } 205 | 206 | - (WKUserScript *)searchCookieForUserScriptWithDomain:(NSString *)domain 207 | { 208 | NSString *cookie = [self jsCookieStringWithDomain:domain]; 209 | WKUserScript * cookieScript = [[WKUserScript alloc] initWithSource: cookie injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:NO]; 210 | return cookieScript; 211 | } 212 | 213 | /** PHP 获取domain的cookie */ 214 | - (NSString *)phpCookieStringWithDomain:(NSString *)domain 215 | { 216 | @autoreleasepool { 217 | NSMutableString *cookieSting =[NSMutableString string]; 218 | NSArray *cookieArr = [self sharedHTTPCookieStorage]; 219 | for (NSHTTPCookie *cookie in cookieArr) { 220 | if ([cookie.domain containsString:domain]) { 221 | [cookieSting appendString:[NSString stringWithFormat:@"%@ = %@;",cookie.name,cookie.value]]; 222 | } 223 | } 224 | if (cookieSting.length > 1)[cookieSting deleteCharactersInRange:NSMakeRange(cookieSting.length - 1, 1)]; 225 | 226 | return (NSString *)cookieSting; 227 | } 228 | } 229 | 230 | - (NSDate *)currentTime 231 | { 232 | return [NSDate dateWithTimeIntervalSinceNow:0]; 233 | } 234 | 235 | @end 236 | -------------------------------------------------------------------------------- /PAWebView/PAWebViewExtern/PAPhotoBrowser/PAPhotoBrowser.h: -------------------------------------------------------------------------------- 1 | // 2 | // PAPhotoBrowser.h 3 | // Pkit 4 | // 5 | // Created by llyouss on 2018/5/11. 6 | // Copyright © 2018年 llyouss. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import 12 | 13 | @interface PAPhotoBrowser : NSObject 14 | 15 | @property (nonatomic, retain) NSMutableArray *photos; 16 | @property (nonatomic, retain) NSMutableArray *video; 17 | 18 | + (instancetype)shareInstance; 19 | - (void)loadPhotoBrowserShowIndex:(NSInteger)index; 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /PAWebView/PAWebViewExtern/PAPhotoBrowser/PAPhotoBrowser.m: -------------------------------------------------------------------------------- 1 | // 2 | // PAPhotoBrowser.m 3 | // Pkit 4 | // 5 | // Created by llyouss on 2018/5/11. 6 | // Copyright © 2018年 llyouss. All rights reserved. 7 | // 8 | 9 | #import "PAPhotoBrowser.h" 10 | #import 11 | 12 | @interface PAPhotoBrowser () 13 | 14 | @property (nonatomic, retain) NSMutableArray *thumbs; 15 | 16 | @end 17 | 18 | @implementation PAPhotoBrowser 19 | 20 | + (instancetype)shareInstance{ 21 | 22 | static PAPhotoBrowser *photoBrowser = nil; 23 | static dispatch_once_t onceToken; 24 | dispatch_once(&onceToken, ^{ 25 | photoBrowser = [[PAPhotoBrowser alloc]init]; 26 | }); 27 | 28 | return photoBrowser; 29 | } 30 | 31 | - (NSMutableArray *)thumbs{ 32 | 33 | _thumbs = _thumbs ? _thumbs : [NSMutableArray array]; 34 | return _thumbs; 35 | } 36 | 37 | - (void)setPhotos:(NSMutableArray *)photos 38 | { 39 | _photos = photos; 40 | } 41 | 42 | - (void)loadPhotoBrowserShowIndex:(NSInteger)index{ 43 | 44 | #pragma clang diagnostic push 45 | #pragma clang diagnostic ignored "-Wundeclared-selector" 46 | 47 | id photo; 48 | Class photoClass = NSClassFromString(@"MWPhoto"); 49 | __weak typeof(self)weakSelf = self; 50 | 51 | [self.thumbs removeAllObjects]; 52 | 53 | //添加图片 54 | for (id imageOBJ in _photos) { 55 | if ([imageOBJ isKindOfClass:[NSString class]]) { 56 | 57 | 58 | photo = ((id (*)(id, SEL, NSURL *)) 59 | objc_msgSend)(photoClass, 60 | @selector(photoWithURL:), 61 | [NSURL URLWithString:imageOBJ] 62 | ); 63 | }else if ([imageOBJ isKindOfClass:[NSURL class]]){ 64 | photo = ((id (*)(id, SEL, NSURL *)) 65 | objc_msgSend)(photoClass, 66 | @selector(photoWithURL:), 67 | [NSURL URLWithString:imageOBJ] 68 | ); 69 | }else if ([imageOBJ isKindOfClass:[UIImage class]]){ 70 | 71 | photo = ((id (*)(id, SEL, NSString *)) 72 | objc_msgSend)(photoClass, 73 | @selector(photoWithImage:), 74 | imageOBJ 75 | ); 76 | 77 | } 78 | [weakSelf.thumbs addObject:photo]; 79 | } 80 | 81 | //添加视频 82 | for (NSString *videoURLString in _video) { 83 | 84 | __block id video; 85 | [self thumbnailImageWithUrl:[NSURL URLWithString:videoURLString] completion:^(UIImage *iamge) { 86 | 87 | video = ((id (*)(id, SEL, UIImage *)) 88 | objc_msgSend)(photoClass, 89 | @selector(photoWithImage:), 90 | iamge 91 | ); 92 | [video setObject:[NSURL URLWithString:videoURLString] forKey:@"videoURL"]; 93 | [weakSelf.thumbs addObject:video]; 94 | }]; 95 | } 96 | 97 | // Create browser 98 | UIViewController * browser = [NSClassFromString(@"MWPhotoBrowser") alloc]; 99 | 100 | if (!browser) { 101 | NSLog(@"请使用拖入MWPhotoBrowser框架"); 102 | return ; 103 | } 104 | 105 | SEL fun = NSSelectorFromString(@"initWithDelegate:"); 106 | if ([browser respondsToSelector:fun]) { 107 | 108 | #pragma clang diagnostic push 109 | #pragma clang diagnostic "-Warc-performSelector-leaks" 110 | browser = [browser performSelector:fun withObject:self]; 111 | #pragma clang diagnostic pop 112 | 113 | }else{ 114 | NSLog(@"请使用PAPhotoBrowser扩展!"); 115 | return ; 116 | } 117 | 118 | [browser setValue:@YES forKey:@"displayActionButton"]; 119 | [browser setValue:@YES forKey:@"displayNavArrows"]; 120 | [browser setValue:@NO forKey:@"displaySelectionButtons"]; 121 | [browser setValue:@NO forKey:@"alwaysShowControls"]; 122 | [browser setValue:@YES forKey:@"zoomPhotosToFill"]; 123 | [browser setValue:@NO forKey:@"enableGrid"]; 124 | [browser setValue:@NO forKey:@"startOnGrid"]; 125 | [browser setValue:@NO forKey:@"enableSwipeToDismiss"]; 126 | [browser setValue:@NO forKey:@"autoPlayOnAppear"]; 127 | 128 | //设置位置 129 | ((void (*)(id, SEL, NSInteger)) 130 | objc_msgSend)(browser, 131 | @selector(setCurrentPhotoIndex:), 132 | index 133 | ); 134 | 135 | //监听 136 | ((void (*)(id, SEL, id, NSString *, unsigned long, void *)) 137 | objc_msgSend)(browser.navigationItem, 138 | @selector(addObserver:forKeyPath:options:context:), 139 | self, 140 | @"rightBarButtonItem", 141 | 1, 142 | NULL 143 | ); 144 | 145 | #pragma clang diagnostic pop 146 | 147 | // Present 148 | UINavigationController *nc = [[UINavigationController alloc] initWithRootViewController:browser]; 149 | nc.modalTransitionStyle = UIModalTransitionStyleCrossDissolve; 150 | [self.getCurrentVC presentViewController:nc animated:YES completion:nil]; 151 | } 152 | 153 | /** 替换 done */ 154 | -(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context 155 | { 156 | if ([keyPath isEqualToString:@"rightBarButtonItem"]) 157 | { 158 | if ([object isKindOfClass:[UINavigationItem class]]) 159 | { 160 | NSArray *array = [object valueForKey:@"_rightBarButtonItems"]; 161 | for (UIBarButtonItem *doneButton in array) { 162 | if ([doneButton.title.lowercaseString isEqualToString:@"done"]) { 163 | [doneButton setTitle:@"关闭"]; 164 | [object removeObserver:self forKeyPath:@"rightBarButtonItem"]; 165 | } 166 | } 167 | } 168 | } 169 | } 170 | 171 | #pragma mark - MWPhotoBrowserDelegate 172 | 173 | //实现MWPhotoBrowser 的代理方法 174 | + (BOOL)resolveInstanceMethod:(SEL)sel 175 | { 176 | #pragma clang diagnostic push 177 | #pragma clang diagnostic ignored "-Wundeclared-selector" 178 | 179 | if (sel == @selector(numberOfPhotosInPhotoBrowser:)) 180 | { 181 | //添加代理方法 182 | class_addMethod(self, @selector(numberOfPhotosInPhotoBrowser:), (IMP)numberOfPhotosInPhotoBrowser, "v@:"); 183 | 184 | } 185 | 186 | if (sel == @selector(photoBrowser:photoAtIndex:)) 187 | { 188 | //添加代理方法 189 | class_addMethod(self, @selector(photoBrowser:photoAtIndex:), (IMP)photoBrowserPhotoAtIndex, "v@:"); 190 | } 191 | 192 | return [super resolveInstanceMethod:sel]; 193 | #pragma clang diagnostic pop 194 | } 195 | 196 | //实现代理方法 - (NSUInteger)numberOfPhotosInPhotoBrowser:(MWPhotoBrowser *)photoBrowser; 197 | unsigned long numberOfPhotosInPhotoBrowser(id self,SEL sel,id object){ 198 | 199 | return [self thumbs].count ; 200 | } 201 | 202 | //实现代理方法 - (id )photoBrowser:(MWPhotoBrowser *)photoBrowser photoAtIndex:(NSUInteger)index; 203 | id photoBrowserPhotoAtIndex(id self, SEL sel, id obj, unsigned long index){ 204 | 205 | if (index < [self thumbs].count) 206 | return [[self thumbs] objectAtIndex:index]; 207 | return nil; 208 | } 209 | 210 | //获取视频的缩略图 211 | - (void) thumbnailImageWithUrl:(NSURL *)videoURL completion:(void (^)(UIImage *iamge))block { 212 | 213 | AVURLAsset *asset = [[AVURLAsset alloc] initWithURL:videoURL options:nil]; 214 | AVAssetImageGenerator *assetImageGenerator =[[AVAssetImageGenerator alloc] initWithAsset:asset]; 215 | assetImageGenerator.appliesPreferredTrackTransform = YES; 216 | assetImageGenerator.apertureMode = AVAssetImageGeneratorApertureModeEncodedPixels; 217 | 218 | CGImageRef thumbnailImageRef = NULL; 219 | CMTime time = CMTimeMakeWithSeconds(0.0, 5); 220 | NSError *thumbnailImageGenerationError = nil; 221 | thumbnailImageRef = [assetImageGenerator copyCGImageAtTime:time actualTime:NULL error:&thumbnailImageGenerationError]; 222 | 223 | if(!thumbnailImageRef) 224 | NSLog(@"thumbnailImageGenerationError %@",thumbnailImageGenerationError); 225 | 226 | UIImage*thumbnailImage = thumbnailImageRef ? [[UIImage alloc]initWithCGImage: thumbnailImageRef] : nil; 227 | 228 | block ? block(thumbnailImage) : NULL; 229 | } 230 | 231 | //获取Window当前显示的ViewController 232 | - (UIViewController*)getCurrentVC 233 | { 234 | //获得当前活动窗口的根视图 235 | UIViewController* vc = [UIApplication sharedApplication].keyWindow.rootViewController; 236 | while (1) 237 | { 238 | //根据不同的页面切换方式,逐步取得最上层的viewController 239 | if ([vc isKindOfClass:[UITabBarController class]]) { 240 | vc = ((UITabBarController*)vc).selectedViewController; 241 | } 242 | if ([vc isKindOfClass:[UINavigationController class]]) { 243 | vc = ((UINavigationController*)vc).visibleViewController; 244 | } 245 | if (vc.presentedViewController) { 246 | vc = vc.presentedViewController; 247 | }else{ 248 | break; 249 | } 250 | } 251 | return vc; 252 | } 253 | 254 | @end 255 | -------------------------------------------------------------------------------- /PAWebView/PAWebViewExtern/TYSnapshot/PreviewVc.h: -------------------------------------------------------------------------------- 1 | // 2 | // PreviewVc.h 3 | // TYSnapshotScroll 4 | // 5 | // Created by TonyReet on 2017/11/24. 6 | // Copyright © 2017年 TonyReet. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface PreviewVc : UIViewController 12 | 13 | - (instancetype)init:(UIImage *)image; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /PAWebView/PAWebViewExtern/TYSnapshot/PreviewVc.m: -------------------------------------------------------------------------------- 1 | // 2 | // PreviewVc.m 3 | // TYSnapshotScroll 4 | // 5 | // Created by TonyReet on 2017/11/24. 6 | // Copyright © 2017年 TonyReet. All rights reserved. 7 | // 8 | 9 | #import "PreviewVc.h" 10 | 11 | @interface PreviewVc () 12 | 13 | @property (nonatomic,strong) UIScrollView *scrollView; 14 | 15 | @property (nonatomic,strong) UIImageView *imageView; 16 | 17 | @property (nonatomic,strong) UIImage *image; 18 | 19 | @end 20 | 21 | @implementation PreviewVc 22 | 23 | 24 | - (instancetype)init:(UIImage *)image 25 | { 26 | self = [super init]; //用于初始化父类 27 | if (self) { 28 | self.image = image; 29 | } 30 | return self; 31 | } 32 | 33 | - (void)viewDidLoad { 34 | [super viewDidLoad]; 35 | 36 | [self initView]; 37 | 38 | self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"保存" style:UIBarButtonItemStylePlain target:self action:@selector(saveImage)]; 39 | self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"放弃" style:UIBarButtonItemStylePlain target:self action:@selector(dismissViewControllerAnimated)]; 40 | } 41 | 42 | - (void)initView{ 43 | self.view.backgroundColor = [UIColor whiteColor]; 44 | 45 | self.imageView = [UIImageView new]; 46 | self.imageView.image = self.image; 47 | self.imageView.contentMode = UIViewContentModeScaleAspectFit; 48 | 49 | self.scrollView = [UIScrollView new]; 50 | [self.scrollView addSubview:self.imageView]; 51 | 52 | [self.view addSubview:self.scrollView]; 53 | } 54 | 55 | - (void)viewDidAppear:(BOOL)animated{ 56 | [super viewDidAppear:animated]; 57 | 58 | CGFloat height = self.image.size.height; 59 | CGFloat width = self.image.size.width; 60 | self.scrollView.frame = self.view.bounds; 61 | self.scrollView.contentSize = CGSizeMake(width, height); 62 | self.imageView.frame = CGRectMake(0, 0, width, height); 63 | } 64 | 65 | - (void)saveImage{ 66 | //save to photosAlbum 67 | UIImageWriteToSavedPhotosAlbum(self.image, self, @selector(image:didFinishSavingWithError:contextInfo:), nil); 68 | } 69 | 70 | - (void)image:(UIImage *)image didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo 71 | { 72 | NSString *barItemTitle = @"保存成功"; 73 | if (error != nil) { 74 | barItemTitle = @"保存失败"; 75 | } 76 | 77 | [self.navigationItem.rightBarButtonItem setTitle:barItemTitle]; 78 | [self dismissViewControllerAnimated]; 79 | } 80 | 81 | - (void)dismissViewControllerAnimated{ 82 | [self dismissViewControllerAnimated:YES completion:nil]; 83 | } 84 | 85 | @end 86 | -------------------------------------------------------------------------------- /PAWebView/PAWebViewExtern/TYSnapshot/TYSnapshot.h: -------------------------------------------------------------------------------- 1 | // 2 | // TYSnapshot.h 3 | // TYSnapshotScroll 4 | // 5 | // Created by apple on 16/12/28. 6 | // Copyright © 2016年 TonyReet. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import "PreviewVc.h" 12 | 13 | #define TYSnapshotMainScreenBounds [UIScreen mainScreen].bounds 14 | 15 | @interface TYSnapshot : NSObject 16 | 17 | + (void )screenSnapshot:(UIView *)snapshotView finishBlock:(void(^)(UIImage *snapShotImage))finishBloc; 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /PAWebView/PAWebViewExtern/TYSnapshot/TYSnapshot.m: -------------------------------------------------------------------------------- 1 | // 2 | // TYSnapshot.m 3 | // TYSnapshotScroll 4 | // 5 | // Created by apple on 16/12/28. 6 | // Copyright © 2016年 TonyReet. All rights reserved. 7 | // 8 | 9 | #import "TYSnapshot.h" 10 | #import "WKWebView+TYSnapshot.h" 11 | #import "UIScrollView+TYSnapshot.h" 12 | 13 | @implementation TYSnapshot 14 | 15 | + (void )screenSnapshot:(UIView *)snapshotView finishBlock:(void(^)(UIImage *snapShotImage))finishBlock{ 16 | if ([snapshotView isKindOfClass:[UITableView class]]) { 17 | //tableview 18 | UITableView *tableView = (UITableView *)snapshotView; 19 | 20 | [tableView screenSnapshot:^(UIImage *snapShotImage) { 21 | if (snapShotImage != nil && finishBlock) { 22 | finishBlock(snapShotImage); 23 | } 24 | }]; 25 | }else if([snapshotView isKindOfClass:[WKWebView class]]){ 26 | //WKWebView 27 | WKWebView *wkWebView = (WKWebView *)snapshotView; 28 | [wkWebView screenSnapshot:^(UIImage *snapShotImage) { 29 | if (snapShotImage != nil && finishBlock) { 30 | finishBlock(snapShotImage); 31 | } 32 | }]; 33 | 34 | }else if([snapshotView isKindOfClass:[UIWebView class]]){ 35 | 36 | //UIWebView 37 | UIWebView *webView = (UIWebView *)snapshotView; 38 | 39 | [webView.scrollView screenSnapshot:^(UIImage *snapShotImage) { 40 | if (snapShotImage != nil && finishBlock) { 41 | finishBlock(snapShotImage); 42 | } 43 | }]; 44 | }else if([snapshotView isKindOfClass:[UIScrollView class]]){ 45 | //ScrollView 46 | UIScrollView *scrollView = (UIScrollView *)snapshotView; 47 | 48 | [scrollView screenSnapshot:^(UIImage *snapShotImage) { 49 | if (snapShotImage != nil && finishBlock) { 50 | finishBlock(snapShotImage); 51 | } 52 | }]; 53 | }else{ 54 | NSLog(@"不支持的类型"); 55 | } 56 | } 57 | 58 | 59 | @end 60 | -------------------------------------------------------------------------------- /PAWebView/PAWebViewExtern/TYSnapshot/UIImage+TYSnapshot.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIImage+TYSnapshot.h 3 | // UITableViewSnapshotTest 4 | // 5 | // Created by Tony on 2016/7/11. 6 | // Copyright © 2016年 TonyReet. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface UIImage (TYSnapshot) 12 | 13 | /** 14 | * 拼接快照 15 | * 16 | * @param imagesArr 快照的数组 17 | * 18 | * @return 最终拼接的图片 19 | */ 20 | + (UIImage *)getImageFromImagesArray:(NSArray *)imagesArr; 21 | 22 | @end 23 | -------------------------------------------------------------------------------- /PAWebView/PAWebViewExtern/TYSnapshot/UIImage+TYSnapshot.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIImage+TYSnapshot.h.m 3 | // UITableViewSnapshotTest 4 | // 5 | // Created by Tony on 2016/7/11. 6 | // Copyright © 2016年 TonyReet. All rights reserved. 7 | // 8 | 9 | #import "UIImage+TYSnapshot.h" 10 | 11 | @implementation UIImage (TYSnapshot) 12 | 13 | #pragma mark 拼接快照 14 | + (UIImage *)getImageFromImagesArray:(NSArray *)imagesArr 15 | { 16 | 17 | UIImage *image; 18 | @autoreleasepool{ 19 | CGSize imageTotalSize = [self getImageTotalSizeFromImagesArray:imagesArr]; 20 | UIGraphicsBeginImageContextWithOptions(imageTotalSize, NO, [UIScreen mainScreen].scale); 21 | 22 | //拼接图片 23 | int imageOffset = 0; 24 | for (UIImage *images in imagesArr) { 25 | [images drawAtPoint:CGPointMake(0, imageOffset)]; 26 | imageOffset += images.size.height; 27 | } 28 | 29 | image = UIGraphicsGetImageFromCurrentImageContext(); 30 | UIGraphicsEndImageContext(); 31 | } 32 | return image; 33 | 34 | } 35 | 36 | #pragma mark 获取全部图片拼接后size 37 | + (CGSize)getImageTotalSizeFromImagesArray:(NSArray *)imagesArr 38 | { 39 | CGSize totalSize = CGSizeZero; 40 | for (UIImage *image in imagesArr) { 41 | CGSize imageSize = [image size]; 42 | totalSize.height += imageSize.height; 43 | totalSize.width = MAX(totalSize.width, imageSize.width); 44 | } 45 | return totalSize; 46 | } 47 | 48 | @end 49 | -------------------------------------------------------------------------------- /PAWebView/PAWebViewExtern/TYSnapshot/UIScrollView+TYSnapshot.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIScrollView+TYSnapshot.h 3 | // UITableViewSnapshotTest 4 | // 5 | // Created by Tony on 2016/7/11. 6 | // Copyright © 2016年 TonyReet. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface UIScrollView (TYSnapshot) 12 | 13 | - (void )screenSnapshot:(void(^)(UIImage *snapShotImage))finishBlock; 14 | 15 | //- (void )screenWebViewSnapshot:(void(^)(UIImage *snapShotImage))finishBlock; 16 | 17 | +(UIImage *)screenSnapshotWithSnapshotView:(UIView *)snapshotView; 18 | 19 | +(UIImage *)screenSnapshotWithSnapshotView:(UIView *)snapshotView snapshotSize:(CGSize )snapshotSize; 20 | @end 21 | 22 | -------------------------------------------------------------------------------- /PAWebView/PAWebViewExtern/TYSnapshot/UIScrollView+TYSnapshot.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIScrollView+TYSnapshot.m 3 | // UITableViewSnapshotTest 4 | // 5 | // Created by Tony on 2016/7/11. 6 | // Copyright © 2016年 TonyReet. All rights reserved. 7 | // 8 | 9 | #import "UIScrollView+TYSnapshot.h" 10 | #import "UIImage+TYSnapshot.h" 11 | 12 | @implementation UIScrollView (TYSnapshot) 13 | 14 | - (void )screenSnapshot:(void(^)(UIImage *snapShotImage))finishBlock{ 15 | __block UIImage* snapshotImage = nil; 16 | 17 | //保存offset 18 | CGPoint oldContentOffset = self.contentOffset; 19 | //保存frame 20 | CGRect oldFrame = self.frame; 21 | 22 | if (self.contentSize.height > self.frame.size.height) { 23 | self.contentOffset = CGPointMake(0, self.contentSize.height - self.frame.size.height); 24 | } 25 | self.frame = CGRectMake(0, 0, self.contentSize.width, self.contentSize.height); 26 | 27 | //延迟0.3秒,避免有时候渲染不出来的情况 28 | [NSThread sleepForTimeInterval:0.3]; 29 | 30 | self.contentOffset = CGPointZero; 31 | @autoreleasepool{ 32 | UIGraphicsBeginImageContextWithOptions(self.bounds.size,NO,[UIScreen mainScreen].scale); 33 | 34 | [self.layer renderInContext: UIGraphicsGetCurrentContext()]; 35 | 36 | snapshotImage = UIGraphicsGetImageFromCurrentImageContext(); 37 | 38 | UIGraphicsEndImageContext(); 39 | } 40 | 41 | self.frame = oldFrame; 42 | //还原 43 | self.contentOffset = oldContentOffset; 44 | 45 | if (snapshotImage != nil && finishBlock) { 46 | finishBlock(snapshotImage); 47 | } 48 | } 49 | 50 | #pragma mark - 获取屏幕快照 51 | /* 52 | * snapshotView:需要截取的view 53 | */ 54 | +(UIImage *)screenSnapshotWithSnapshotView:(UIView *)snapshotView 55 | { 56 | return [self screenSnapshotWithSnapshotView:snapshotView snapshotSize:CGSizeZero]; 57 | } 58 | 59 | /* 60 | * snapshotView:需要截取的view 61 | * snapshotSize:需要截取的size 62 | */ 63 | +(UIImage *)screenSnapshotWithSnapshotView:(UIView *)snapshotView snapshotSize:(CGSize )snapshotSize 64 | { 65 | UIImage *snapshotImg; 66 | 67 | @autoreleasepool{ 68 | if (snapshotSize.height == 0|| snapshotSize.width == 0) {//宽高为0的时候没有意义 69 | snapshotSize = snapshotView.bounds.size; 70 | } 71 | 72 | //创建 73 | UIGraphicsBeginImageContextWithOptions(snapshotSize,NO,[UIScreen mainScreen].scale); 74 | 75 | CGContextRef context = UIGraphicsGetCurrentContext(); 76 | 77 | [snapshotView.layer renderInContext:context]; 78 | 79 | //获取图片 80 | snapshotImg = UIGraphicsGetImageFromCurrentImageContext(); 81 | 82 | //关闭 83 | UIGraphicsEndImageContext(); 84 | } 85 | return snapshotImg; 86 | } 87 | 88 | 89 | @end 90 | 91 | -------------------------------------------------------------------------------- /PAWebView/PAWebViewExtern/TYSnapshot/UITableView+TYSnapshot.h: -------------------------------------------------------------------------------- 1 | // 2 | // UITableView+TYSnapshot.h 3 | // TableViewShotsTest 4 | // 5 | // Created by Tony on 2016/12/27. 6 | // Copyright © 2016年 TonyReet. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | 12 | @interface UITableView (TYSnapshot) 13 | 14 | - (void )screenSnapshot:(void(^)(UIImage *snapShotImage))finishBlock; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /PAWebView/PAWebViewExtern/TYSnapshot/UITableView+TYSnapshot.m: -------------------------------------------------------------------------------- 1 | // 2 | // UITableView+TYSnapshot.m 3 | // TableViewShotsTest 4 | // 5 | // Created by Tony on 2016/12/27. 6 | // Copyright © 2016年 TonyReet. All rights reserved. 7 | // 8 | 9 | #import "UITableView+TYSnapshot.h" 10 | #import "UIImage+TYSnapshot.h" 11 | #import "UIScrollView+TYSnapshot.h" 12 | 13 | @implementation UITableView (TYSnapshot) 14 | 15 | - (void )screenSnapshot:(void(^)(UIImage *snapShotImage))finishBlock{ 16 | NSMutableArray *screenSnapshots = [NSMutableArray array]; 17 | //表头快照 18 | UIImage *headerScreenSnapshot = [self screenSnapshotOfHeaderView]; 19 | 20 | if (headerScreenSnapshot) [screenSnapshots addObject:headerScreenSnapshot]; 21 | 22 | for (int section=0; section 10 | 11 | @interface WKWebView (TYSnapshot) 12 | 13 | - (void )screenSnapshot:(void(^)(UIImage *snapShotImage))finishBlock; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /PAWebView/PAWebViewExtern/TYSnapshot/WKWebView+TYSnapshot.m: -------------------------------------------------------------------------------- 1 | // 2 | // WKWebView+TYSnapshot.m 3 | // TYSnapshotScroll 4 | // 5 | // Created by apple on 16/12/28. 6 | // Copyright © 2016年 TonyReet. All rights reserved. 7 | // 8 | 9 | #import "WKWebView+TYSnapshot.h" 10 | 11 | @implementation WKWebView (TYSnapshot) 12 | 13 | - (void )screenSnapshot:(void(^)(UIImage *snapShotImage))finishBlock{ 14 | //添加遮盖 15 | UIView *snapShotView = [self snapshotViewAfterScreenUpdates:YES]; 16 | snapShotView.frame = CGRectMake(self.frame.origin.x, self.frame.origin.y, snapShotView.frame.size.width, snapShotView.frame.size.height); 17 | [self.superview addSubview:snapShotView]; 18 | 19 | 20 | //保存原始信息 21 | CGRect oldFrame = self.frame; 22 | CGPoint oldOffset = self.scrollView.contentOffset; 23 | CGSize contentSize = self.scrollView.contentSize; 24 | 25 | //计算快照屏幕数 26 | NSUInteger snapshotScreenCount = floorf(contentSize.height / self.scrollView.bounds.size.height); 27 | 28 | //设置frame为contentSize 29 | self.frame = CGRectMake(0, 0, contentSize.width, contentSize.height); 30 | 31 | self.scrollView.contentOffset = CGPointZero; 32 | 33 | UIGraphicsBeginImageContextWithOptions(contentSize, NO, [UIScreen mainScreen].scale); 34 | 35 | __weak typeof(self) weakSelf = self; 36 | //截取完所有图片 37 | [self scrollToDraw:0 maxIndex:(NSInteger )snapshotScreenCount finishBlock:^{ 38 | [snapShotView removeFromSuperview]; 39 | 40 | UIImage *snapshotImage = UIGraphicsGetImageFromCurrentImageContext(); 41 | UIGraphicsEndImageContext(); 42 | 43 | weakSelf.frame = oldFrame; 44 | weakSelf.scrollView.contentOffset = oldOffset; 45 | 46 | if (finishBlock) { 47 | finishBlock(snapshotImage); 48 | } 49 | }]; 50 | } 51 | 52 | //滑动画了再截图 53 | - (void )scrollToDraw:(NSInteger )index maxIndex:(NSInteger )maxIndex finishBlock:(void(^)())finishBlock{ 54 | UIView *superView = self.superview; 55 | 56 | //截取的frame 57 | CGRect snapshotFrame = CGRectMake(0, (float)index * superView.bounds.size.height, superView.bounds.size.width, superView.bounds.size.height); 58 | 59 | // set up webview originY 60 | CGRect myFrame = self.frame; 61 | myFrame.origin.y = -((index) * superView.frame.size.height); 62 | self.frame = myFrame; 63 | 64 | __weak typeof(self) weakSelf = self; 65 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(300 * NSEC_PER_MSEC)), dispatch_get_main_queue(), ^{ 66 | 67 | [superView drawViewHierarchyInRect:snapshotFrame afterScreenUpdates:YES]; 68 | 69 | if(index < maxIndex){ 70 | [weakSelf scrollToDraw:index + 1 maxIndex:maxIndex finishBlock:finishBlock]; 71 | }else{ 72 | if (finishBlock) { 73 | finishBlock(); 74 | } 75 | } 76 | }); 77 | } 78 | 79 | @end 80 | -------------------------------------------------------------------------------- /PAWebView/PAWebViewTool/NSURL+PATool.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSURL+PATool.h 3 | // Pkit 4 | // 5 | // Created by llyouss on 2017/12/22. 6 | // Copyright © 2017年 llyouss. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface NSURL (PATool) 12 | 13 | 14 | 15 | /** 16 | 组合请求参数 17 | 18 | @param baseURL 请求链接,?之前部分 19 | @param params ?之后的参数 20 | @return 完整的请求链接 21 | */ 22 | + (NSURL *)generateURL:(NSString*)baseURL params:(NSDictionary*)params; 23 | 24 | 25 | /** 26 | 打开跨域请求 27 | 28 | @param URL 请求链接 29 | */ 30 | + (void)openURL:(NSURL *)URL; 31 | 32 | + (void)safariOpenURL:(NSURL *)URL; 33 | 34 | @end 35 | -------------------------------------------------------------------------------- /PAWebView/PAWebViewTool/NSURL+PATool.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSURL+PATool.m 3 | // Pkit 4 | // 5 | // Created by llyouss on 2017/12/22. 6 | // Copyright © 2017年 llyouss. All rights reserved. 7 | // 8 | 9 | #import "NSURL+PATool.h" 10 | #import 11 | #import "UIAlertController+WKWebAlert.h" 12 | #import "registerURLSchemes.h" 13 | 14 | #define IOS10BWK [[UIDevice currentDevice].systemVersion floatValue] >= 10 15 | #define IOS9BWK [[UIDevice currentDevice].systemVersion floatValue] >= 9 16 | 17 | @implementation NSURL (PATool) 18 | 19 | + (NSURL *)generateURL:(NSString*)baseURL params:(NSDictionary*)params { 20 | 21 | NSMutableDictionary *param = [NSMutableDictionary dictionaryWithDictionary:params]; 22 | NSMutableArray* pairs = [NSMutableArray array]; 23 | 24 | for (NSString* key in param.keyEnumerator) { 25 | NSString *value = [NSString stringWithFormat:@"%@",[param objectForKey:key]]; 26 | [pairs addObject:[NSString stringWithFormat:@"%@=%@", key, value]]; 27 | } 28 | 29 | NSString *query = [pairs componentsJoinedByString:@"&"]; 30 | 31 | #ifdef IOS9BWK 32 | 33 | NSCharacterSet *characterSet = [[NSCharacterSet characterSetWithCharactersInString:@"`#%^{}\"[]|\\<> "] invertedSet]; 34 | baseURL = [baseURL stringByAddingPercentEncodingWithAllowedCharacters:characterSet]; 35 | 36 | #else 37 | 38 | baseURL = [baseURL stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; 39 | 40 | #endif 41 | 42 | NSString* url = @""; 43 | if ([baseURL containsString:@"?"]) { 44 | url = [NSString stringWithFormat:@"%@&%@",baseURL, query]; 45 | } 46 | else { 47 | url = [NSString stringWithFormat:@"%@?%@",baseURL, query]; 48 | } 49 | 50 | return [NSURL URLWithString:url]; 51 | } 52 | 53 | + (void)openURL:(NSURL *)URL 54 | { 55 | __weak typeof(self)weekSelf = self; 56 | if ([URL.host.lowercaseString isEqualToString:@"itunes.apple.com"] || 57 | [URL.host.lowercaseString isEqualToString:@"itunesconnect.apple.com"]) { 58 | 59 | [UIAlertController PAlertWithTitle:[NSString stringWithFormat:@"即将打开Appstore下载应用"] message:@"如果不是本人操作,请取消" action1Title:@"取消" action2Title:@"打开" action1:^{ 60 | 61 | } action2:^{ 62 | 63 | [weekSelf safariOpenURL:URL]; 64 | }]; 65 | }else{ 66 | 67 | //获取应用名字 68 | NSDictionary *urlschemes = [registerURLSchemes urlschemes]; 69 | NSDictionary *appInfo = [urlschemes objectForKey:URL.scheme]; 70 | NSString *name =[appInfo objectForKey:@"name"]; 71 | 72 | if ([[UIApplication sharedApplication] canOpenURL:URL]) { 73 | 74 | if(!name) name = URL.scheme; 75 | [UIAlertController PAlertWithTitle:[NSString stringWithFormat:@"即将打开%@",name] message:@"如果不是本人操作,请取消" action1Title:@"取消" action2Title:@"打开" action1:^{ 76 | 77 | } action2:^{ 78 | 79 | [weekSelf safariOpenURL:URL]; 80 | }]; 81 | 82 | }else{ 83 | 84 | if (!appInfo) return; 85 | NSString *urlString = [appInfo objectForKey:@"url"]; 86 | if (!urlString) return; 87 | NSURL *appstoreURL = [NSURL URLWithString:urlString]; 88 | [UIAlertController PAlertWithTitle:[NSString stringWithFormat:@"前往Appstore下载"] message:@"你还没安装该应用,是否前往Appstore下载?" action1Title:@"取消" action2Title:@"去下载" action1:^{ 89 | 90 | } action2:^{ 91 | 92 | [weekSelf safariOpenURL:appstoreURL]; 93 | }]; 94 | } 95 | } 96 | } 97 | 98 | + (void)safariOpenURL:(NSURL *)URL 99 | { 100 | if (@available(iOS 10.0, *)) { 101 | [[UIApplication sharedApplication] openURL:URL options:@{UIApplicationOpenURLOptionUniversalLinksOnly : @NO} completionHandler:^(BOOL success) 102 | { 103 | if (!success) { 104 | [UIAlertController PAlertWithTitle:@"提示" message:@"打开失败" completion:nil]; 105 | } 106 | }]; 107 | } else { 108 | 109 | if (![[UIApplication sharedApplication] openURL:URL]) { 110 | [UIAlertController PAlertWithTitle:@"提示" message:@"打开失败" completion:nil]; 111 | } 112 | } 113 | } 114 | 115 | @end 116 | -------------------------------------------------------------------------------- /PAWebView/PAWebViewTool/PAWebViewMenu.h: -------------------------------------------------------------------------------- 1 | // 2 | // PAWebViewMenu.h 3 | // Pkit 4 | // 5 | // Created by llyouss on 2018/5/10. 6 | // Copyright © 2018年 llyouss. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "UIAlertController+WKWebAlert.h" 11 | 12 | typedef NS_ENUM(NSInteger,PAWebViewMenuType){ 13 | 14 | OPEN_SAFARI, 15 | COPY_URL, 16 | RELOAD_URL, 17 | SHARE_URL, 18 | }; 19 | 20 | NS_ASSUME_NONNULL_BEGIN 21 | 22 | @interface PAWebViewMenu : NSObject 23 | 24 | @property (nonatomic, assign) BOOL defaultType; 25 | 26 | + (instancetype)shareInstance; 27 | 28 | /** 29 | 默认菜单栏展示 30 | 31 | @param viewController 展示图层 32 | @param title 标题 33 | @param message 信息 34 | @param buttonTitleArray 按钮标题 35 | @param buttonTitleColorArray 按钮标题颜色 36 | @param popoverPresentationControllerBlock 弹框完毕回到 37 | @param block 响应回调 38 | */ 39 | - (void)defaultMenuShowInViewController:(nonnull UIViewController *)viewController 40 | title:(nullable NSString *)title 41 | message:(nullable NSString *)message 42 | buttonTitleArray:(nullable NSArray *)buttonTitleArray 43 | buttonTitleColorArray:(nullable NSArray *)buttonTitleColorArray 44 | #if TARGET_OS_IOS 45 | popoverPresentationControllerBlock:(nullable UIAlertControllerPopoverPresentationControllerBlock)popoverPresentationControllerBlock 46 | #endif 47 | block:(nullable BAKit_AlertControllerButtonActionBlock)block; 48 | 49 | 50 | #pragma clang diagnostic pop 51 | 52 | - (void)customMenuShowInViewController:(nonnull UIViewController *)viewController 53 | title:(nullable NSString *)title 54 | message:(nullable NSString *)message 55 | buttonTitleArray:(nullable NSArray *)buttonTitleArray 56 | buttonTitleColorArray:(nullable NSArray *)buttonTitleColorArray 57 | #if TARGET_OS_IOS 58 | popoverPresentationControllerBlock:(nullable UIAlertControllerPopoverPresentationControllerBlock)popoverPresentationControllerBlock 59 | #endif 60 | block:(nullable BAKit_AlertControllerButtonActionBlock)block; 61 | 62 | 63 | #pragma clang diagnostic pop 64 | 65 | 66 | 67 | @end 68 | 69 | NS_ASSUME_NONNULL_END 70 | -------------------------------------------------------------------------------- /PAWebView/PAWebViewTool/PAWebViewMenu.m: -------------------------------------------------------------------------------- 1 | // 2 | // PAWebViewMenu.m 3 | // Pkit 4 | // 5 | // Created by llyouss on 2018/5/10. 6 | // Copyright © 2018年 llyouss. All rights reserved. 7 | // 8 | 9 | #import "PAWebViewMenu.h" 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @implementation PAWebViewMenu 14 | 15 | + (instancetype)shareInstance{ 16 | 17 | static PAWebViewMenu *menu = nil; 18 | static dispatch_once_t onceToken; 19 | dispatch_once(&onceToken, ^{ 20 | menu = [[PAWebViewMenu alloc]init]; 21 | }); 22 | return menu; 23 | } 24 | 25 | - (void)defaultMenuShowInViewController:(nonnull UIViewController *)viewController 26 | title:(nullable NSString *)title 27 | message:(nullable NSString *)message 28 | buttonTitleArray:(nullable NSArray *)buttonTitleArray 29 | buttonTitleColorArray:(nullable NSArray *)buttonTitleColorArray 30 | popoverPresentationControllerBlock:(nullable UIAlertControllerPopoverPresentationControllerBlock)popoverPresentationControllerBlock 31 | block:(nullable BAKit_AlertControllerButtonActionBlock)block 32 | { 33 | 34 | [UIAlertController ba_actionSheetShowInViewController:viewController 35 | title:title 36 | message:message 37 | buttonTitleArray:buttonTitleArray 38 | buttonTitleColorArray:buttonTitleColorArray 39 | popoverPresentationControllerBlock:popoverPresentationControllerBlock 40 | block:block]; 41 | 42 | } 43 | 44 | - (void)customMenuShowInViewController:(UIViewController *)viewController 45 | title:(nullable NSString *)title 46 | message:(nullable NSString *)message 47 | buttonTitleArray:(nullable NSArray *)buttonTitleArray 48 | buttonTitleColorArray:(nullable NSArray *)buttonTitleColorArray 49 | popoverPresentationControllerBlock:(nullable UIAlertControllerPopoverPresentationControllerBlock)popoverPresentationControllerBlock 50 | block:(nullable BAKit_AlertControllerButtonActionBlock)block{ 51 | [UIAlertController ba_actionSheetShowInViewController:viewController 52 | title:title 53 | message:message 54 | buttonTitleArray:buttonTitleArray 55 | buttonTitleColorArray:buttonTitleColorArray 56 | popoverPresentationControllerBlock:popoverPresentationControllerBlock 57 | block:block]; 58 | 59 | } 60 | 61 | 62 | @end 63 | 64 | NS_ASSUME_NONNULL_END 65 | -------------------------------------------------------------------------------- /PAWebView/PAWebViewTool/WKWebView+LongPress.h: -------------------------------------------------------------------------------- 1 | // 2 | // WKWebView+LongPress.h 3 | // Pkit 4 | // 5 | // Created by llyouss on 2018/2/7. 6 | // Copyright © 2018年 llyouss. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface WKWebView (LongPress) 12 | 13 | /** 14 | 添加长按手势 15 | */ 16 | - (void)addGestureRecognizerObserverWebElements:(void(^)(BOOL longpress))Event; 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /PAWebView/PAWebViewTool/WKWebView+LongPress.m: -------------------------------------------------------------------------------- 1 | // 2 | // WKWebView+LongPress.m 3 | // Pkit 4 | // 5 | // Created by llyouss on 2018/2/7. 6 | // Copyright © 2018年 llyouss. All rights reserved. 7 | // 8 | 9 | #import "WKWebView+LongPress.h" 10 | #import 11 | #import "UIAlertController+WKWebAlert.h" 12 | #import "PAPhotoBrowser.h" 13 | #import 14 | 15 | FOUNDATION_EXPORT NSString* const NotiName_LoadRequest; 16 | FOUNDATION_EXPORT NSString* const Key_LoadQRCodeUrl; 17 | FOUNDATION_EXPORT NSString* const JSSearchHrefFromHtml; //抓取链接的JS方法 18 | FOUNDATION_EXPORT NSString* const JSSearchImageFromHtml; //抓取图片 19 | FOUNDATION_EXPORT NSString* const JSSearchAllImageFromHtml; 20 | 21 | FOUNDATION_EXPORT NSString* const JSFunctionAddEventCanal; //添加忽略事件响应 22 | FOUNDATION_EXPORT NSString* const JSFunctionRemoveEventCanal; //移除添加的事件 23 | FOUNDATION_EXPORT NSString* const JSFunctionEventIgnore; //忽略事件响应的方法 24 | 25 | typedef void(^LONGPRESS)(BOOL longpress); 26 | static LONGPRESS longPress = nil; 27 | 28 | @implementation WKWebView (LongPress) 29 | 30 | CGPoint touchPoint; 31 | 32 | /** 33 | 添加长按手势 34 | */ 35 | - (void)addGestureRecognizerObserverWebElements:(void(^)(BOOL longpress))Event; 36 | { 37 | longPress = (LONGPRESS)Event; 38 | //长按识别图中的二维码,类似于微信里面的功能,前提是当前页面必须有二维码 39 | UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(startLongPress:)]; 40 | longPress.delegate = self; 41 | 42 | longPress.minimumPressDuration = 0.4f; 43 | longPress.numberOfTouchesRequired = 1; 44 | longPress.cancelsTouchesInView = YES; 45 | [self addGestureRecognizer:longPress]; 46 | } 47 | 48 | /** 手势精确识别,防止误操作 */ 49 | - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer { 50 | NSLog(@"%@",otherGestureRecognizer.class); 51 | 52 | if ([otherGestureRecognizer isKindOfClass:[UILongPressGestureRecognizer class]]) { 53 | 54 | return YES; 55 | }else{ 56 | return NO; 57 | } 58 | } 59 | 60 | /** 长按识别代理 */ 61 | - (void)startLongPress:(UILongPressGestureRecognizer *)pressSender 62 | { 63 | if(pressSender.state == UIGestureRecognizerStateBegan){ 64 | 65 | //获取位置 66 | touchPoint = [pressSender locationInView:self]; 67 | //识别/抓取html元素 68 | [self detectQRCodeInWebView:pressSender]; 69 | NSLog(@"1. 开始长按手势"); 70 | 71 | }else if(pressSender.state == UIGestureRecognizerStateEnded){ 72 | 73 | //可以添加你长按手势执行的方法,不过是在手指松开后执行 74 | NSLog(@"2. 结束长按手势"); 75 | 76 | }else if(pressSender.state == UIGestureRecognizerStateChanged){ 77 | 78 | //在手指点下去一直不松开的状态执行 79 | NSLog(@"3. 长按手势改变"); 80 | } 81 | } 82 | 83 | #pragma mark - 84 | 85 | #pragma mark - 响应间隔禁止 86 | 87 | -(void)userInteractionDisableWithTime:(double)interval { 88 | if(time <= 0 && !self.userInteractionEnabled) { 89 | return; 90 | } 91 | 92 | self.userInteractionEnabled = NO; 93 | __weak typeof(self)weekSelf = self; 94 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(interval * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 95 | weekSelf.userInteractionEnabled = YES; 96 | }); 97 | } 98 | 99 | #pragma mark - 100 | 101 | #pragma mark - JS注入 102 | 103 | /** 抓取链接 */ 104 | - (void)hrefFromJSPointX:(float)x ppintY:(float)y callBack:(void(^)(NSString *hre))callback 105 | { 106 | //注入JS方法 107 | NSString *hrefJS = JSSearchHrefFromHtml; 108 | [self evaluateJavaScript:hrefJS completionHandler:nil]; 109 | 110 | //调用JS方法 111 | NSString *hrefFunc = [NSString stringWithFormat:@"JSSearchHref(%f,%f);",x,y]; 112 | [self evaluateJavaScript:hrefFunc completionHandler:^(id _Nullable href, NSError * _Nullable error) 113 | { 114 | callback ? callback(href) : NULL; 115 | }]; 116 | } 117 | 118 | /** 抓取所有图片 */ 119 | - (void)showAllImageFromHtmIndex:(NSString *)ImageUrlString 120 | { 121 | //注入JS方法 122 | NSString *hrefJS = JSSearchAllImageFromHtml; 123 | [self evaluateJavaScript:hrefJS completionHandler:nil]; 124 | 125 | //调用JS方法 126 | NSString *hrefFunc = [NSString stringWithFormat:@"JSSearchAllImage();"]; 127 | [self evaluateJavaScript:hrefFunc completionHandler:^(id _Nullable image, NSError * _Nullable error) 128 | { 129 | NSArray *imageArray = [self sortImageFromArray:image]; 130 | NSLog(@"%@",imageArray); 131 | 132 | 133 | #pragma clang diagnostic push 134 | #pragma clang diagnostic ignored "-Wundeclared-selector" 135 | 136 | Class cls = NSClassFromString(@"PAPhotoBrowser"); 137 | 138 | id photos; 139 | SEL fun = NSSelectorFromString(@"shareInstance"); 140 | if ([cls respondsToSelector:fun]) { 141 | photos = [cls performSelector:fun]; 142 | }else{ 143 | NSLog(@"请使用PAPhotoBrowser扩展!"); 144 | return ; 145 | } 146 | 147 | [photos setValue:imageArray forKey:@"photos"]; 148 | 149 | NSInteger index = [imageArray indexOfObject:ImageUrlString]; 150 | if (index == NSNotFound){ 151 | 152 | NSMutableArray *array = [photos objectForKey:@"photos"]; 153 | [array insertObject:ImageUrlString atIndex:0]; 154 | 155 | ((void (*)(id, SEL, NSInteger)) 156 | objc_msgSend)(photos, 157 | @selector(loadPhotoBrowserShowIndex:), 158 | 0 159 | ); 160 | 161 | } 162 | else { 163 | ((void (*)(id, SEL, NSInteger)) 164 | objc_msgSend)(photos, 165 | @selector(loadPhotoBrowserShowIndex:), 166 | index 167 | ); 168 | } 169 | 170 | #pragma clang diagnostic pop 171 | 172 | 173 | }]; 174 | } 175 | 176 | 177 | /** 过滤从网页中抓取的图片 */ 178 | - (NSArray *)sortImageFromArray:(NSArray *)array 179 | { 180 | NSMutableArray *imageArray = [NSMutableArray array]; 181 | for (NSString *urlString in array) { 182 | 183 | if (!urlString || [urlString isEqual:[NSNull null]] || [urlString isKindOfClass:[NSNull class]] ) continue ; 184 | NSString *lowString = urlString.lowercaseString; 185 | if ([lowString hasPrefix:@"http"] 186 | &&([lowString.lowercaseString containsString:@".jpg"] || 187 | [lowString.lowercaseString containsString:@".jpeg"]|| 188 | [lowString.lowercaseString containsString:@".png"] || 189 | [lowString.lowercaseString containsString:@".gif"])) { 190 | 191 | [imageArray addObject:urlString]; 192 | } 193 | } 194 | return (NSArray *)imageArray; 195 | } 196 | 197 | /** 注入忽略event的方法 */ 198 | - (void)addJSFuntionIgnoreEvent 199 | { 200 | //注入JS方法 201 | NSString *hrefJS = JSFunctionEventIgnore; 202 | [self evaluateJavaScript:hrefJS completionHandler:nil]; 203 | } 204 | 205 | /** 注入添加忽略事件 */ 206 | - (void)addJSFunctionAddEventCanalWithObject:(CGPoint)point 207 | { 208 | //注入JS方法 209 | NSString *hrefJS = JSFunctionAddEventCanal; 210 | [self evaluateJavaScript:hrefJS completionHandler:nil]; 211 | 212 | //调用JS方法 213 | NSString *hrefFunc = [NSString stringWithFormat:@"JSAddEventCanal(%lf,%lf)",point.x,point.y]; 214 | [self evaluateJavaScript:hrefFunc completionHandler:^(id _Nullable image, NSError * _Nullable error) 215 | { 216 | NSLog(@"%@",image); 217 | }]; 218 | } 219 | 220 | /** 注入添加忽略事件 */ 221 | - (void)addJSFunctionRemoveEventCanalWithObject:(CGPoint)point 222 | { 223 | //注入JS方法 224 | NSString *hrefJS = JSFunctionRemoveEventCanal; 225 | [self evaluateJavaScript:hrefJS completionHandler:nil]; 226 | 227 | //调用JS方法 228 | NSString *hrefFunc = [NSString stringWithFormat:@"JSRemoveEventCanal(%lf,%lf)",point.x,point.y]; 229 | [self evaluateJavaScript:hrefFunc completionHandler:^(id _Nullable image, NSError * _Nullable error) 230 | { 231 | NSLog(@"%@",image); 232 | }]; 233 | } 234 | 235 | #pragma mark - 236 | 237 | #pragma mark - 检测获取的网页元素 238 | 239 | /** 检测图片 */ 240 | - (void)detectQRCodeInWebView:(UILongPressGestureRecognizer *)pressSender 241 | { 242 | //获取长按位置对应的图片url的JS代码 243 | NSString *imgJS = [NSString stringWithFormat:@"document.elementFromPoint(%f, %f).src", touchPoint.x, touchPoint.y]; 244 | NSString *titleJS = [NSString stringWithFormat:@"document.elementFromPoint(%f, %f).innerText", touchPoint.x, touchPoint.y]; 245 | 246 | //判断是否是标题还是文章 247 | NSString * typeJS = [NSString stringWithFormat:@"document.elementFromPoint(%f, %f).tagName", touchPoint.x, touchPoint.y]; 248 | 249 | // 执行对应的JS代码 获取url 250 | __block NSString *imgUrlString; 251 | __weak typeof(self)weakSelf = self; 252 | //抓取image 253 | [self evaluateJavaScript:imgJS completionHandler:^(id _Nullable imgUrl, NSError * _Nullable error) 254 | { 255 | imgUrlString = imgUrl; 256 | 257 | //抓取title 258 | [weakSelf evaluateJavaScript:titleJS completionHandler:^(id _Nullable title, NSError * _Nullable error) 259 | { 260 | //抓取title的类型 261 | [weakSelf evaluateJavaScript:typeJS completionHandler:^(id _Nullable t, NSError * _Nullable error) { 262 | 263 | //抓取href 264 | [weakSelf hrefFromJSPointX:touchPoint.x ppintY:touchPoint.y callBack:^(NSString *hre) { 265 | 266 | [weakSelf showActionWithImage:imgUrlString href:hre title:title type:t]; 267 | }]; 268 | }]; 269 | }]; 270 | }]; 271 | } 272 | 273 | /** 弹出检测出来的信息 */ 274 | - (void)showActionWithImage:(NSString *)imageUrl href:(NSString *)href title:(NSString *)title type:(NSString *)t 275 | { 276 | NSString *type = t; 277 | NSString *innerTitle = title; 278 | NSString *imgUrlString = imageUrl; 279 | if ((!imageUrl || [imageUrl isEqualToString:@""] || 280 | !([imgUrlString.lowercaseString containsString:@".jpg"] || 281 | [imgUrlString.lowercaseString containsString:@".jpeg"]|| 282 | [imgUrlString.lowercaseString containsString:@".png"] || 283 | [imgUrlString.lowercaseString containsString:@".gif"])) 284 | && (innerTitle.length <= 0)) 285 | { 286 | return; 287 | } 288 | 289 | //长按操作 290 | UIAlertController *showActionTip = 291 | [UIAlertController alertControllerWithTitle:nil 292 | message:nil 293 | preferredStyle:UIAlertControllerStyleActionSheet]; 294 | 295 | //识别二维码 296 | NSURL *url = [self detectQRCode:self]; 297 | 298 | #pragma mark - 299 | #pragma mark - 创建按钮 300 | __weak typeof(self)weakSelf = self; 301 | UIAlertAction *ActionSacn = [UIAlertAction actionWithTitle:@"识别二维码" 302 | style:UIAlertActionStyleDefault 303 | handler:^(UIAlertAction * _Nonnull action) 304 | { 305 | longPress ? longPress(NO) : NULL; 306 | //返回二维码信息 307 | [[NSNotificationCenter defaultCenter] postNotificationName:NotiName_LoadRequest object:nil userInfo:@{Key_LoadQRCodeUrl:url.absoluteString}]; 308 | }]; 309 | 310 | 311 | //看图模式 312 | UIAlertAction *ActionIntoImageMode = [UIAlertAction actionWithTitle:@"进入看图模式" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { 313 | longPress ? longPress(NO) : NULL; 314 | [weakSelf showAllImageFromHtmIndex:imgUrlString]; 315 | }]; 316 | 317 | 318 | //下载图片 319 | UIAlertAction *ActionloadImage = [UIAlertAction actionWithTitle:@"保存图片" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { 320 | longPress ? longPress(NO) : NULL; 321 | [weakSelf LoadImageFromURL:[NSURL URLWithString:imgUrlString]]; 322 | }]; 323 | 324 | //复制图片地址 325 | UIAlertAction *ActionCopyImageLink = [UIAlertAction actionWithTitle:@"复制图片地址" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { 326 | longPress ? longPress(NO) : NULL; 327 | [UIPasteboard generalPasteboard].string = imgUrlString; 328 | 329 | }]; 330 | 331 | //分享图片 332 | UIAlertAction *ActionShareImage = [UIAlertAction actionWithTitle:@"分享图片" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { 333 | longPress ? longPress(NO) : NULL; 334 | }]; 335 | 336 | //复制标题 337 | UIAlertAction *ActionCopyInnerTitle = [UIAlertAction actionWithTitle:@"复制链接文字" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { 338 | 339 | longPress ? longPress(NO) : NULL; 340 | [UIPasteboard generalPasteboard].string = innerTitle; 341 | }]; 342 | 343 | //复制链接地址 344 | UIAlertAction *ActionCopyHref = [UIAlertAction actionWithTitle:@"复制链接地址" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { 345 | 346 | longPress ? longPress(NO) : NULL; 347 | [UIPasteboard generalPasteboard].string = href; 348 | }]; 349 | 350 | 351 | //取消 352 | UIAlertAction *Canel = [UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) { 353 | 354 | longPress ? longPress(NO) : NULL; 355 | }]; 356 | 357 | 358 | #pragma mark - 359 | #pragma mark - 显示按钮 360 | 361 | //添加图片相关的操作 362 | if ([imgUrlString.lowercaseString hasSuffix:@"jpg"] || 363 | [imgUrlString.lowercaseString hasSuffix:@"jpeg"]|| 364 | [imgUrlString.lowercaseString hasSuffix:@"png"] || 365 | [imgUrlString.lowercaseString hasSuffix:@"gif"]) 366 | { 367 | [showActionTip addAction:ActionIntoImageMode]; 368 | [showActionTip addAction:ActionloadImage]; 369 | [showActionTip addAction:ActionShareImage]; 370 | if (url) { 371 | [showActionTip addAction:ActionSacn]; 372 | } 373 | [showActionTip addAction:ActionCopyImageLink]; 374 | } 375 | 376 | //添加链接复制操作 377 | if (href) { 378 | [showActionTip addAction:ActionCopyHref]; 379 | } 380 | 381 | //添加标题相关的操作 382 | if (innerTitle.length > 0 && [type.lowercaseString containsString:@"h"]) { 383 | [showActionTip addAction:ActionCopyInnerTitle]; 384 | } 385 | 386 | //添加取消操作 387 | [showActionTip addAction:Canel]; 388 | if (showActionTip.actions.count == 1) { 389 | return; 390 | } 391 | 392 | //响应长长按 393 | longPress ? longPress(YES) : NULL; 394 | dispatch_async(dispatch_get_main_queue(), ^{ 395 | [[UIApplication sharedApplication].keyWindow.rootViewController presentViewController:showActionTip animated:YES completion:nil]; 396 | }); 397 | 398 | } 399 | 400 | /** 识别二维码 */ 401 | - (NSURL *)detectQRCode:(UIView *)fview 402 | { 403 | //截图 再读取 404 | UIGraphicsBeginImageContextWithOptions(self.bounds.size, YES, 0); 405 | CGContextRef context = UIGraphicsGetCurrentContext(); 406 | [fview.layer renderInContext:context]; 407 | UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); 408 | UIGraphicsEndImageContext(); 409 | CIImage *ciImage = [[CIImage alloc] initWithCGImage:image.CGImage options:nil]; 410 | 411 | //渲染 412 | CIContext *ciContext = [CIContext contextWithOptions:@{kCIContextUseSoftwareRenderer : @(YES)}]; 413 | CIDetector *detector = [CIDetector detectorOfType:CIDetectorTypeQRCode context:ciContext options:@{CIDetectorAccuracy : CIDetectorAccuracyHigh}];// 二维码识别 414 | NSArray *features = [detector featuresInImage:ciImage]; 415 | 416 | for (CIQRCodeFeature *feature in features) { 417 | 418 | NSURL * url = [NSURL URLWithString: feature.messageString]; 419 | if (url) return url; 420 | } 421 | return nil; 422 | } 423 | 424 | /** 下载图片 */ 425 | - (void)LoadImageFromURL:(NSURL *)URLString 426 | { 427 | NSData *data = [NSData dataWithContentsOfURL:URLString]; 428 | UIImage *image = [UIImage imageWithData:data]; 429 | if (!image) { 430 | [UIAlertController PAlertWithTitle:@"提示" message:@"下载失败" completion:nil]; 431 | return; 432 | } 433 | [self saveImageFinished:image]; 434 | } 435 | 436 | /** 保存图片到相册 */ 437 | - (void)saveImageFinished:(UIImage *)image 438 | { 439 | if (!image) { 440 | [UIAlertController PAlertWithTitle:@"提示" message:@"下载失败" completion:nil]; 441 | return; 442 | } 443 | [[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{ 444 | 445 | //写入图片到相册 446 | [PHAssetChangeRequest creationRequestForAssetFromImage:image]; 447 | 448 | } completionHandler:^(BOOL success, NSError * _Nullable error) { 449 | if (success){ 450 | 451 | [UIAlertController PAlertWithTitle:@"提示" message:@"已经保存到相册" completion:nil]; 452 | }else{ 453 | 454 | [UIAlertController PAlertWithTitle:@"提示" message:@"保存失败" completion:nil]; 455 | } 456 | }]; 457 | } 458 | 459 | 460 | @end 461 | -------------------------------------------------------------------------------- /PAWebView/PAWebViewTool/WKWebView+POST.h: -------------------------------------------------------------------------------- 1 | // 2 | // WKWebView+POST.h 3 | // Pkit 4 | // 5 | // Created by llyouss on 2018/1/9. 6 | // Copyright © 2018年 llyouss. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface WKWebView (POST) 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /PAWebView/PAWebViewTool/WKWebView+POST.m: -------------------------------------------------------------------------------- 1 | // 2 | // WKWebView+POST.m 3 | // Pkit 4 | // 5 | // Created by llyouss on 2018/1/9. 6 | // Copyright © 2018年 llyouss. All rights reserved. 7 | // 8 | 9 | #import "WKWebView+POST.h" 10 | #import 11 | 12 | @implementation WKWebView (POST) 13 | 14 | + (void)load 15 | { 16 | static dispatch_once_t onceToken; 17 | dispatch_once(&onceToken, ^{ 18 | Class class = [self class]; 19 | SEL originalSelector = @selector(loadRequest:); 20 | SEL swizzledSelector = @selector(post_loadRequest:); 21 | Method originalMethod = class_getInstanceMethod(class, originalSelector); 22 | Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector); 23 | BOOL success = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod)); 24 | if (success) { 25 | class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod)); 26 | } else { 27 | method_exchangeImplementations(originalMethod, swizzledMethod); 28 | } 29 | }); 30 | } 31 | 32 | - (WKNavigation *)post_loadRequest:(NSURLRequest *)request 33 | { 34 | NSLog(@"%@",[request.HTTPMethod uppercaseString]); 35 | if ([[request.HTTPMethod uppercaseString] isEqualToString:@"POST"]){ 36 | NSString *url = request.URL.absoluteString; 37 | NSString *params = [[NSString alloc] initWithData:request.HTTPBody encoding:NSUTF8StringEncoding]; 38 | if ([params containsString:@"="]) { 39 | params = [params stringByReplacingOccurrencesOfString:@"=" withString:@"\":\""]; 40 | params = [params stringByReplacingOccurrencesOfString:@"&" withString:@"\",\""]; 41 | params = [NSString stringWithFormat:@"{\"%@\"}", params]; 42 | }else{ 43 | params = @"{}"; 44 | } 45 | NSString *postJavaScript = [NSString stringWithFormat:@"\ 46 | var url = '%@';\ 47 | var params = %@;\ 48 | var form = document.createElement('form');\ 49 | form.setAttribute('method', 'post');\ 50 | form.setAttribute('action', url);\ 51 | for(var key in params) {\ 52 | if(params.hasOwnProperty(key)) {\ 53 | var hiddenField = document.createElement('input');\ 54 | hiddenField.setAttribute('type', 'hidden');\ 55 | hiddenField.setAttribute('name', key);\ 56 | hiddenField.setAttribute('value', params[key]);\ 57 | form.appendChild(hiddenField);\ 58 | }\ 59 | }\ 60 | document.body.appendChild(form);\ 61 | form.submit();", url, params]; 62 | 63 | __weak typeof(self) wself = self; 64 | [self evaluateJavaScript:postJavaScript completionHandler:^(id object, NSError * _Nullable error) { 65 | if (error && [wself.navigationDelegate respondsToSelector:@selector(webView:didFailProvisionalNavigation:withError:)]) { 66 | dispatch_async(dispatch_get_main_queue(), ^{ 67 | [wself.navigationDelegate webView:wself didFailProvisionalNavigation:nil withError:error]; 68 | }); 69 | } 70 | }]; 71 | return nil; 72 | }else{ 73 | return [self post_loadRequest:request]; 74 | } 75 | } 76 | 77 | @end 78 | -------------------------------------------------------------------------------- /PAWebView/PAWebViewTool/registerURLSchemes.h: -------------------------------------------------------------------------------- 1 | // 2 | // registerURLSchemes.h 3 | // Pkit 4 | // 5 | // Created by llyouss on 2017/12/25. 6 | // Copyright © 2017年 llyouss. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class urlschemeModel; 12 | 13 | @interface registerURLSchemes : NSObject 14 | 15 | //目前当app跨域请求时,app提示打开的 urlschemes,该类用于映射 urlschemes 和应用信息。 16 | 17 | /** 18 | 存储URLSchemes主要用于识别urlschemes的来源名字 19 | 20 | @params URLSchemes 列表 21 | */ 22 | 23 | + (void)registerURLSchemes:(NSDictionary *)URLSchemes; 24 | + (void)registerURLSchemeModel:(NSArray*)URLScheme; 25 | 26 | /** 27 | 需要注册的URLSchemes数据,需要时添加 28 | 29 | @return urlschemes 信息 30 | */ 31 | + (NSDictionary *)urlschemes; 32 | 33 | @end 34 | -------------------------------------------------------------------------------- /PAWebView/PAWebViewTool/registerURLSchemes.m: -------------------------------------------------------------------------------- 1 | // 2 | // registerURLSchemes.m 3 | // Pkit 4 | // 5 | // Created by llyouss on 2017/12/25. 6 | // Copyright © 2017年 llyouss. All rights reserved. 7 | // 8 | 9 | #import "registerURLSchemes.h" 10 | #import "urlschemeModel.h" 11 | 12 | @implementation registerURLSchemes 13 | 14 | + (NSString *)urlSchemesPath 15 | { 16 | NSString *path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject]; 17 | return path = [path stringByAppendingPathComponent:@"appURLSchemes"]; 18 | } 19 | 20 | + (void)registerURLSchemes:(NSDictionary *)URLSchemes 21 | { 22 | NSString *path = [self urlSchemesPath]; 23 | [URLSchemes writeToFile:path atomically:YES]; 24 | } 25 | 26 | + (void)registerURLSchemeModel:(NSArray *)URLScheme 27 | { 28 | NSMutableDictionary *contantDic = [NSMutableDictionary dictionary]; 29 | 30 | for (urlschemeModel *model in URLScheme) { 31 | 32 | NSMutableDictionary *subDic = [NSMutableDictionary dictionary]; 33 | [subDic setValue:model.appstoreURL forKey:@"url"]; 34 | [subDic setValue:model.appid forKey:@"id"]; 35 | [subDic setValue:model.displayName forKey:@"name"]; 36 | 37 | [contantDic setObject:subDic forKey:model.scheme]; 38 | } 39 | 40 | [self registerURLSchemes:contantDic]; 41 | } 42 | 43 | + (NSDictionary *)urlschemes 44 | { 45 | /** 把该信息注册到 info plist 列表 */ 46 | NSDictionary * mainUrlschemes = @{ 47 | @"weixin":@{ 48 | @"url":@"https://itunes.apple.com/cn/app/id414478124?mt=8", 49 | @"id":@"414478124", 50 | @"name":@"微信", 51 | }, 52 | @"wechat":@{ 53 | @"url":@"https://itunes.apple.com/cn/app/id414478124?mt=8", 54 | @"id":@"414478124", 55 | @"name":@"微信", 56 | }, 57 | @"alipay":@{ 58 | @"url":@"https://itunes.apple.com/cn/app/id333206289?mt=8", 59 | @"id":@"333206289", 60 | @"name":@"支付宝", 61 | }, 62 | @"alipayshare":@{ 63 | @"url":@"https://itunes.apple.com/cn/app/id333206289?mt=8", 64 | @"id":@"333206289", 65 | @"name":@"支付宝", 66 | }, 67 | @"taobao":@{ 68 | @"url":@"https://itunes.apple.com/cn/app/id387682726?mt=8", 69 | @"id":@"387682726", 70 | @"name":@"淘宝", 71 | }, 72 | @"mqq":@{ 73 | @"url":@"https://itunes.apple.com/cn/app/id444934666?mt=8", 74 | @"id":@"444934666", 75 | @"name":@"QQ", 76 | }, 77 | @"mqqapi":@{ 78 | @"url":@"https://itunes.apple.com/cn/app/id444934666?mt=8", 79 | @"id":@"444934666", 80 | @"name":@"QQ", 81 | }, 82 | @"mqzone":@{ 83 | @"url":@"https://itunes.apple.com/cn/app/id444934666?mt=8", 84 | @"id":@"444934666", 85 | @"name":@"QQ", 86 | }, 87 | @"mqqwpa":@{ 88 | @"url":@"https://itunes.apple.com/cn/app/id444934666?mt=8", 89 | @"id":@"444934666", 90 | @"name":@"QQ", 91 | }, 92 | @"mqqapi":@{ 93 | @"url":@"https://itunes.apple.com/cn/app/id444934666?mt=8", 94 | @"id":@"444934666", 95 | @"name":@"QQ", 96 | }, 97 | @"BaiduSSO":@{ 98 | @"url":@"https://itunes.apple.com/cn/app/id382201985?mt=8", 99 | @"id":@"382201985", 100 | @"name":@"百度", 101 | }, 102 | @"ucbrowser":@{ 103 | @"url":@"https://itunes.apple.com/cn/app/id527109739?mt=8", 104 | @"id":@"527109739", 105 | @"name":@"UC浏览器", 106 | }, 107 | @"bdmap":@{ 108 | @"url":@"https://itunes.apple.com/cn/app/id452186370?mt=8", 109 | @"id":@"452186370", 110 | @"name":@"百度地图", 111 | }, 112 | @"snssdk141":@{ 113 | @"url":@"https://itunes.apple.com/cn/app/id529092160?mt=8", 114 | @"id":@"529092160", 115 | @"name":@"今日头条", 116 | }, 117 | @"imeituan":@{ 118 | @"url":@"https://itunes.apple.com/cn/app/id423084029?mt=8", 119 | @"id":@"414245413", 120 | @"name":@"美团", 121 | }, 122 | @"openapp.jdmoble":@{ 123 | @"url":@"https://itunes.apple.com/cn/app/id414245413?mt=8", 124 | @"id":@"414245413", 125 | @"name":@"京东", 126 | }, 127 | @"VSSpecialSwitch":@{ 128 | @"url":@"https://itunes.apple.com/cn/app/id417200582?mt=8", 129 | @"id":@"417200582", 130 | @"name":@"唯品会", 131 | }, 132 | @"dianping":@{ 133 | @"url":@"https://itunes.apple.com/cn/app/id351091731?mt=8", 134 | @"id":@"351091731", 135 | @"name":@"大众点评", 136 | }, 137 | @"sinaweibo":@{ 138 | @"url":@"https://itunes.apple.com/cn/app/id350962117?mt=8", 139 | @"id":@"350962117", 140 | @"name":@"微博", 141 | }, 142 | @"weibosdk2.5":@{ 143 | @"url":@"https://itunes.apple.com/cn/app/id350962117?mt=8", 144 | @"id":@"350962117", 145 | @"name":@"微博", 146 | }, 147 | @"weibosdk":@{ 148 | @"url":@"https://itunes.apple.com/cn/app/id350962117?mt=8", 149 | @"id":@"350962117", 150 | @"name":@"微博", 151 | }, 152 | @"sinaweibosso":@{ 153 | @"url":@"https://itunes.apple.com/cn/app/id350962117?mt=8", 154 | @"id":@"350962117", 155 | @"name":@"微博", 156 | }, 157 | @"sinaweibohd":@{ 158 | @"url":@"https://itunes.apple.com/cn/app/id350962117?mt=8", 159 | @"id":@"350962117", 160 | @"name":@"微博", 161 | }, 162 | @"diditaxi":@{ 163 | @"url":@"https://itunes.apple.com/cn/app/id554499054?mt=8", 164 | @"id":@"554499054", 165 | @"name":@"滴滴打车", 166 | }, 167 | @"kugouURL":@{ 168 | @"url":@"https://itunes.apple.com/cn/app/id472208016?mt=8", 169 | @"id":@"472208016", 170 | @"name":@"酷狗音乐", 171 | }, 172 | @"qqmusic":@{ 173 | @"url":@"https://itunes.apple.com/cn/app/id472208016?mt=8", 174 | @"id":@"472208016", 175 | @"name":@"酷狗音乐", 176 | }, 177 | @"zhihu":@{ 178 | @"url":@"https://itunes.apple.com/cn/app/%E7%9F%A5%E4%B9%8E-%E5%8F%91%E7%8E%B0%E6%9B%B4%E5%A4%A7%E7%9A%84%E4%B8%96%E7%95%8C/id432274380?mt=8", 179 | @"id":@"432274380", 180 | @"name":@"知乎", 181 | }, 182 | }; 183 | 184 | NSMutableDictionary *urlschemesDic = [NSMutableDictionary dictionaryWithDictionary:mainUrlschemes]; 185 | NSString *path = [self urlSchemesPath]; 186 | NSDictionary *registerURLSchemes = [NSDictionary dictionaryWithContentsOfFile:path]; 187 | [urlschemesDic addEntriesFromDictionary:registerURLSchemes]; 188 | 189 | return urlschemesDic; 190 | } 191 | 192 | /* 193 | 194 | 添加到plist列表 195 | 196 | LSApplicationQueriesSchemes 197 | 198 | 199 | wechat 200 | weixin 201 | 202 | 203 | sinaweibohd 204 | sinaweibo 205 | sinaweibosso 206 | weibosdk 207 | weibosdk2.5 208 | 209 | 210 | mqqapi 211 | mqq 212 | mqqOpensdkSSoLogin 213 | mqqconnect 214 | mqqopensdkdataline 215 | mqqopensdkgrouptribeshare 216 | mqqopensdkfriend 217 | mqqopensdkapi 218 | mqqopensdkapiV2 219 | mqqopensdkapiV3 220 | mqzoneopensdk 221 | wtloginmqq 222 | wtloginmqq2 223 | mqqwpa 224 | mqzone 225 | mqzonev2 226 | mqzoneshare 227 | wtloginqzone 228 | mqzonewx 229 | mqzoneopensdkapiV2 230 | mqzoneopensdkapi19 231 | mqzoneopensdkapi 232 | mqzoneopensdk 233 | 234 | 235 | alipay 236 | alipayshare 237 | taobao 238 | 239 | 240 | imeituan 241 | 242 | 243 | BaiduSSO 244 | bdmap 245 | 246 | 247 | ucbrowser 248 | 249 | 250 | snssdk141 251 | 252 | 253 | openapp.jdmoble 254 | 255 | 256 | diditaxi 257 | 258 | 259 | kugouURL 260 | 261 | 262 | qqmusic 263 | 264 | 265 | zhihu 266 | 267 | 268 | 269 | */ 270 | 271 | @end 272 | -------------------------------------------------------------------------------- /PAWebView/PAWebViewTool/urlschemeModel.h: -------------------------------------------------------------------------------- 1 | // 2 | // urlschemeModel.h 3 | // Pkit 4 | // 5 | // Created by llyouss on 2017/12/27. 6 | // Copyright © 2017年 llyouss. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface urlschemeModel : NSObject 12 | 13 | @property (nonatomic,copy) NSString *scheme; 14 | @property (nonatomic,copy) NSString *appstoreURL; 15 | @property (nonatomic,copy) NSString *appid; 16 | @property (nonatomic,copy) NSString *displayName; 17 | 18 | @end 19 | 20 | 21 | -------------------------------------------------------------------------------- /PAWebView/PAWebViewTool/urlschemeModel.m: -------------------------------------------------------------------------------- 1 | // 2 | // urlschemeModel.m 3 | // Pkit 4 | // 5 | // Created by llyouss on 2017/12/27. 6 | // Copyright © 2017年 llyouss. All rights reserved. 7 | // 8 | 9 | #import "urlschemeModel.h" 10 | 11 | @implementation urlschemeModel 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /PAWebView/WKJSFunction.m: -------------------------------------------------------------------------------- 1 | // 2 | // WKJSFunction.m 3 | // Pkit 4 | // 5 | // Created by llyouss on 2017/12/21. 6 | // Copyright © 2017年 llyouss. All rights reserved. 7 | // 8 | 9 | 10 | #import 11 | 12 | 13 | #pragma mark --- 14 | 15 | #pragma mark --- JS 方法 16 | 17 | /** 获取网页body里面的HTML*/ 18 | NSString* const JSGetHTMLFromBody = 19 | @"document.body.innerHTML"; 20 | 21 | /** 获取网页body里面的HTML*/ 22 | NSString* const JSGetHTMLById = 23 | @"function JSGetHTMLById(id) {\ 24 | return document.getElementById(id).innerHTML;\ 25 | }"; 26 | 27 | /** 获取链接的js方法 */ 28 | NSString* const JSSearchHrefFromHtml = 29 | @"function JSSearchHref(x,y) {\ 30 | var e = document.elementFromPoint(x, y);\ 31 | while(e){\ 32 | if(e.href){\ 33 | return e.href;\ 34 | }\ 35 | e = e.parentElement;\ 36 | }\ 37 | return e.href;\ 38 | }"; 39 | 40 | /** 抓取文本标题 */ 41 | NSString* const JSSearchTextTitleFromHtml = 42 | @"function JSSearchText(x,y) {" 43 | "return document.elementFromPoint(x, y).innerText;" 44 | "}"; 45 | 46 | /** 获取链接的js方法 */ 47 | NSString* const JSSearchTextTypeFromHtml = 48 | @"function JSSearchTextType(x,y) {" 49 | "return document.elementFromPoint(x, y).tagName;" 50 | "}"; 51 | 52 | /** 获取图片链接的js方法 */ 53 | NSString* const JSSearchImageFromHtml = 54 | @"function JSSearchImage(x,y) {" 55 | "return document.elementFromPoint(x, y).src;" 56 | "}"; 57 | 58 | /** 获取HTML所有的图片 */ 59 | NSString* const JSSearchAllImageFromHtml = 60 | @"function JSSearchAllImage(){" 61 | "var img = [];" 62 | "for(var i=0;i<$(\"img\").length;i++){" 63 | "if(parseInt($(\"img\").eq(i).css(\"width\"))> 60){ "//获取所有符合放大要求的图片,将图片路径(src)获取 64 | " img[i] = $(\"img\").eq(i).attr(\"src\");" 65 | " }" 66 | "}" 67 | "var img_info = {};" 68 | "img_info.list = img;" //保存所有图片的url 69 | "return img;" 70 | "}"; 71 | 72 | /** 获取web page宽 */ 73 | NSString* const JSGetWebPageWidth = 74 | @"document.getElementById('content').offsetWidth"; 75 | 76 | /** 获取web page高 */ 77 | NSString* const JSGetWebPageHeight = 78 | @"document.getElementById('content').offsetHeight"; 79 | 80 | 81 | /** 设置web page尺寸 */ 82 | NSString* const JSSetWebPageWidth = 83 | @"function JSSetWebPageWidth(wid) {\ 84 | document.querySelector('meta[name=viewport]').setAttribute('content',\ 85 | 'width=wid;', false); \ 86 | }"; 87 | 88 | /** 设置web page字体大小 */ 89 | NSString* const JSSetFontSize = 90 | @"function JSSetFontSize(size) {\ 91 | document.getElementsByTagName('body')[0].style.webkitTextSizeAdjust= size\ 92 | }"; 93 | 94 | /** 设置web page字体大小 */ 95 | NSString* const JSRemoveAllLink = 96 | @"$(document).ready(function () {$('a').removeAttr('href');})"; 97 | 98 | /** 替换所有的href */ 99 | NSString* const JSReplaceAllHref = 100 | @"function JSReplaceAllLinkWithUrlString(url) {\ 101 | $(document).ready(function () {$('a').setAttribute('href',url);})\ 102 | }"; 103 | 104 | /** 通过id替换元素的href */ 105 | NSString* const JSReplaceURLStringById = 106 | @"function JSSetURLString(id,url) {\ 107 | document.getElementById(id).setAttribute('href',url);\ 108 | }"; 109 | 110 | /** 通过手势位置替换元素的href */ 111 | NSString* const JSReplaceURLStringByLongPress = 112 | @"function JSSetURLStringByLongPress(x,y,url) {\ 113 | document.elementFromPoint(x, y).setAttribute('href',url);\ 114 | }"; 115 | 116 | /** 通过id替换元素的href */ 117 | NSString* const JSReplaceImageById = 118 | @"function JSReplaceImageById(id,url) {\ 119 | document.getElementById(id).src = url;\ 120 | }"; 121 | 122 | /** 通过手势位置替换图片 */ 123 | NSString* const JSReplaceImageByLongPress = 124 | @"function JSSetURLStringByLongPress(x,y,url) {\ 125 | document.elementFromPoint(x, y).setAttribute('src',url);\ 126 | }"; 127 | 128 | 129 | #pragma mark -- 130 | 131 | #pragma mark -- 点击事件相关操作 132 | 133 | 134 | /** 提交表单事件 */ 135 | NSString* const JSSubmitForms = 136 | @"document.forms[0].submit();"; 137 | 138 | /** 取消点击事件 */ 139 | NSString* const JSFunctionAddEventCanal = 140 | @"function JSAddEventCanal(x,y) {\ 141 | var e = document.elementFromPoint(x, y);\ 142 | var num = 0;\ 143 | while(e){\ 144 | if(num>5)return;\ 145 | num++;\ 146 | e.addEventListener('click',function(e) {\ 147 | if ( e && e.preventDefault )\ 148 | e.preventDefault();\ 149 | else\ 150 | window.event.returnValue = false;\ 151 | return false;\ 152 | },false);\ 153 | e = e.parentElement;\ 154 | }\ 155 | }"; 156 | 157 | /* 移除忽略事件 */ 158 | NSString* const JSFunctionRemoveEventCanal = 159 | @"function JSRemoveEventCanal(x,y) {\ 160 | var e = document.elementFromPoint(x, y);\ 161 | var num = 0;\ 162 | while(e){\ 163 | if(num > 5)return;\ 164 | num++;\ 165 | e.addEventListener('click',PAEventIgnore,false);\ 166 | e = e.parentElement;\ 167 | }\ 168 | }"; 169 | 170 | /* 忽略事件方法 */ 171 | NSString* const JSFunctionEventIgnore = 172 | @"function PAEventIgnore(e) {\ 173 | if ( e && e.preventDefault )\ 174 | e.preventDefault();\ 175 | else\ 176 | window.event.returnValue = false;\ 177 | return false;\ 178 | }\ 179 | }"; 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | -------------------------------------------------------------------------------- /PAWebViewDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /PAWebViewDemo.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /PAWebViewDemo.xcodeproj/xcuserdata/llyouss.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /PAWebViewDemo.xcodeproj/xcuserdata/llyouss.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | PAWebViewDemo.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | PAWebViewDemo.xcscheme_^#shared#^_ 13 | 14 | orderHint 15 | 0 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /PAWebViewDemo/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // PAWebViewDemo 4 | // 5 | // Created by llyouss on 2018/1/9. 6 | // Copyright © 2018年 llyouss. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | 16 | @end 17 | 18 | -------------------------------------------------------------------------------- /PAWebViewDemo/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // PAWebViewDemo 4 | // 5 | // Created by llyouss on 2018/1/9. 6 | // Copyright © 2018年 llyouss. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | #import "ViewController.h" 11 | 12 | @interface AppDelegate () 13 | 14 | @end 15 | 16 | @implementation AppDelegate 17 | 18 | 19 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 20 | // Override point for customization after application launch. 21 | 22 | self.window =[[UIWindow alloc]initWithFrame:[[UIScreen mainScreen]bounds]]; 23 | [UIApplication sharedApplication].statusBarStyle = UIStatusBarStyleLightContent; 24 | 25 | ViewController *root = [[ViewController alloc]init]; 26 | UINavigationController *mainNavc = [[UINavigationController alloc]initWithRootViewController:root]; 27 | self.window.rootViewController = mainNavc; 28 | [self.window makeKeyAndVisible]; 29 | 30 | return YES; 31 | } 32 | 33 | 34 | - (void)applicationWillResignActive:(UIApplication *)application { 35 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 36 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 37 | } 38 | 39 | 40 | - (void)applicationDidEnterBackground:(UIApplication *)application { 41 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 42 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 43 | } 44 | 45 | 46 | - (void)applicationWillEnterForeground:(UIApplication *)application { 47 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 48 | } 49 | 50 | 51 | - (void)applicationDidBecomeActive:(UIApplication *)application { 52 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 53 | } 54 | 55 | 56 | - (void)applicationWillTerminate:(UIApplication *)application { 57 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 58 | } 59 | 60 | 61 | @end 62 | -------------------------------------------------------------------------------- /PAWebViewDemo/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /PAWebViewDemo/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /PAWebViewDemo/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /PAWebViewDemo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleAllowMixedLocalizations 6 | 7 | CFBundleDevelopmentRegion 8 | $(DEVELOPMENT_LANGUAGE) 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | 1 23 | LSApplicationCategoryType 24 | 25 | LSApplicationQueriesSchemes 26 | 27 | 28 | 29 | LSRequiresIPhoneOS 30 | 31 | NSAppTransportSecurity 32 | 33 | NSAllowsArbitraryLoads 34 | 35 | 36 | NSCameraUsageDescription 37 | 使用相机 38 | NSLocationAlwaysUsageDescription 39 | 获取当前地理位置 40 | NSLocationWhenInUseUsageDescription 41 | 获取当前地理位置 42 | NSMicrophoneUsageDescription 43 | 使用麦克风 44 | NSPhotoLibraryUsageDescription 45 | 访问相册 46 | UILaunchStoryboardName 47 | LaunchScreen 48 | UIRequiredDeviceCapabilities 49 | 50 | armv7 51 | 52 | UIStatusBarStyle 53 | UIStatusBarStyleDefault 54 | UISupportedInterfaceOrientations 55 | 56 | UIInterfaceOrientationPortrait 57 | UIInterfaceOrientationLandscapeLeft 58 | UIInterfaceOrientationLandscapeRight 59 | 60 | UISupportedInterfaceOrientations~ipad 61 | 62 | UIInterfaceOrientationPortrait 63 | UIInterfaceOrientationPortraitUpsideDown 64 | UIInterfaceOrientationLandscapeLeft 65 | UIInterfaceOrientationLandscapeRight 66 | 67 | UIViewControllerBasedStatusBarAppearance 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /PAWebViewDemo/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // PAWebViewDemo 4 | // 5 | // Created by llyouss on 2018/1/9. 6 | // Copyright © 2018年 llyouss. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ViewController : UIViewController 12 | 13 | 14 | @end 15 | 16 | -------------------------------------------------------------------------------- /PAWebViewDemo/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // PAWebViewDemo 4 | // 5 | // Created by llyouss on 2018/1/9. 6 | // Copyright © 2018年 llyouss. All rights reserved. 7 | // 8 | 9 | #import "ViewController.h" 10 | #import "PAWebView.h" 11 | 12 | #define PAColor(r, g, b, a) [UIColor colorWithRed:(r)/255.0f green:(g)/255.0f blue:(b)/255.0f alpha:a] 13 | 14 | #define DEFAULT_BACKGROUND_COLOR PAColor(239.0, 239.0, 244.0, 1.0) //默认背景颜色 15 | #define DEFAULT_NAVBAR_COLOR PAColor(22.0, 129.0, 254.0, 1.0) //导航栏背景颜色 16 | 17 | typedef void (^runCaseBlock)(id); 18 | 19 | @interface ViewController () 20 | 21 | @property (nonatomic,strong) NSDictionary * runBlockDict; 22 | 23 | @end 24 | 25 | @implementation ViewController 26 | 27 | - (void)viewDidLoad { 28 | [super viewDidLoad]; 29 | 30 | [self.navigationController.navigationBar setBarTintColor:DEFAULT_NAVBAR_COLOR]; 31 | [self.navigationController.navigationBar setTintColor:[UIColor whiteColor]]; 32 | 33 | //初始化,单例 34 | PAWebView *webView = [PAWebView shareInstance]; 35 | 36 | // webView setcookie:<#(NSHTTPCookie *)#> //设置cookie 37 | // [webView WKSharedHTTPCookieStorage]; //获取所有缓存的cookies 38 | // [webView deleteAllWKCookies]; //删除所有缓存的cookies 39 | 40 | //添加与JS交互事件 41 | [self addMessageHandleName]; 42 | 43 | //加载网页 44 | [webView loadRequestURL:[NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"https://www.sina.cn"] cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:20.0f]]; 45 | // [webView loadLocalHTMLWithFileName:@"main"]; 46 | // webView loadLocalHTMLWithFileName:<#(NSString *)#> 加载本地网页 47 | // [webView reload]; //重新加载网页 48 | // [webView reloadFromOrigin]; //无视缓存,重新加载服务器最新的网页 49 | 50 | [self.navigationController pushViewController:webView animated:YES]; 51 | 52 | //调用js 53 | // [self performSelector:@selector(TESTCallJS) withObject:nil afterDelay:6]; 54 | 55 | // [webView goback]; //返回上一级 56 | // [webView goForward]; //返回下一级 57 | 58 | //二维码识别后返回的二维码数据 59 | [webView notificationInfoFromQRCode:^(NSString *info) { 60 | 61 | }]; 62 | 63 | // 清除缓存 64 | // [webView deleteAllWebCache]; 65 | 66 | } 67 | 68 | - (void)PAUserContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message{ 69 | NSLog(@"监听JS调用IC"); 70 | ((runCaseBlock)self.runBlockDict[message.name])(message.body); 71 | } 72 | 73 | #pragma mark - JS 调用 OC 74 | - (void)addMessageHandleName 75 | { 76 | PAWebView *webView = [PAWebView shareInstance]; 77 | // webView.messageHandlerdelegate =self; 78 | // [webView addScriptMessageHandlerWithName:@[@"AliPay",@"weixin",@"webViewApp"]]; 79 | 80 | __weak typeof(self)weekSelf = self; 81 | [webView addScriptMessageHandlerWithName:@[@"AliPay",@"weixin",@"webViewApp"] observeValue:^(WKUserContentController *userContentController, WKScriptMessage *message) { 82 | 83 | //JS调用OC处理 84 | __strong typeof(self)strongSelf = weekSelf; 85 | ((runCaseBlock)strongSelf.runBlockDict[message.name])(message.body); 86 | }]; 87 | } 88 | 89 | //JS调用OC处理事件 90 | #pragma mark runBlockDict 运行的代码块 91 | -(NSDictionary *)runBlockDict 92 | { 93 | if (_runBlockDict == nil) { 94 | _runBlockDict = 95 | @{ 96 | @"AliPay": 97 | ^(id body) { 98 | NSLog(@"请求支付宝事件"); 99 | }, 100 | @"weixin": 101 | ^(id body) { 102 | NSLog(@"请求微信事件"); 103 | }, 104 | @"webViewApp": 105 | ^(id body) { 106 | NSLog(@"请求微信事件"); 107 | }, 108 | }; 109 | } 110 | return _runBlockDict; 111 | } 112 | 113 | #pragma mark - OC 调用 JS 114 | - (void)TESTCallJS{ 115 | [[PAWebView shareInstance] callJS:@"alert('调用JS成功')"]; 116 | } 117 | 118 | - (void)TESTcallJS1 119 | { 120 | [[PAWebView shareInstance] callJS:@"alert('调用JS成功1')" handler:^(id response, NSError *error) { 121 | NSLog(@"调用js回调事件"); 122 | }]; 123 | } 124 | 125 | 126 | - (void)didReceiveMemoryWarning { 127 | [super didReceiveMemoryWarning]; 128 | // Dispose of any resources that can be recreated. 129 | } 130 | 131 | 132 | @end 133 | -------------------------------------------------------------------------------- /PAWebViewDemo/iOS.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 33 | 34 | 35 |

这是按钮调用

36 | 37 | 38 | 39 |

回调展示区

40 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /PAWebViewDemo/main.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Webview页面标题 10 | 12 | 13 | 55 | 56 | 57 | Hello,World!
58 | 59 | 60 |
61 |


62 |


63 |


64 | 65 | -------------------------------------------------------------------------------- /PAWebViewDemo/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // PAWebViewDemo 4 | // 5 | // Created by llyouss on 2018/1/9. 6 | // Copyright © 2018年 llyouss. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) { 13 | @autoreleasepool { 14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /PAWebViewDemoTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /PAWebViewDemoTests/PAWebViewDemoTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // PAWebViewDemoTests.m 3 | // PAWebViewDemoTests 4 | // 5 | // Created by llyouss on 2018/1/9. 6 | // Copyright © 2018年 llyouss. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface PAWebViewDemoTests : XCTestCase 12 | 13 | @end 14 | 15 | @implementation PAWebViewDemoTests 16 | 17 | - (void)setUp { 18 | [super setUp]; 19 | // Put setup code here. This method is called before the invocation of each test method in the class. 20 | } 21 | 22 | - (void)tearDown { 23 | // Put teardown code here. This method is called after the invocation of each test method in the class. 24 | [super tearDown]; 25 | } 26 | 27 | - (void)testExample { 28 | // This is an example of a functional test case. 29 | // Use XCTAssert and related functions to verify your tests produce the correct results. 30 | } 31 | 32 | - (void)testPerformanceExample { 33 | // This is an example of a performance test case. 34 | [self measureBlock:^{ 35 | // Put the code you want to measure the time of here. 36 | }]; 37 | } 38 | 39 | @end 40 | -------------------------------------------------------------------------------- /PAWebViewDemoUITests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /PAWebViewDemoUITests/PAWebViewDemoUITests.m: -------------------------------------------------------------------------------- 1 | // 2 | // PAWebViewDemoUITests.m 3 | // PAWebViewDemoUITests 4 | // 5 | // Created by llyouss on 2018/1/9. 6 | // Copyright © 2018年 llyouss. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface PAWebViewDemoUITests : XCTestCase 12 | 13 | @end 14 | 15 | @implementation PAWebViewDemoUITests 16 | 17 | - (void)setUp { 18 | [super setUp]; 19 | 20 | // Put setup code here. This method is called before the invocation of each test method in the class. 21 | 22 | // In UI tests it is usually best to stop immediately when a failure occurs. 23 | self.continueAfterFailure = NO; 24 | // UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method. 25 | [[[XCUIApplication alloc] init] launch]; 26 | 27 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. 28 | } 29 | 30 | - (void)tearDown { 31 | // Put teardown code here. This method is called after the invocation of each test method in the class. 32 | [super tearDown]; 33 | } 34 | 35 | - (void)testExample { 36 | // Use recording to get started writing UI tests. 37 | // Use XCTAssert and related functions to verify your tests produce the correct results. 38 | } 39 | 40 | @end 41 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PAWebView. 2 | An component WebView for ios 3 | ## Introduction 4 | PAWeView is an extensible WebView which is built on top of WKWebView, the modern WebKit framework debuted in iOS 8.0. It provides fast Web for developing sophisticated iOS native or hybrid applications. 5 | ## Sample Project 6 | For a complete example on how to use PAWeView, see the Sample Project. 7 | ## The Class Structure Chart of PAWeView 8 | ![Image text](https://github.com/llyouss/PAWeView/blob/master/Image/PAWebview.png) 9 | ## Minimum Requirements 10 | - Deployment: iOS 8.0 11 | ## Usage 12 | - #import "PAWebView.h" 13 | - plist 14 | ``` 15 | UIViewControllerBasedStatusBarAppearance 16 | 17 | UIStatusBarStyle 18 | UIStatusBarStyleDefault 19 | NSPhotoLibraryUsageDescription 20 | 访问相册 21 | NSAppTransportSecurity 22 | 23 | NSAllowsArbitraryLoads 24 | 25 | 26 | ``` 27 | - Loading 28 | ``` 29 | //初始化单例 30 | PAWebView *webView = [PAWebView shareInstance]; 31 | 32 | //加载网页 33 | [webView loadRequestURL:[NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"https://www.sina.cn"] cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:20.0f]]; 34 | [self.navigationController pushViewController:webView animated:YES]; 35 | 36 | //缓存沿用了 NSURLRequest 的缓存机制,用户可以自定义设置; 37 | typedef NS_ENUM(NSUInteger, NSURLRequestCachePolicy) 38 | { 39 | NSURLRequestUseProtocolCachePolicy = 0, //默认的缓存策略 40 | 41 | NSURLRequestReloadIgnoringLocalCacheData = 1, //忽略缓存,从服务端加载数据; 42 | NSURLRequestReloadIgnoringLocalAndRemoteCacheData = 4, // Unimplemented 43 | NSURLRequestReloadIgnoringCacheData = NSURLRequestReloadIgnoringLocalCacheData, 44 | 45 | NSURLRequestReturnCacheDataElseLoad = 2, 46 | NSURLRequestReturnCacheDataDontLoad = 3, 47 | 48 | NSURLRequestReloadRevalidatingCacheData = 5, // Unimplemented 49 | }; 50 | 51 | ``` 52 | - Refress 53 | ``` 54 | //重新加载网页 55 | [webView reload]; 56 | //无视缓存,重新加载服务器最新的网 57 | [webView reloadFromOrigin]; 58 | 59 | ``` 60 | - JS->Native 61 | ``` 62 | /* messageHander实现JS调用Native */ 63 | - (void)addMessageHander 64 | { 65 | //注入交互事件,实现 PAWKScriptMessageHandler 代理 66 | [webView addScriptMessageHandlerWithName:@[@"AliPay",@"weixin"]]; 67 | 68 | //通过block的形式实现 69 | [webView addScriptMessageHandlerWithName:@[@"AliPay",@"weixin"] observeValue:^(WKUserContentController *userContentController, WKScriptMessage *message) { 70 | 71 | //JS调用OC处理 72 | NSLog(@"name:%@;body:%@",message.name,message.body); 73 | }]; 74 | } 75 | 76 | /* 实现 PAWKScriptMessageHandler 代理 */ 77 | - (void)PAUserContentController: (WKUserContentController *) userContentController didReceiveScriptMessage:(WKScriptMessage *)message{ 78 | 79 | //JS调用OC处理 80 | NSLog(@"name:%@;body:%@",message.name,message.body); 81 | } 82 | ``` 83 | - Native -> JS 84 | ``` 85 | //方式一、 86 | [[PAWebView shareInstance] callJS:@"alert('调用JS成功')"]; 87 | //方式二、 88 | [[PAWebView shareInstance] callJS:@"alert('调用JS成功')" handler:^(id response, NSError *error) { 89 | NSLog(@"调用js回调事件"); 90 | }]; 91 | ``` 92 | - Cooikes Manager 93 | ``` 94 | /** 95 | 读取本地磁盘的cookies,包括WKWebview的cookies和sharedHTTPCookieStorage存储的cookies 96 | 97 | @return 返回包含所有的cookies的数组; 98 | 当系统低于 iOS11 时,cookies 将同步NSHTTPCookieStorage的cookies,当系统大于iOS11时,cookies 将同步 99 | */ 100 | - (NSMutableArray *)WKSharedHTTPCookieStorage; 101 | 102 | /** 103 | 提供cookies插入,用于loadRequest 网页之前 104 | 105 | @param cookie NSHTTPCookie 类型 106 | cookie 需要设置 cookie 的name,value,domain,expiresDate(过期时间,当不设置过期时间,cookie将不会自动清除); 107 | cookie 设置expiresDate时使用 [cookieProperties setObject:expiresDate forKey:NSHTTPCookieExpires];将不起作用,原因不明;使用 cookieProperties[expiresDate] = expiresDate; 设置cookies 设置时间。 108 | */ 109 | - (void)setCookie:(NSHTTPCookie *)cookie; 110 | 111 | /** 删除单个cookie */ 112 | - (void)deleteWKCookie:(NSHTTPCookie *)cookie completionHandler:(nullable void (^)(void))completionHandler; 113 | /** 删除域名下的所有的cookie */ 114 | - (void)deleteWKCookiesByHost:(NSURL *)host completionHandler:(nullable void (^)(void))completionHandler; 115 | 116 | /** 清除所有的cookies */ 117 | - (void)clearWKCookies; 118 | 119 | ``` 120 | 121 | - 清除缓存 122 | 123 | ``` 124 | 125 | /** 清除所有缓存(cookie除外) */ 126 | - (void)clearWebCacheFinish:(void(^)(BOOL finish,NSError *error))block; 127 | 128 | ``` 129 | 130 | - 清除 backForwardList 列表 131 | 132 | ``` 133 | /*清除backForwardList 列表*/ 134 | - (void)clearBackForwardList; 135 | 136 | ``` 137 | 138 | 139 | --------------------------------------------------------------------------------