├── .gitattributes ├── snapshot ├── 001.png ├── 002.png └── 003.png ├── WebViewLongPress ├── Vendor │ ├── FSActionSheet │ │ ├── FSActionSheetResources │ │ │ ├── FSActionSheet_cancel@2x.png │ │ │ └── FSActionSheet_cancel@3x.png │ │ ├── FSActionSheetCell.h │ │ ├── FSActionSheetItem.m │ │ ├── FSActionSheetConfig.m │ │ ├── FSActionSheet.h │ │ ├── FSActionSheetItem.h │ │ ├── FSActionSheetConfig.h │ │ ├── FSActionSheetCell.m │ │ └── FSActionSheet.m │ └── RNCachingURLProtocol │ │ ├── NSString+Sha1.h │ │ ├── NSString+Sha1.m │ │ ├── RNCachingURLProtocol.h │ │ ├── Reachability.h │ │ ├── Reachability.m │ │ └── RNCachingURLProtocol.m ├── SwizzeMethod.h ├── MainViewController.h ├── AppDelegate.h ├── main.m ├── CVWebViewController.h ├── WKWebViewController.h ├── CVWebViewController+ImageHelper.h ├── MainViewController.m ├── SwizzeMethod.m ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json ├── Info.plist ├── CVWebViewController.m ├── AppDelegate.m ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── WKWebViewController.m └── CVWebViewController+ImageHelper.m ├── WebViewLongPress.xcodeproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── project.pbxproj ├── .gitignore ├── WebViewLongPressTests ├── Info.plist └── WebViewLongPressTests.m └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | *.strings text diff 2 | *.pbxproj merge=union -------------------------------------------------------------------------------- /snapshot/001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guoxiucai/WebViewLongPress/HEAD/snapshot/001.png -------------------------------------------------------------------------------- /snapshot/002.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guoxiucai/WebViewLongPress/HEAD/snapshot/002.png -------------------------------------------------------------------------------- /snapshot/003.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guoxiucai/WebViewLongPress/HEAD/snapshot/003.png -------------------------------------------------------------------------------- /WebViewLongPress/Vendor/FSActionSheet/FSActionSheetResources/FSActionSheet_cancel@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guoxiucai/WebViewLongPress/HEAD/WebViewLongPress/Vendor/FSActionSheet/FSActionSheetResources/FSActionSheet_cancel@2x.png -------------------------------------------------------------------------------- /WebViewLongPress/Vendor/FSActionSheet/FSActionSheetResources/FSActionSheet_cancel@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guoxiucai/WebViewLongPress/HEAD/WebViewLongPress/Vendor/FSActionSheet/FSActionSheetResources/FSActionSheet_cancel@3x.png -------------------------------------------------------------------------------- /WebViewLongPress.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /WebViewLongPress/SwizzeMethod.h: -------------------------------------------------------------------------------- 1 | // 2 | // SwizzeMethod.h 3 | // WebViewLongPress 4 | // 5 | // Created by guoqingwei on 16/6/14. 6 | // Copyright © 2016年 cvte. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | void SwizzlingMethod(Class c, SEL origSEL, SEL newSEL); 12 | -------------------------------------------------------------------------------- /WebViewLongPress/MainViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // MainViewController.h 3 | // WebViewLongPress 4 | // 5 | // Created by guoqingwei on 2017/2/15. 6 | // Copyright © 2017年 cvte. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface MainViewController : UIViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /WebViewLongPress/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // WebViewLongPress 4 | // 5 | // Created by guoqingwei on 16/6/14. 6 | // Copyright © 2016年 cvte. 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 | -------------------------------------------------------------------------------- /WebViewLongPress/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // WebViewLongPress 4 | // 5 | // Created by guoqingwei on 16/6/14. 6 | // Copyright © 2016年 cvte. 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 | -------------------------------------------------------------------------------- /WebViewLongPress/CVWebViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // CVWebViewController.h 3 | // WebViewLongPress 4 | // 5 | // Created by guoqingwei on 16/6/14. 6 | // Copyright © 2016年 cvte. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | 12 | @interface CVWebViewController : UIViewController 13 | 14 | @property (nonatomic, strong) NSURL *url; 15 | 16 | @property (nonatomic, strong) UIWebView *webView; 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /WebViewLongPress/Vendor/RNCachingURLProtocol/NSString+Sha1.h: -------------------------------------------------------------------------------- 1 | 2 | #import 3 | #import 4 | 5 | /** 6 | * This extension contains several a helper 7 | * for creating a sha1 hash from instances of NSString 8 | */ 9 | @interface NSString (Sha1) 10 | 11 | /** 12 | * Creates a SHA1 (hash) representation of NSString. 13 | * 14 | * @return NSString 15 | */ 16 | - (NSString *)sha1; 17 | 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /WebViewLongPress/WKWebViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // WKWebViewController.h 3 | // WebViewLongPress 4 | // 5 | // Created by guoqingwei on 2017/2/15. 6 | // Copyright © 2017年 cvte. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @interface WKWebViewController : UIViewController 13 | 14 | @property (nonatomic, strong) NSURL *url; 15 | 16 | @property (nonatomic, strong) WKWebView *webView; 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /WebViewLongPress/Vendor/FSActionSheet/FSActionSheetCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // FSActionSheetCell.h 3 | // FSActionSheet 4 | // 5 | // Created by Steven on 6/7/16. 6 | // Copyright © 2016年 Steven. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "FSActionSheetConfig.h" 11 | @class FSActionSheetItem; 12 | 13 | @interface FSActionSheetCell : UITableViewCell 14 | 15 | @property (nonatomic, assign) FSContentAlignment contentAlignment; 16 | @property (nonatomic, strong) FSActionSheetItem *item; 17 | @property (nonatomic, assign) BOOL hideTopLine; ///< 是否隐藏顶部线条 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /WebViewLongPress/Vendor/FSActionSheet/FSActionSheetItem.m: -------------------------------------------------------------------------------- 1 | // 2 | // FSActionSheetItem.m 3 | // FSActionSheet 4 | // 5 | // Created by Steven on 16/5/11. 6 | // Copyright © 2016年 Steven. All rights reserved. 7 | // 8 | 9 | #import "FSActionSheetItem.h" 10 | 11 | @implementation FSActionSheetItem 12 | 13 | + (instancetype)itemWithType:(FSActionSheetType)type image:(UIImage *)image title:(NSString *)title tintColor:(UIColor *)tintColor { 14 | 15 | FSActionSheetItem *item = [[FSActionSheetItem alloc] init]; 16 | item.type = type; 17 | item.image = image; 18 | item.title = title; 19 | item.tintColor = tintColor; 20 | 21 | return item; 22 | } 23 | 24 | @end 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### Objective-C ### 2 | # Xcode 3 | # 4 | build/ 5 | *.pbxuser 6 | !default.pbxuser 7 | *.mode1v3 8 | !default.mode1v3 9 | *.mode2v3 10 | !default.mode2v3 11 | *.perspectivev3 12 | !default.perspectivev3 13 | xcuserdata 14 | *.xccheckout 15 | *.moved-aside 16 | DerivedData 17 | *.hmap 18 | *.ipa 19 | *.xcuserstate 20 | 21 | ### OSX ### 22 | .DS_Store 23 | .AppleDouble 24 | .LSOverride 25 | 26 | # Icon must end with two \r 27 | Icon 28 | 29 | 30 | # Thumbnails 31 | ._* 32 | 33 | # Files that might appear on external disk 34 | .Spotlight-V100 35 | .Trashes 36 | 37 | # Directories potentially created on remote AFP share 38 | .AppleDB 39 | .AppleDesktop 40 | Network Trash Folder 41 | Temporary Items 42 | .apdisk 43 | -------------------------------------------------------------------------------- /WebViewLongPress/Vendor/RNCachingURLProtocol/NSString+Sha1.m: -------------------------------------------------------------------------------- 1 | 2 | #import "NSString+Sha1.h" 3 | 4 | @implementation NSString (Sha1) 5 | 6 | - (NSString *)sha1 7 | { 8 | // see http://www.makebetterthings.com/iphone/how-to-get-md5-and-sha1-in-objective-c-ios-sdk/ 9 | NSData *data = [self dataUsingEncoding:NSUTF8StringEncoding]; 10 | uint8_t digest[CC_SHA1_DIGEST_LENGTH]; 11 | 12 | CC_SHA1(data.bytes, (CC_LONG)data.length, digest); 13 | 14 | NSMutableString *output = [NSMutableString stringWithCapacity:CC_SHA1_DIGEST_LENGTH * 2]; 15 | 16 | for (int i = 0; i < CC_SHA1_DIGEST_LENGTH; i++) { 17 | [output appendFormat:@"%02x", digest[i]]; 18 | } 19 | 20 | return output; 21 | } 22 | 23 | 24 | @end 25 | -------------------------------------------------------------------------------- /WebViewLongPress/CVWebViewController+ImageHelper.h: -------------------------------------------------------------------------------- 1 | // 2 | // CVWebViewController+ImageHelper.h 3 | // WebViewLongPress 4 | // 5 | // Created by guoqingwei on 16/6/14. 6 | // Copyright © 2016年 cvte. All rights reserved. 7 | // 8 | 9 | #import "CVWebViewController.h" 10 | #import "FSActionSheet.h" 11 | 12 | @interface CVWebViewController (ImageHelper) 13 | 14 | /** 15 | * get image's url from this javascript 16 | */ 17 | @property (nonatomic, strong) NSString *imageJS; 18 | 19 | /** 20 | * image's qr code string, if have 21 | * or nil 22 | */ 23 | @property (nonatomic, strong) NSString *qrCodeString; 24 | 25 | /** 26 | * image 27 | */ 28 | @property (strong, nonatomic) UIImage *image; 29 | 30 | @end 31 | -------------------------------------------------------------------------------- /WebViewLongPressTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 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 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /WebViewLongPress/MainViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // MainViewController.m 3 | // WebViewLongPress 4 | // 5 | // Created by guoqingwei on 2017/2/15. 6 | // Copyright © 2017年 cvte. All rights reserved. 7 | // 8 | 9 | #import "MainViewController.h" 10 | 11 | @interface MainViewController () 12 | 13 | @end 14 | 15 | @implementation MainViewController 16 | 17 | - (void)viewDidLoad { 18 | [super viewDidLoad]; 19 | self.navigationController.navigationBar.translucent = NO; 20 | // Do any additional setup after loading the view. 21 | } 22 | 23 | - (void)didReceiveMemoryWarning { 24 | [super didReceiveMemoryWarning]; 25 | // Dispose of any resources that can be recreated. 26 | } 27 | 28 | /* 29 | #pragma mark - Navigation 30 | 31 | // In a storyboard-based application, you will often want to do a little preparation before navigation 32 | - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { 33 | // Get the new view controller using [segue destinationViewController]. 34 | // Pass the selected object to the new view controller. 35 | } 36 | */ 37 | 38 | @end 39 | -------------------------------------------------------------------------------- /WebViewLongPressTests/WebViewLongPressTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // WebViewLongPressTests.m 3 | // WebViewLongPressTests 4 | // 5 | // Created by guoqingwei on 16/6/14. 6 | // Copyright © 2016年 cvte. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface WebViewLongPressTests : XCTestCase 12 | 13 | @end 14 | 15 | @implementation WebViewLongPressTests 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 | -------------------------------------------------------------------------------- /WebViewLongPress/Vendor/FSActionSheet/FSActionSheetConfig.m: -------------------------------------------------------------------------------- 1 | // 2 | // FSActionSheetConfig.m 3 | // FSActionSheet 4 | // 5 | // Created by Steven on 6/8/16. 6 | // Copyright © 2016 Steven. All rights reserved. 7 | // 8 | 9 | #import "FSActionSheetConfig.h" 10 | 11 | // float 12 | CGFloat const FSActionSheetDefaultMargin = 10; ///< 默认边距 (标题四边边距, 选项靠左或靠右时距离边缘的距离) 13 | CGFloat const FSActionSheetContentMaxScale = 0.65; ///< 弹窗内容高度与屏幕高度的默认比例 14 | CGFloat const FSActionSheetRowHeight = 52; ///< 行高 15 | CGFloat const FSActionSheetTitleLineSpacing = 2.5; ///< 标题行距 16 | CGFloat const FSActionSheetTitleKernSpacing = 0.5; ///< 标题字距 17 | CGFloat const FSActionSheetItemTitleFontSize = 16; ///< 选项文字字体大小, default is 16. 18 | CGFloat const FSActionSheetItemContentSpacing = 5; ///< 选项图片和文字的间距 19 | // color 20 | NSString * const FSActionSheetTitleColor = @"#888888"; ///< 标题颜色 21 | NSString * const FSActionSheetBackColor = @"#E8E8ED"; ///< 背景颜色 22 | NSString * const FSActionSheetRowNormalColor = @"#FBFBFE"; ///< 单元格背景颜色 23 | NSString * const FSActionSheetRowHighlightedColor = @"#F1F1F5"; ///< 选中高亮颜色 24 | NSString * const FSActionSheetRowTopLineColor = @"#D7D7D8"; ///< 单元格顶部线条颜色 25 | NSString * const FSActionSheetItemNormalColor = @"#000000"; ///< 选项默认颜色 26 | NSString * const FSActionSheetItemHighlightedColor = @"#E64340"; ///< 选项高亮颜色 27 | -------------------------------------------------------------------------------- /WebViewLongPress/SwizzeMethod.m: -------------------------------------------------------------------------------- 1 | // 2 | // SwizzeMethod.m 3 | // WebViewLongPress 4 | // 5 | // Created by guoqingwei on 16/6/14. 6 | // Copyright © 2016年 cvte. All rights reserved. 7 | // 8 | 9 | #import "SwizzeMethod.h" 10 | 11 | #import 12 | 13 | /** 14 | * swizzle method implementation 15 | * instance or class methods supported. 16 | */ 17 | void SwizzlingMethod(Class class, SEL originSEL, SEL swizzledSEL) 18 | { 19 | Method originMethod = class_getInstanceMethod(class, originSEL); 20 | Method swizzledMethod = nil; 21 | 22 | if (!originMethod) { 23 | originMethod = class_getClassMethod(class, originSEL); 24 | if (!originMethod) { 25 | return; 26 | } 27 | swizzledMethod = class_getClassMethod(class, swizzledSEL); 28 | if (!swizzledMethod) { 29 | return; 30 | } 31 | } else { 32 | swizzledMethod = class_getInstanceMethod(class, swizzledSEL); 33 | if (!swizzledMethod){ 34 | return; 35 | } 36 | } 37 | 38 | if(class_addMethod(class, originSEL, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod))) { 39 | class_replaceMethod(class, swizzledSEL, method_getImplementation(originMethod), method_getTypeEncoding(originMethod)); 40 | } else { 41 | method_exchangeImplementations(originMethod, swizzledMethod); 42 | } 43 | } 44 | 45 | -------------------------------------------------------------------------------- /WebViewLongPress/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "ipad", 35 | "size" : "29x29", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "ipad", 40 | "size" : "29x29", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "40x40", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "40x40", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "76x76", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "76x76", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } -------------------------------------------------------------------------------- /WebViewLongPress/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSAppTransportSecurity 6 | 7 | NSAllowsArbitraryLoads 8 | 9 | 10 | CFBundleDevelopmentRegion 11 | en 12 | CFBundleExecutable 13 | $(EXECUTABLE_NAME) 14 | CFBundleIdentifier 15 | $(PRODUCT_BUNDLE_IDENTIFIER) 16 | CFBundleInfoDictionaryVersion 17 | 6.0 18 | CFBundleName 19 | $(PRODUCT_NAME) 20 | CFBundlePackageType 21 | APPL 22 | CFBundleShortVersionString 23 | 1.0 24 | CFBundleSignature 25 | ???? 26 | CFBundleVersion 27 | 1 28 | LSRequiresIPhoneOS 29 | 30 | UILaunchStoryboardName 31 | LaunchScreen 32 | UIMainStoryboardFile 33 | Main 34 | UIRequiredDeviceCapabilities 35 | 36 | armv7 37 | 38 | UISupportedInterfaceOrientations 39 | 40 | UIInterfaceOrientationPortrait 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | UISupportedInterfaceOrientations~ipad 45 | 46 | UIInterfaceOrientationPortrait 47 | UIInterfaceOrientationPortraitUpsideDown 48 | UIInterfaceOrientationLandscapeLeft 49 | UIInterfaceOrientationLandscapeRight 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /WebViewLongPress/Vendor/FSActionSheet/FSActionSheet.h: -------------------------------------------------------------------------------- 1 | // 2 | // FSActionSheet.h 3 | // FSActionSheet 4 | // 5 | // Created by Steven on 6/7/16. 6 | // Copyright © 2016年 Steven. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "FSActionSheetItem.h" 11 | #import "FSActionSheetConfig.h" 12 | 13 | @class FSActionSheet; 14 | @protocol FSActionSheetDelegate 15 | 16 | @optional 17 | - (void)FSActionSheet:(FSActionSheet *)actionSheet selectedIndex:(NSInteger)selectedIndex; 18 | 19 | @end 20 | 21 | @interface FSActionSheet : UIView 22 | 23 | @property (nonatomic, weak) id delegate; ///< 代理对象 24 | @property (nonatomic, assign) FSContentAlignment contentAlignment; ///< 默认是FSContentAlignmentCenter. 25 | @property (nonatomic, assign) BOOL hideOnTouchOutside; ///< 是否开启点击半透明层隐藏弹窗, 默认为YES. 26 | 27 | /*! @author Steven 28 | * @brief 单文本选项快速初始化 29 | * @param title 标题 30 | * @param delegate 代理 31 | * @param cancelButtonTitle 取消按钮标题 32 | * @param highlightedButtonTitle 高亮按钮标题 33 | * @param otherButtonTitles 其他按钮标题集合 34 | */ 35 | - (instancetype)initWithTitle:(NSString *)title delegate:(id)delegate cancelButtonTitle:(NSString *)cancelButtonTitle highlightedButtonTitle:(NSString *)highlightedButtonTitle otherButtonTitles:(NSArray *)otherButtonTitles; 36 | 37 | /*! @author Steven 38 | * @brief 在外部组装选项按钮item 39 | * @param title 标题 40 | * @param cancelTitle 取消按钮标题 41 | * @param items 选项按钮item 42 | */ 43 | - (instancetype)initWithTitle:(NSString *)title cancelTitle:(NSString *)cancelTitle items:(NSArray *)items; 44 | 45 | /*! @author Steven 46 | * @brief 单展示, 不绑定block回调 47 | */ 48 | - (void)show; 49 | 50 | /*! @author Steven 51 | * @brief 展示并绑定block回调 52 | * @param selectedHandler 选择选项按钮回调. 53 | */ 54 | - (void)showWithSelectedCompletion:(FSActionSheetHandler)selectedHandler; 55 | 56 | @end 57 | -------------------------------------------------------------------------------- /WebViewLongPress/CVWebViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // CVWebViewController.m 3 | // WebViewLongPress 4 | // 5 | // Created by guoqingwei on 16/6/14. 6 | // Copyright © 2016年 cvte. All rights reserved. 7 | // 8 | 9 | #import "CVWebViewController.h" 10 | 11 | @implementation CVWebViewController 12 | 13 | - (void)viewDidLoad 14 | { 15 | [super viewDidLoad]; 16 | 17 | self.title = @"UIWebView"; 18 | 19 | NSString *urlString = @"mp.weixin.qq.com/s?__biz=MzI2ODAzODAzMw==&mid=2650057120&idx=2&sn=c875f7d03ea3823e8dcb3dc4d0cff51d&scene=0#wechat_redirect"; 20 | 21 | self.url = [self autoFillURL:[NSURL URLWithString:urlString]]; 22 | 23 | [self.view addSubview:self.webView]; 24 | 25 | [self.webView loadRequest:[[NSURLRequest alloc] initWithURL:self.url]]; 26 | } 27 | 28 | - (UIWebView *)webView 29 | { 30 | if (_webView == nil) { 31 | _webView = [[UIWebView alloc] initWithFrame:self.view.bounds]; 32 | _webView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; 33 | _webView.delegate = self; 34 | } 35 | return _webView; 36 | } 37 | 38 | #pragma mark - UIWebViewDelegate 39 | 40 | - (void)webViewDidStartLoad:(UIWebView *)webView 41 | { 42 | NSLog(@"start load"); 43 | } 44 | 45 | - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType 46 | { 47 | return YES; 48 | } 49 | 50 | - (void)webViewDidFinishLoad:(UIWebView *)webView 51 | { 52 | NSLog(@"finish load"); 53 | } 54 | 55 | - (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error 56 | { 57 | NSLog(@"load fail"); 58 | } 59 | 60 | #pragma mark - private methods 61 | 62 | - (NSURL *)autoFillURL:(NSURL *)url 63 | { 64 | //If no URL scheme was supplied, defer back to HTTP. 65 | if (url.scheme.length == 0) { 66 | url = [NSURL URLWithString:[NSString stringWithFormat:@"http://%@", [url absoluteString]]]; 67 | } 68 | 69 | return url; 70 | } 71 | 72 | 73 | @end 74 | -------------------------------------------------------------------------------- /WebViewLongPress/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // WebViewLongPress 4 | // 5 | // Created by guoqingwei on 16/6/14. 6 | // Copyright © 2016年 cvte. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | #import "RNCachingURLProtocol.h" 11 | #import "CVWebViewController+ImageHelper.h" 12 | 13 | @interface AppDelegate () 14 | 15 | @end 16 | 17 | @implementation AppDelegate 18 | 19 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 20 | // Override point for customization after application launch. 21 | [NSURLProtocol registerClass:[RNCachingURLProtocol class]]; 22 | return YES; 23 | } 24 | 25 | - (void)applicationWillResignActive:(UIApplication *)application { 26 | // 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. 27 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 28 | } 29 | 30 | - (void)applicationDidEnterBackground:(UIApplication *)application { 31 | // 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. 32 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 33 | } 34 | 35 | - (void)applicationWillEnterForeground:(UIApplication *)application { 36 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 37 | } 38 | 39 | - (void)applicationDidBecomeActive:(UIApplication *)application { 40 | // 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. 41 | } 42 | 43 | - (void)applicationWillTerminate:(UIApplication *)application { 44 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 45 | } 46 | 47 | @end 48 | -------------------------------------------------------------------------------- /WebViewLongPress/Vendor/FSActionSheet/FSActionSheetItem.h: -------------------------------------------------------------------------------- 1 | // 2 | // FSActionSheetItem.h 3 | // FSActionSheet 4 | // 5 | // Created by Steven on 16/5/11. 6 | // Copyright © 2016年 Steven. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "FSActionSheetConfig.h" 11 | 12 | @interface FSActionSheetItem : NSObject 13 | 14 | @property (nonatomic, assign) FSActionSheetType type; ///< 选项类型, 有 默认 和 高亮 两种类型. 15 | @property (nonatomic, strong) UIImage *image; ///< 选项图标, 建议image的size为 @2x: 46x46, @3x: 69x69. 16 | @property (nonatomic, copy) NSString *title; ///< 选项标题 17 | @property (nonatomic, strong) UIColor *tintColor; ///< 选项前景色, 如果设置了这个颜色的话, 则无论选项设置的图标是什么颜色都会被修改为当前设置的这个颜色, 18 | ///< 同时这个颜色也会是标题的文本颜色. 19 | 20 | + (instancetype)itemWithType:(FSActionSheetType)type image:(UIImage *)image title:(NSString *)title tintColor:(UIColor *)tintColor; 21 | 22 | @end 23 | 24 | /*! @author Steven 25 | * @brief 单标题的选项 26 | * @param type 类型 27 | * @param title 标题 28 | */ 29 | NS_INLINE FSActionSheetItem *FSActionSheetTitleItemMake(FSActionSheetType type, NSString *title) { 30 | return [FSActionSheetItem itemWithType:type image:nil title:title tintColor:nil]; 31 | } 32 | 33 | /*! @author Steven 34 | * @brief 标题和图标的选项 35 | * @param type 类型 36 | * @param image 图片 37 | * @param title 标题 38 | */ 39 | NS_INLINE FSActionSheetItem *FSActionSheetTitleWithImageItemMake(FSActionSheetType type, UIImage *image, NSString *title) { 40 | return [FSActionSheetItem itemWithType:type image:image title:title tintColor:nil]; 41 | } 42 | 43 | /*! @author Steven 44 | * @brief 单标题且自定义前景色的选项 45 | * @param type 类型 46 | * @param title 标题 47 | * @param tintColor 自定义前景色 48 | */ 49 | NS_INLINE FSActionSheetItem *FSActionSheetTitleWithColorItemMake(FSActionSheetType type, NSString *title, UIColor *tintColor) { 50 | return [FSActionSheetItem itemWithType:type image:nil title:title tintColor:tintColor]; 51 | } 52 | 53 | /*! @author Steven 54 | * @brief 标题和图片并且自定义前景色的选项 55 | * @param type 类型 56 | * @param title 标题 57 | * @param image 图片 58 | * @param tintColor 自定义前景色 59 | */ 60 | NS_INLINE FSActionSheetItem *FSActionSheetItemMake(FSActionSheetType type, UIImage *image, NSString *title, UIColor *tintColor) { 61 | return [FSActionSheetItem itemWithType:type image:image title:title tintColor:tintColor]; 62 | } 63 | 64 | 65 | -------------------------------------------------------------------------------- /WebViewLongPress/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 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /WebViewLongPress/Vendor/RNCachingURLProtocol/RNCachingURLProtocol.h: -------------------------------------------------------------------------------- 1 | // 2 | // RNCachingURLProtocol.h 3 | // 4 | // Created by Robert Napier on 1/10/12. 5 | // Copyright (c) 2012 Rob Napier. All rights reserved. 6 | // 7 | // This code is licensed under the MIT License: 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a 10 | // copy of this software and associated documentation files (the "Software"), 11 | // to deal in the Software without restriction, including without limitation 12 | // the rights to use, copy, modify, merge, publish, distribute, sublicense, 13 | // and/or sell copies of the Software, and to permit persons to whom the 14 | // Software is furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in 17 | // all copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 24 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | // DEALINGS IN THE SOFTWARE. 26 | // 27 | 28 | // RNCachingURLProtocol is a simple shim for the HTTP protocol (that’s not 29 | // nearly as scary as it sounds). Anytime a URL is download, the response is 30 | // cached to disk. Anytime a URL is requested, if we’re online then things 31 | // proceed normally. If we’re offline, then we retrieve the cached version. 32 | // 33 | // The point of RNCachingURLProtocol is mostly to demonstrate how this is done. 34 | // The current implementation is extremely simple. In particular, it doesn’t 35 | // worry about cleaning up the cache. The assumption is that you’re caching just 36 | // a few simple things, like your “Latest News” page (which was the problem I 37 | // was solving). It caches all HTTP traffic, so without some modifications, it’s 38 | // not appropriate for an app that has a lot of HTTP connections (see 39 | // MKNetworkKit for that). But if you need to cache some URLs and not others, 40 | // that is easy to implement. 41 | // 42 | // You should also look at [AFCache](https://github.com/artifacts/AFCache) for a 43 | // more powerful caching engine that is currently integrating the ideas of 44 | // RNCachingURLProtocol. 45 | // 46 | // A quick rundown of how to use it: 47 | // 48 | // 1. To build, you will need the Reachability code from Apple (included). That requires that you link with 49 | // `SystemConfiguration.framework`. 50 | // 51 | // 2. At some point early in the program (application:didFinishLaunchingWithOptions:), 52 | // call the following: 53 | // 54 | // `[NSURLProtocol registerClass:[RNCachingURLProtocol class]];` 55 | // 56 | // 3. There is no step 3. 57 | // 58 | // For more details see 59 | // [Drop-in offline caching for UIWebView (and NSURLProtocol)](http://robnapier.net/blog/offline-uiwebview-nsurlprotocol-588). 60 | 61 | #import 62 | 63 | @interface RNCachingURLProtocol : NSURLProtocol 64 | 65 | + (NSSet *)supportedSchemes; 66 | + (void)setSupportedSchemes:(NSSet *)supportedSchemes; 67 | 68 | /** 69 | * Add to support url string. 70 | */ 71 | + (NSString *)cachePathForURLString:(NSString *)url; 72 | 73 | - (NSString *)cachePathForRequest:(NSURLRequest *)aRequest; 74 | - (BOOL) useCache; 75 | 76 | @end 77 | 78 | @interface RNCachedData : NSObject 79 | @property (nonatomic, readwrite, strong) NSData *data; 80 | @property (nonatomic, readwrite, strong) NSURLResponse *response; 81 | @property (nonatomic, readwrite, strong) NSURLRequest *redirectRequest; 82 | @end 83 | -------------------------------------------------------------------------------- /WebViewLongPress/Vendor/FSActionSheet/FSActionSheetConfig.h: -------------------------------------------------------------------------------- 1 | // 2 | // FSActionSheetConfig.h 3 | // FSActionSheet 4 | // 5 | // Created by Steven on 6/7/16. 6 | // Copyright © 2016 Steven. All rights reserved. 7 | // 8 | 9 | 10 | #import 11 | #import 12 | 13 | // 选择选项block回调 14 | typedef void(^FSActionSheetHandler)(NSInteger selectedIndex); 15 | 16 | // 选项类型枚举 17 | typedef NS_ENUM(NSInteger, FSActionSheetType) { 18 | FSActionSheetTypeNormal = 0, 19 | FSActionSheetTypeHighlighted, 20 | }; 21 | 22 | // 内容偏移枚举 23 | typedef NS_ENUM(NSInteger, FSContentAlignment) { 24 | FSContentAlignmentLeft = 0, ///< 内容紧靠左边 25 | FSContentAlignmentCenter, ///< 内容居中 26 | FSContentAlignmentRight, ///< 内容紧靠右边 27 | }; 28 | 29 | 30 | // float 31 | UIKIT_EXTERN CGFloat const FSActionSheetDefaultMargin; ///< 默认边距 (标题四边边距, 选项靠左或靠右时距离边缘的距离), default is 10. 32 | UIKIT_EXTERN CGFloat const FSActionSheetContentMaxScale; ///< 弹窗内容高度与屏幕高度的默认比例, default is 0.65. 33 | UIKIT_EXTERN CGFloat const FSActionSheetRowHeight; ///< 行高, default is 44. 34 | UIKIT_EXTERN CGFloat const FSActionSheetTitleLineSpacing; ///< 标题行距, default is 2.5. 35 | UIKIT_EXTERN CGFloat const FSActionSheetTitleKernSpacing; ///< 标题字距, default is 0.5. 36 | UIKIT_EXTERN CGFloat const FSActionSheetItemTitleFontSize; ///< 选项文字字体大小, default is 16. 37 | UIKIT_EXTERN CGFloat const FSActionSheetItemContentSpacing; ///< 选项图片和文字的间距, default is 5. 38 | // color 39 | UIKIT_EXTERN NSString * const FSActionSheetTitleColor; ///< 标题颜色, default is #888888 40 | UIKIT_EXTERN NSString * const FSActionSheetBackColor; ///< 背景颜色, default is #E8E8ED 41 | UIKIT_EXTERN NSString * const FSActionSheetRowNormalColor; ///< 单元格背景颜色, default is #FBFBFE 42 | UIKIT_EXTERN NSString * const FSActionSheetRowHighlightedColor; ///< 选中高亮颜色, default is #F1F1F5 43 | UIKIT_EXTERN NSString * const FSActionSheetRowTopLineColor; ///< 单元格顶部线条颜色, default is #D7D7D8 44 | UIKIT_EXTERN NSString * const FSActionSheetItemNormalColor; ///< 选项默认颜色, default is #000000 45 | UIKIT_EXTERN NSString * const FSActionSheetItemHighlightedColor; ///< 选项高亮颜色, default is #E64340 46 | 47 | /*! @author Steven 48 | * @brief 获取颜色 49 | * @param aColorString 十六进制颜色字符串 50 | */ 51 | NS_INLINE UIColor *FSColorWithString(NSString *aColorString) { 52 | if (aColorString.length == 0) { 53 | return nil; 54 | } 55 | 56 | if ([aColorString hasPrefix:@"#"]) { 57 | aColorString = [aColorString substringFromIndex:1]; 58 | } 59 | 60 | if (aColorString.length == 6) { 61 | int len = (int)aColorString.length/3; 62 | unsigned int a[3]; 63 | for (int i=0; i<3; i++) { 64 | NSRange range; 65 | range.location = i*len; 66 | range.length = len; 67 | NSString *str = [aColorString substringWithRange:range]; 68 | [[NSScanner scannerWithString:str] scanHexInt:a+i]; 69 | if (len == 1) { 70 | a[i] *= 17; 71 | } 72 | } 73 | return [UIColor colorWithRed:a[0]/255.0f green:a[1]/255.0f blue:a[2]/255.0f alpha:1]; 74 | } 75 | else if (aColorString.length == 8) { 76 | int len = (int)aColorString.length/4; 77 | unsigned int a[4]; 78 | for (int i=0; i<4; i++) { 79 | NSRange range; 80 | range.location = i*len; 81 | range.length = len; 82 | NSString *str = [aColorString substringWithRange:range]; 83 | [[NSScanner scannerWithString:str] scanHexInt:a+i]; 84 | if (len == 1) { 85 | a[i] *= 17; 86 | } 87 | } 88 | return [UIColor colorWithRed:a[0]/255.0f green:a[1]/255.0f blue:a[2]/255.0f alpha:a[3]/255.0f]; 89 | } 90 | else if (aColorString.length <= 2) { 91 | unsigned int gray; 92 | [[NSScanner scannerWithString:aColorString] scanHexInt:&gray]; 93 | if (aColorString.length == 1) 94 | { 95 | gray *= 17; 96 | } 97 | return [UIColor colorWithWhite:gray/255.0f alpha:1]; 98 | } 99 | 100 | return nil; 101 | } 102 | 103 | 104 | 105 | -------------------------------------------------------------------------------- /WebViewLongPress/Vendor/RNCachingURLProtocol/Reachability.h: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | File: Reachability.h 4 | Abstract: Basic demonstration of how to use the SystemConfiguration Reachablity APIs. 5 | 6 | Version: 2.2 7 | 8 | Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple Inc. 9 | ("Apple") in consideration of your agreement to the following terms, and your 10 | use, installation, modification or redistribution of this Apple software 11 | constitutes acceptance of these terms. If you do not agree with these terms, 12 | please do not use, install, modify or redistribute this Apple software. 13 | 14 | In consideration of your agreement to abide by the following terms, and subject 15 | to these terms, Apple grants you a personal, non-exclusive license, under 16 | Apple's copyrights in this original Apple software (the "Apple Software"), to 17 | use, reproduce, modify and redistribute the Apple Software, with or without 18 | modifications, in source and/or binary forms; provided that if you redistribute 19 | the Apple Software in its entirety and without modifications, you must retain 20 | this notice and the following text and disclaimers in all such redistributions 21 | of the Apple Software. 22 | Neither the name, trademarks, service marks or logos of Apple Inc. may be used 23 | to endorse or promote products derived from the Apple Software without specific 24 | prior written permission from Apple. Except as expressly stated in this notice, 25 | no other rights or licenses, express or implied, are granted by Apple herein, 26 | including but not limited to any patent rights that may be infringed by your 27 | derivative works or by other works in which the Apple Software may be 28 | incorporated. 29 | 30 | The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES NO 31 | WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED 32 | WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR 33 | PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN 34 | COMBINATION WITH YOUR PRODUCTS. 35 | 36 | IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR 37 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE 38 | GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 39 | ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR 40 | DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF 41 | CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF 42 | APPLE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 43 | 44 | Copyright (C) 2010 Apple Inc. All Rights Reserved. 45 | 46 | */ 47 | 48 | 49 | #import 50 | #import 51 | #import 52 | 53 | typedef enum 54 | { 55 | NotReachable = 0, 56 | ReachableViaWiFi, 57 | ReachableViaWWAN 58 | } NetworkStatus; 59 | #define kReachabilityChangedNotification @"kNetworkReachabilityChangedNotification" 60 | 61 | @interface Reachability : NSObject 62 | { 63 | BOOL localWiFiRef; 64 | SCNetworkReachabilityRef reachabilityRef; 65 | } 66 | 67 | //reachabilityWithHostName- Use to check the reachability of a particular host name. 68 | + (Reachability *)reachabilityWithHostName:(NSString *)hostName; 69 | 70 | //reachabilityWithAddress- Use to check the reachability of a particular IP address. 71 | + (Reachability *)reachabilityWithAddress:(const struct sockaddr_in *)hostAddress; 72 | 73 | //reachabilityForInternetConnection- checks whether the default route is available. 74 | // Should be used by applications that do not connect to a particular host 75 | + (Reachability *)reachabilityForInternetConnection; 76 | 77 | //reachabilityForLocalWiFi- checks whether a local wifi connection is available. 78 | + (Reachability *)reachabilityForLocalWiFi; 79 | 80 | //Start listening for reachability notifications on the current run loop 81 | - (BOOL)startNotifier; 82 | - (void)stopNotifier; 83 | 84 | - (NetworkStatus)currentReachabilityStatus; 85 | //WWAN may be available, but not active until a connection has been established. 86 | //WiFi may require a connection for VPN on Demand. 87 | - (BOOL)connectionRequired; 88 | @end 89 | 90 | 91 | -------------------------------------------------------------------------------- /WebViewLongPress/Vendor/FSActionSheet/FSActionSheetCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // FSActionSheetCell.m 3 | // FSActionSheet 4 | // 5 | // Created by Steven on 6/7/16. 6 | // Copyright © 2016年 Steven. All rights reserved. 7 | // 8 | 9 | #import "FSActionSheetCell.h" 10 | #import "FSActionSheetItem.h" 11 | 12 | @interface FSActionSheetCell () 13 | 14 | @property (nonatomic, strong) UIButton *titleButton; 15 | @property (nonatomic, weak) UIView *topLine; ///< 顶部线条 16 | 17 | @end 18 | 19 | @implementation FSActionSheetCell 20 | 21 | - (void)setHighlighted:(BOOL)highlighted animated:(BOOL)animated { 22 | [super setHighlighted:highlighted animated:animated]; 23 | 24 | if (highlighted) { 25 | self.contentView.backgroundColor = FSColorWithString(FSActionSheetRowHighlightedColor); 26 | } else { 27 | [UIView animateWithDuration:0.25 animations:^{ 28 | self.contentView.backgroundColor = self.backgroundColor; 29 | }]; 30 | } 31 | } 32 | 33 | - (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier { 34 | if (!(self = [super initWithStyle:style reuseIdentifier:reuseIdentifier])) return nil; 35 | 36 | self.backgroundColor = FSColorWithString(FSActionSheetRowNormalColor); 37 | self.contentView.backgroundColor = self.backgroundColor; 38 | self.selectionStyle = UITableViewCellSelectionStyleNone; 39 | _contentAlignment = FSContentAlignmentCenter; 40 | 41 | [self setupSubviews]; 42 | 43 | return self; 44 | } 45 | 46 | - (void)setupSubviews { 47 | _titleButton = [UIButton buttonWithType:UIButtonTypeSystem]; 48 | _titleButton.tintColor = FSColorWithString(FSActionSheetItemNormalColor); 49 | _titleButton.titleLabel.font = [UIFont systemFontOfSize:FSActionSheetItemTitleFontSize]; 50 | _titleButton.userInteractionEnabled = NO; 51 | _titleButton.translatesAutoresizingMaskIntoConstraints = NO; 52 | [self.contentView addSubview:_titleButton]; 53 | [self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[_titleButton]|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(_titleButton)]]; 54 | [self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[_titleButton]|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(_titleButton)]]; 55 | 56 | // 顶部线条 57 | UIView *topLine = [[UIView alloc] init]; 58 | topLine.backgroundColor = FSColorWithString(FSActionSheetRowTopLineColor); 59 | topLine.translatesAutoresizingMaskIntoConstraints = NO; 60 | [self.contentView addSubview:topLine]; 61 | self.topLine = topLine; 62 | CGFloat lineHeight = 1/[UIScreen mainScreen].scale; ///< 线条高度 63 | [self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[topLine]|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(topLine)]]; 64 | [self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[topLine(lineHeight)]" options:0 metrics:@{@"lineHeight":@(lineHeight)} views:NSDictionaryOfVariableBindings(topLine)]]; 65 | } 66 | 67 | - (void)setItem:(FSActionSheetItem *)item { 68 | _item = item; 69 | 70 | // 前景色设置, 如果有自定义前景色则使用自定义的前景色, 否则使用预配置的颜色值. 71 | UIColor *tintColor; 72 | if (item.tintColor) { 73 | tintColor = item.tintColor; 74 | } else { 75 | if (_item.type == FSActionSheetTypeNormal) { 76 | tintColor = FSColorWithString(FSActionSheetItemNormalColor); 77 | } else { 78 | tintColor = FSColorWithString(FSActionSheetItemHighlightedColor); 79 | } 80 | } 81 | _titleButton.tintColor = tintColor; 82 | 83 | // 调整图片与标题的间距 84 | _titleButton.imageEdgeInsets = UIEdgeInsetsMake(0, _item.image?-FSActionSheetItemContentSpacing/2:0, 85 | _item.image?1:0, _item.image?FSActionSheetItemContentSpacing/2:0); 86 | _titleButton.titleEdgeInsets = UIEdgeInsetsMake(_item.image?1:0, _item.image?FSActionSheetItemContentSpacing/2:0, 87 | 0, _item.image?-FSActionSheetItemContentSpacing/2:0); 88 | // 设置图片与标题 89 | [_titleButton setTitle:item.title forState:UIControlStateNormal]; 90 | [_titleButton setImage:item.image forState:UIControlStateNormal]; 91 | } 92 | 93 | - (void)setContentAlignment:(FSContentAlignment)contentAlignment { 94 | if (_contentAlignment == contentAlignment) return; 95 | 96 | _contentAlignment = contentAlignment; 97 | // 更新button的图片和标题Edge 98 | [self updateButtonContentEdge]; 99 | // 设置内容偏移 100 | switch (_contentAlignment) { 101 | // 局左 102 | case FSContentAlignmentLeft: { 103 | _titleButton.contentHorizontalAlignment = UIControlContentHorizontalAlignmentLeft; 104 | _titleButton.contentEdgeInsets = UIEdgeInsetsMake(0, FSActionSheetDefaultMargin, 0, -FSActionSheetDefaultMargin); 105 | break; 106 | } 107 | // 居中 108 | case FSContentAlignmentCenter: { 109 | _titleButton.contentHorizontalAlignment = UIControlContentHorizontalAlignmentCenter; 110 | _titleButton.contentEdgeInsets = UIEdgeInsetsZero; 111 | break; 112 | } 113 | // 居右 114 | case FSContentAlignmentRight: { 115 | _titleButton.contentHorizontalAlignment = UIControlContentHorizontalAlignmentRight; 116 | _titleButton.contentEdgeInsets = UIEdgeInsetsMake(0, -FSActionSheetDefaultMargin, 0, FSActionSheetDefaultMargin); 117 | break; 118 | } 119 | } 120 | } 121 | 122 | // 更新button图片与标题的edge 123 | - (void)updateButtonContentEdge { 124 | if (!_item.image) return; 125 | if (_contentAlignment == FSContentAlignmentRight) { 126 | CGFloat titleWidth = [[_titleButton titleForState:UIControlStateNormal] sizeWithAttributes:@{NSFontAttributeName:_titleButton.titleLabel.font}].width; 127 | _titleButton.imageEdgeInsets = UIEdgeInsetsMake(0, titleWidth, 1, -titleWidth); 128 | _titleButton.titleEdgeInsets = UIEdgeInsetsMake(1, -_item.image.size.width-FSActionSheetItemContentSpacing, 129 | 0, _item.image.size.width+FSActionSheetItemContentSpacing); 130 | } else { 131 | _titleButton.imageEdgeInsets = UIEdgeInsetsMake(0, -FSActionSheetItemContentSpacing/2, 1, FSActionSheetItemContentSpacing/2); 132 | _titleButton.titleEdgeInsets = UIEdgeInsetsMake(1, FSActionSheetItemContentSpacing/2, 0, -FSActionSheetItemContentSpacing/2); 133 | } 134 | } 135 | 136 | - (void)setHideTopLine:(BOOL)hideTopLine { 137 | _topLine.hidden = hideTopLine; 138 | } 139 | 140 | @end 141 | 142 | -------------------------------------------------------------------------------- /WebViewLongPress/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 | 33 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /WebViewLongPress/Vendor/RNCachingURLProtocol/Reachability.m: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | File: Reachability.m 4 | Abstract: Basic demonstration of how to use the SystemConfiguration Reachablity APIs. 5 | 6 | Version: 2.2 7 | 8 | Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple Inc. 9 | ("Apple") in consideration of your agreement to the following terms, and your 10 | use, installation, modification or redistribution of this Apple software 11 | constitutes acceptance of these terms. If you do not agree with these terms, 12 | please do not use, install, modify or redistribute this Apple software. 13 | 14 | In consideration of your agreement to abide by the following terms, and subject 15 | to these terms, Apple grants you a personal, non-exclusive license, under 16 | Apple's copyrights in this original Apple software (the "Apple Software"), to 17 | use, reproduce, modify and redistribute the Apple Software, with or without 18 | modifications, in source and/or binary forms; provided that if you redistribute 19 | the Apple Software in its entirety and without modifications, you must retain 20 | this notice and the following text and disclaimers in all such redistributions 21 | of the Apple Software. 22 | Neither the name, trademarks, service marks or logos of Apple Inc. may be used 23 | to endorse or promote products derived from the Apple Software without specific 24 | prior written permission from Apple. Except as expressly stated in this notice, 25 | no other rights or licenses, express or implied, are granted by Apple herein, 26 | including but not limited to any patent rights that may be infringed by your 27 | derivative works or by other works in which the Apple Software may be 28 | incorporated. 29 | 30 | The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES NO 31 | WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED 32 | WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR 33 | PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN 34 | COMBINATION WITH YOUR PRODUCTS. 35 | 36 | IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR 37 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE 38 | GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 39 | ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR 40 | DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF 41 | CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF 42 | APPLE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 43 | 44 | Copyright (C) 2010 Apple Inc. All Rights Reserved. 45 | 46 | */ 47 | 48 | #import 49 | #import 50 | 51 | #import "Reachability.h" 52 | 53 | #define kShouldPrintReachabilityFlags 0 54 | 55 | static void PrintReachabilityFlags(SCNetworkReachabilityFlags flags, const char *comment) { 56 | #if kShouldPrintReachabilityFlags 57 | 58 | (flags & kSCNetworkReachabilityFlagsIsWWAN) ? 'W' : '-', 59 | (flags & kSCNetworkReachabilityFlagsReachable) ? 'R' : '-', 60 | 61 | (flags & kSCNetworkReachabilityFlagsTransientConnection) ? 't' : '-', 62 | (flags & kSCNetworkReachabilityFlagsConnectionRequired) ? 'c' : '-', 63 | (flags & kSCNetworkReachabilityFlagsConnectionOnTraffic) ? 'C' : '-', 64 | (flags & kSCNetworkReachabilityFlagsInterventionRequired) ? 'i' : '-', 65 | (flags & kSCNetworkReachabilityFlagsConnectionOnDemand) ? 'D' : '-', 66 | (flags & kSCNetworkReachabilityFlagsIsLocalAddress) ? 'l' : '-', 67 | (flags & kSCNetworkReachabilityFlagsIsDirect) ? 'd' : '-', 68 | comment 69 | ); 70 | #endif 71 | } 72 | 73 | 74 | @implementation Reachability 75 | static void ReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkReachabilityFlags flags, void *info) { 76 | #pragma unused (target, flags) 77 | NSCAssert(info != NULL, @"info was NULL in ReachabilityCallback"); 78 | NSCAssert([(__bridge id)info isKindOfClass:[Reachability class]], @"info was wrong class in ReachabilityCallback"); 79 | 80 | Reachability *noteObject = (__bridge Reachability *)info; 81 | // Post a notification to notify the client that the network reachability changed. 82 | [[NSNotificationCenter defaultCenter] postNotificationName:kReachabilityChangedNotification object:noteObject]; 83 | } 84 | 85 | - (BOOL)startNotifier 86 | { 87 | BOOL retVal = NO; 88 | SCNetworkReachabilityContext context = {0, (__bridge void *)self, NULL, NULL, NULL}; 89 | if (SCNetworkReachabilitySetCallback(reachabilityRef, ReachabilityCallback, &context)) { 90 | if (SCNetworkReachabilityScheduleWithRunLoop(reachabilityRef, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode)) { 91 | retVal = YES; 92 | } 93 | } 94 | return retVal; 95 | } 96 | 97 | - (void)stopNotifier 98 | { 99 | if (reachabilityRef != NULL) { 100 | SCNetworkReachabilityUnscheduleFromRunLoop(reachabilityRef, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); 101 | } 102 | } 103 | 104 | - (void)dealloc 105 | { 106 | [self stopNotifier]; 107 | if (reachabilityRef != NULL) { 108 | CFRelease(reachabilityRef); 109 | } 110 | } 111 | 112 | + (Reachability *)reachabilityWithHostName:(NSString *)hostName; 113 | { 114 | Reachability *retVal = NULL; 115 | SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithName(NULL, [hostName UTF8String]); 116 | if (reachability != NULL) { 117 | retVal = [[self alloc] init]; 118 | if (retVal != NULL) { 119 | retVal->reachabilityRef = reachability; 120 | retVal->localWiFiRef = NO; 121 | } 122 | } 123 | return retVal; 124 | } 125 | 126 | + (Reachability *)reachabilityWithAddress:(const struct sockaddr_in *)hostAddress; 127 | { 128 | SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, (const struct sockaddr *)hostAddress); 129 | Reachability *retVal = NULL; 130 | if (reachability != NULL) { 131 | retVal = [[self alloc] init]; 132 | if (retVal != NULL) { 133 | retVal->reachabilityRef = reachability; 134 | retVal->localWiFiRef = NO; 135 | } 136 | } 137 | return retVal; 138 | } 139 | 140 | + (Reachability *)reachabilityForInternetConnection; 141 | { 142 | struct sockaddr_in zeroAddress; 143 | bzero(&zeroAddress, sizeof(zeroAddress)); 144 | zeroAddress.sin_len = sizeof(zeroAddress); 145 | zeroAddress.sin_family = AF_INET; 146 | return [self reachabilityWithAddress:&zeroAddress]; 147 | } 148 | 149 | + (Reachability *)reachabilityForLocalWiFi; 150 | { 151 | struct sockaddr_in localWifiAddress; 152 | bzero(&localWifiAddress, sizeof(localWifiAddress)); 153 | localWifiAddress.sin_len = sizeof(localWifiAddress); 154 | localWifiAddress.sin_family = AF_INET; 155 | // IN_LINKLOCALNETNUM is defined in as 169.254.0.0 156 | localWifiAddress.sin_addr.s_addr = htonl(IN_LINKLOCALNETNUM); 157 | Reachability *retVal = [self reachabilityWithAddress:&localWifiAddress]; 158 | if (retVal != NULL) { 159 | retVal->localWiFiRef = YES; 160 | } 161 | return retVal; 162 | } 163 | 164 | #pragma mark Network Flag Handling 165 | 166 | - (NetworkStatus)localWiFiStatusForFlags:(SCNetworkReachabilityFlags)flags 167 | { 168 | PrintReachabilityFlags(flags, "localWiFiStatusForFlags"); 169 | 170 | BOOL retVal = NotReachable; 171 | if ((flags & kSCNetworkReachabilityFlagsReachable) && (flags & kSCNetworkReachabilityFlagsIsDirect)) { 172 | retVal = ReachableViaWiFi; 173 | } 174 | return retVal; 175 | } 176 | 177 | - (NetworkStatus)networkStatusForFlags:(SCNetworkReachabilityFlags)flags 178 | { 179 | PrintReachabilityFlags(flags, "networkStatusForFlags"); 180 | if ((flags & kSCNetworkReachabilityFlagsReachable) == 0) { 181 | // if target host is not reachable 182 | return NotReachable; 183 | } 184 | 185 | BOOL retVal = NotReachable; 186 | 187 | if ((flags & kSCNetworkReachabilityFlagsConnectionRequired) == 0) { 188 | // if target host is reachable and no connection is required 189 | // then we'll assume (for now) that your on Wi-Fi 190 | retVal = ReachableViaWiFi; 191 | } 192 | 193 | 194 | if ((((flags & kSCNetworkReachabilityFlagsConnectionOnDemand) != 0) || 195 | (flags & kSCNetworkReachabilityFlagsConnectionOnTraffic) != 0)) { 196 | // ... and the connection is on-demand (or on-traffic) if the 197 | // calling application is using the CFSocketStream or higher APIs 198 | 199 | if ((flags & kSCNetworkReachabilityFlagsInterventionRequired) == 0) { 200 | // ... and no [user] intervention is needed 201 | retVal = ReachableViaWiFi; 202 | } 203 | } 204 | 205 | if ((flags & kSCNetworkReachabilityFlagsIsWWAN) == kSCNetworkReachabilityFlagsIsWWAN) { 206 | // ... but WWAN connections are OK if the calling application 207 | // is using the CFNetwork (CFSocketStream?) APIs. 208 | retVal = ReachableViaWWAN; 209 | } 210 | return retVal; 211 | } 212 | 213 | - (BOOL)connectionRequired; 214 | { 215 | NSAssert(reachabilityRef != NULL, @"connectionRequired called with NULL reachabilityRef"); 216 | SCNetworkReachabilityFlags flags; 217 | if (SCNetworkReachabilityGetFlags(reachabilityRef, &flags)) { 218 | return (flags & kSCNetworkReachabilityFlagsConnectionRequired); 219 | } 220 | return NO; 221 | } 222 | 223 | - (NetworkStatus)currentReachabilityStatus 224 | { 225 | NSAssert(reachabilityRef != NULL, @"currentNetworkStatus called with NULL reachabilityRef"); 226 | NetworkStatus retVal = NotReachable; 227 | SCNetworkReachabilityFlags flags; 228 | if (SCNetworkReachabilityGetFlags(reachabilityRef, &flags)) { 229 | if (localWiFiRef) { 230 | retVal = [self localWiFiStatusForFlags:flags]; 231 | } 232 | else { 233 | retVal = [self networkStatusForFlags:flags]; 234 | } 235 | } 236 | return retVal; 237 | } 238 | @end 239 | -------------------------------------------------------------------------------- /WebViewLongPress/WKWebViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // WKWebViewController.m 3 | // WebViewLongPress 4 | // 5 | // Created by guoqingwei on 2017/2/15. 6 | // Copyright © 2017年 cvte. All rights reserved. 7 | // 8 | 9 | #import "WKWebViewController.h" 10 | #import "FSActionSheet.h" 11 | 12 | #define iOS7_OR_EARLY ([[[UIDevice currentDevice] systemVersion] floatValue] < 8.0) 13 | 14 | static const NSTimeInterval KLongGestureInterval = 0.6f; 15 | 16 | typedef NS_ENUM(NSInteger, WKSelectItem) { 17 | WKSelectItemSaveImage, 18 | WKSelectItemQRExtract 19 | }; 20 | 21 | @interface WKWebViewController () 22 | 23 | @property (nonatomic, strong) NSString *qrCodeString; 24 | 25 | @property (nonatomic, strong) UIImage *saveimage; 26 | 27 | @end 28 | 29 | @implementation WKWebViewController 30 | 31 | 32 | - (void)viewDidLoad 33 | { 34 | [super viewDidLoad]; 35 | 36 | self.title = @"WKWebView"; 37 | 38 | NSString *urlString = @"mp.weixin.qq.com/s?__biz=MzI2ODAzODAzMw==&mid=2650057120&idx=2&sn=c875f7d03ea3823e8dcb3dc4d0cff51d&scene=0#wechat_redirect"; 39 | 40 | self.url = [self autoFillURL:[NSURL URLWithString:urlString]]; 41 | 42 | [self.view addSubview:self.webView]; 43 | 44 | [self.webView loadRequest:[[NSURLRequest alloc] initWithURL:self.url]]; 45 | } 46 | 47 | - (WKWebView *)webView 48 | { 49 | if (_webView == nil) { 50 | 51 | // this js script help to disable callouts in WKWebView. 52 | NSString *source = @"var style = document.createElement('style'); \ 53 | style.type = 'text/css'; \ 54 | style.innerText = '*:not(input):not(textarea) { -webkit-user-select: none; -webkit-touch-callout: none; }'; \ 55 | var head = document.getElementsByTagName('head')[0];\ 56 | head.appendChild(style);"; 57 | 58 | WKUserScript *script = [[WKUserScript alloc] initWithSource:source injectionTime:WKUserScriptInjectionTimeAtDocumentEnd forMainFrameOnly:YES]; 59 | 60 | // Create the user content controller and add the script to it 61 | WKUserContentController *userContentController = [WKUserContentController new]; 62 | [userContentController addUserScript:script]; 63 | 64 | // Create the configuration with the user content controller 65 | WKWebViewConfiguration *configuration = [WKWebViewConfiguration new]; 66 | configuration.userContentController = userContentController; 67 | 68 | 69 | //begin 70 | //I don't know why? When I add this code snip, it works fine! Otherwise, it works bad. 71 | UIView *view = [[UIView alloc] initWithFrame:CGRectZero]; 72 | [self.view addSubview:view]; 73 | // end 74 | 75 | _webView = [[WKWebView alloc] initWithFrame:self.view.bounds configuration:configuration]; 76 | _webView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; 77 | _webView.contentMode = UIViewContentModeRedraw; 78 | _webView.navigationDelegate = self; 79 | _webView.allowsBackForwardNavigationGestures = YES; 80 | 81 | UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleLongPress:)]; 82 | longPress.minimumPressDuration = KLongGestureInterval; 83 | longPress.allowableMovement = 20.f; 84 | longPress.delegate = self; 85 | [_webView addGestureRecognizer:longPress]; 86 | } 87 | return _webView; 88 | } 89 | 90 | 91 | #pragma mark - WKNavigationDelegate 92 | 93 | - (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation 94 | { 95 | NSLog(@"start load"); 96 | } 97 | 98 | - (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(WKNavigation *)navigation withError:(NSError *)error 99 | { 100 | NSLog(@"fail load"); 101 | } 102 | 103 | - (void)webView:(WKWebView *)webView didFailNavigation:(WKNavigation *)navigation withError:(NSError *)error 104 | { 105 | NSLog(@"fali navigation"); 106 | } 107 | 108 | - (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation 109 | { 110 | NSLog(@"finish load"); 111 | } 112 | 113 | #pragma mark - UIGestureRecognizerDelegate 114 | 115 | - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer 116 | { 117 | return YES; 118 | } 119 | 120 | #pragma mark - FSActionSheetDelegate 121 | 122 | - (void)FSActionSheet:(FSActionSheet *)actionSheet selectedIndex:(NSInteger)selectedIndex 123 | { 124 | switch (selectedIndex) { 125 | case WKSelectItemSaveImage: 126 | { 127 | UIImageWriteToSavedPhotosAlbum(self.saveimage, self, @selector(image:didFinishSavingWithError:contextInfo:), nil); 128 | } 129 | break; 130 | case WKSelectItemQRExtract: 131 | { 132 | NSURL *qrUrl = [NSURL URLWithString:self.qrCodeString]; 133 | //open with safari 134 | if ([[UIApplication sharedApplication] canOpenURL:qrUrl]) { 135 | [[UIApplication sharedApplication] openURL:qrUrl]; 136 | } 137 | // open in inner webview 138 | //[self.webView loadRequest:[NSURLRequest requestWithURL:qrUrl]]; 139 | } 140 | break; 141 | 142 | default: 143 | break; 144 | } 145 | } 146 | 147 | #pragma mark - Save image callback 148 | 149 | - (void)image:(UIImage *)image didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo 150 | { 151 | NSString *message = @"Succeed"; 152 | 153 | if (error) { 154 | message = @"Fail"; 155 | } 156 | NSLog(@"save result :%@", message); 157 | } 158 | 159 | #pragma mark - Private methods 160 | 161 | - (void)handleLongPress:(UILongPressGestureRecognizer *)sender 162 | { 163 | if (sender.state != UIGestureRecognizerStateBegan) { 164 | return; 165 | } 166 | 167 | CGPoint touchPoint = [sender locationInView:self.webView]; 168 | // get image url where pressed. 169 | NSString *imgJS = [NSString stringWithFormat:@"document.elementFromPoint(%f, %f).src", touchPoint.x, touchPoint.y]; 170 | 171 | [self.webView evaluateJavaScript:imgJS completionHandler:^(id _Nullable imageUrl, NSError * _Nullable error) { 172 | 173 | if (imageUrl) { 174 | 175 | NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:imageUrl]]; 176 | 177 | UIImage *image = [UIImage imageWithData:data]; 178 | if (!image) { 179 | NSLog(@"read fail"); 180 | return; 181 | } 182 | self.saveimage = image; 183 | 184 | FSActionSheet *actionSheet = nil; 185 | 186 | if ([self isAvailableQRcodeIn:image]) { 187 | 188 | actionSheet = [[FSActionSheet alloc] initWithTitle:nil 189 | delegate:self 190 | cancelButtonTitle:@"Cancel" 191 | highlightedButtonTitle:nil 192 | otherButtonTitles:@[@"Save Image", @"Extract QR code"]]; 193 | 194 | } else { 195 | 196 | actionSheet = [[FSActionSheet alloc] initWithTitle:nil 197 | delegate:self 198 | cancelButtonTitle:@"Cancel" 199 | highlightedButtonTitle:nil 200 | otherButtonTitles:@[@"Save Image"]]; 201 | } 202 | [actionSheet show]; 203 | } 204 | }]; 205 | } 206 | 207 | - (BOOL)isAvailableQRcodeIn:(UIImage *)img 208 | { 209 | if (iOS7_OR_EARLY) { 210 | return NO; 211 | } 212 | 213 | //Extract QR code by screenshot 214 | //UIImage *image = [self snapshot:self.view]; 215 | 216 | UIImage *image = [self imageByInsetEdge:UIEdgeInsetsMake(-20, -20, -20, -20) withColor:[UIColor lightGrayColor] withImage:img]; 217 | 218 | CIDetector *detector = [CIDetector detectorOfType:CIDetectorTypeQRCode context:nil options:@{}]; 219 | 220 | NSArray *features = [detector featuresInImage:[CIImage imageWithCGImage:image.CGImage]]; 221 | 222 | if (features.count >= 1) { 223 | CIQRCodeFeature *feature = [features objectAtIndex:0]; 224 | 225 | self.qrCodeString = [feature.messageString copy]; 226 | 227 | NSLog(@"QR result :%@", self.qrCodeString); 228 | 229 | return YES; 230 | } else { 231 | NSLog(@"No QR"); 232 | return NO; 233 | } 234 | } 235 | 236 | // you can also implement by UIView category 237 | - (UIImage *)snapshot:(UIView *)view 238 | { 239 | UIGraphicsBeginImageContextWithOptions(view.bounds.size, YES, view.window.screen.scale); 240 | 241 | if ([view respondsToSelector:@selector(drawViewHierarchyInRect:afterScreenUpdates:)]) { 242 | [view drawViewHierarchyInRect:view.bounds afterScreenUpdates:YES]; 243 | } 244 | UIImage* image = UIGraphicsGetImageFromCurrentImageContext(); 245 | 246 | UIGraphicsEndImageContext(); 247 | 248 | return image; 249 | } 250 | 251 | // you can also implement by UIImage category 252 | - (UIImage *)imageByInsetEdge:(UIEdgeInsets)insets withColor:(UIColor *)color withImage:(UIImage *)image 253 | { 254 | CGSize size = image.size; 255 | size.width -= insets.left + insets.right; 256 | size.height -= insets.top + insets.bottom; 257 | if (size.width <= 0 || size.height <= 0) { 258 | return nil; 259 | } 260 | CGRect rect = CGRectMake(-insets.left, -insets.top, image.size.width, image.size.height); 261 | UIGraphicsBeginImageContextWithOptions(size, NO, image.scale); 262 | CGContextRef context = UIGraphicsGetCurrentContext(); 263 | if (color) { 264 | CGContextSetFillColorWithColor(context, color.CGColor); 265 | CGMutablePathRef path = CGPathCreateMutable(); 266 | CGPathAddRect(path, NULL, CGRectMake(0, 0, size.width, size.height)); 267 | CGPathAddRect(path, NULL, rect); 268 | CGContextAddPath(context, path); 269 | CGContextEOFillPath(context); 270 | CGPathRelease(path); 271 | } 272 | [image drawInRect:rect]; 273 | UIImage *insetEdgedImage = UIGraphicsGetImageFromCurrentImageContext(); 274 | UIGraphicsEndImageContext(); 275 | 276 | return insetEdgedImage; 277 | } 278 | 279 | - (NSURL *)autoFillURL:(NSURL *)url 280 | { 281 | //If no URL scheme was supplied, defer back to HTTP. 282 | if (url.scheme.length == 0) { 283 | url = [NSURL URLWithString:[NSString stringWithFormat:@"http://%@", [url absoluteString]]]; 284 | } 285 | 286 | return url; 287 | } 288 | 289 | @end 290 | -------------------------------------------------------------------------------- /WebViewLongPress/Vendor/RNCachingURLProtocol/RNCachingURLProtocol.m: -------------------------------------------------------------------------------- 1 | // 2 | // RNCachingURLProtocol.m 3 | // 4 | // Created by Robert Napier on 1/10/12. 5 | // Copyright (c) 2012 Rob Napier. 6 | // 7 | // This code is licensed under the MIT License: 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a 10 | // copy of this software and associated documentation files (the "Software"), 11 | // to deal in the Software without restriction, including without limitation 12 | // the rights to use, copy, modify, merge, publish, distribute, sublicense, 13 | // and/or sell copies of the Software, and to permit persons to whom the 14 | // Software is furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in 17 | // all copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 24 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | // DEALINGS IN THE SOFTWARE. 26 | // 27 | 28 | #import "RNCachingURLProtocol.h" 29 | #import "Reachability.h" 30 | #import "NSString+Sha1.h" 31 | 32 | #define WORKAROUND_MUTABLE_COPY_LEAK 1 33 | 34 | #if WORKAROUND_MUTABLE_COPY_LEAK 35 | // required to workaround http://openradar.appspot.com/11596316 36 | @interface NSURLRequest(MutableCopyWorkaround) 37 | 38 | - (id) mutableCopyWorkaround; 39 | 40 | @end 41 | #endif 42 | 43 | 44 | 45 | static NSString *RNCachingURLHeader = @"X-RNCache"; 46 | 47 | @interface RNCachingURLProtocol () // iOS5-only 48 | @property (nonatomic, readwrite, strong) NSURLConnection *connection; 49 | @property (nonatomic, readwrite, strong) NSMutableData *data; 50 | @property (nonatomic, readwrite, strong) NSURLResponse *response; 51 | - (void)appendData:(NSData *)newData; 52 | @end 53 | 54 | static NSObject *RNCachingSupportedSchemesMonitor; 55 | static NSSet *RNCachingSupportedSchemes; 56 | 57 | @implementation RNCachingURLProtocol 58 | @synthesize connection = connection_; 59 | @synthesize data = data_; 60 | @synthesize response = response_; 61 | 62 | + (void)initialize 63 | { 64 | if (self == [RNCachingURLProtocol class]) 65 | { 66 | static dispatch_once_t onceToken; 67 | dispatch_once(&onceToken, ^{ 68 | RNCachingSupportedSchemesMonitor = [NSObject new]; 69 | }); 70 | 71 | [self setSupportedSchemes:[NSSet setWithObject:@"http"]]; 72 | } 73 | } 74 | 75 | + (BOOL)canInitWithRequest:(NSURLRequest *)request 76 | { 77 | // only handle http requests we haven't marked with our header. 78 | if ([[self supportedSchemes] containsObject:[[request URL] scheme]] && 79 | ([request valueForHTTPHeaderField:RNCachingURLHeader] == nil)) 80 | { 81 | return YES; 82 | } 83 | return NO; 84 | } 85 | 86 | + (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request 87 | { 88 | return request; 89 | } 90 | 91 | + (NSString *)cachePathForURLString:(NSString *)url{ 92 | NSString *cachesPath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject]; 93 | NSString *fileName = [url sha1]; 94 | return [cachesPath stringByAppendingPathComponent:fileName]; 95 | } 96 | 97 | - (NSString *)cachePathForRequest:(NSURLRequest *)aRequest 98 | { 99 | // This stores in the Caches directory, which can be deleted when space is low, but we only use it for offline access 100 | NSString *cachesPath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject]; 101 | NSString *fileName = [[[aRequest URL] absoluteString] sha1]; 102 | 103 | return [cachesPath stringByAppendingPathComponent:fileName]; 104 | } 105 | 106 | - (void)startLoading 107 | { 108 | if (![self useCache]) { 109 | NSMutableURLRequest *connectionRequest = 110 | #if WORKAROUND_MUTABLE_COPY_LEAK 111 | [[self request] mutableCopyWorkaround]; 112 | #else 113 | [[self request] mutableCopy]; 114 | #endif 115 | // we need to mark this request with our header so we know not to handle it in +[NSURLProtocol canInitWithRequest:]. 116 | [connectionRequest setValue:@"" forHTTPHeaderField:RNCachingURLHeader]; 117 | NSURLConnection *connection = [NSURLConnection connectionWithRequest:connectionRequest 118 | delegate:self]; 119 | [self setConnection:connection]; 120 | } 121 | else { 122 | RNCachedData *cache = [NSKeyedUnarchiver unarchiveObjectWithFile:[self cachePathForRequest:[self request]]]; 123 | if (cache) { 124 | NSData *data = [cache data]; 125 | NSURLResponse *response = [cache response]; 126 | NSURLRequest *redirectRequest = [cache redirectRequest]; 127 | if (redirectRequest) { 128 | [[self client] URLProtocol:self wasRedirectedToRequest:redirectRequest redirectResponse:response]; 129 | } else { 130 | 131 | [[self client] URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed]; // we handle caching ourselves. 132 | [[self client] URLProtocol:self didLoadData:data]; 133 | [[self client] URLProtocolDidFinishLoading:self]; 134 | } 135 | } 136 | else { 137 | [[self client] URLProtocol:self didFailWithError:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorCannotConnectToHost userInfo:nil]]; 138 | } 139 | } 140 | } 141 | 142 | - (void)stopLoading 143 | { 144 | [[self connection] cancel]; 145 | } 146 | 147 | // NSURLConnection delegates (generally we pass these on to our client) 148 | 149 | - (NSURLRequest *)connection:(NSURLConnection *)connection willSendRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)response 150 | { 151 | // Thanks to Nick Dowell https://gist.github.com/1885821 152 | if (response != nil) { 153 | NSMutableURLRequest *redirectableRequest = 154 | #if WORKAROUND_MUTABLE_COPY_LEAK 155 | [request mutableCopyWorkaround]; 156 | #else 157 | [request mutableCopy]; 158 | #endif 159 | // We need to remove our header so we know to handle this request and cache it. 160 | // There are 3 requests in flight: the outside request, which we handled, the internal request, 161 | // which we marked with our header, and the redirectableRequest, which we're modifying here. 162 | // The redirectable request will cause a new outside request from the NSURLProtocolClient, which 163 | // must not be marked with our header. 164 | [redirectableRequest setValue:nil forHTTPHeaderField:RNCachingURLHeader]; 165 | 166 | NSString *cachePath = [self cachePathForRequest:[self request]]; 167 | RNCachedData *cache = [RNCachedData new]; 168 | [cache setResponse:response]; 169 | [cache setData:[self data]]; 170 | [cache setRedirectRequest:redirectableRequest]; 171 | [NSKeyedArchiver archiveRootObject:cache toFile:cachePath]; 172 | [[self client] URLProtocol:self wasRedirectedToRequest:redirectableRequest redirectResponse:response]; 173 | return redirectableRequest; 174 | } else { 175 | return request; 176 | } 177 | } 178 | 179 | - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data 180 | { 181 | [[self client] URLProtocol:self didLoadData:data]; 182 | [self appendData:data]; 183 | } 184 | 185 | - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error 186 | { 187 | [[self client] URLProtocol:self didFailWithError:error]; 188 | [self setConnection:nil]; 189 | [self setData:nil]; 190 | [self setResponse:nil]; 191 | } 192 | 193 | - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response 194 | { 195 | [self setResponse:response]; 196 | [[self client] URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed]; // We cache ourselves. 197 | } 198 | 199 | - (void)connectionDidFinishLoading:(NSURLConnection *)connection 200 | { 201 | [[self client] URLProtocolDidFinishLoading:self]; 202 | 203 | NSString *cachePath = [self cachePathForRequest:[self request]]; 204 | RNCachedData *cache = [RNCachedData new]; 205 | [cache setResponse:[self response]]; 206 | [cache setData:[self data]]; 207 | [NSKeyedArchiver archiveRootObject:cache toFile:cachePath]; 208 | 209 | [self setConnection:nil]; 210 | [self setData:nil]; 211 | [self setResponse:nil]; 212 | } 213 | 214 | - (BOOL) useCache 215 | { 216 | BOOL reachable = (BOOL) [[Reachability reachabilityWithHostName:[[[self request] URL] host]] currentReachabilityStatus] != NotReachable; 217 | return !reachable; 218 | } 219 | 220 | - (void)appendData:(NSData *)newData 221 | { 222 | if ([self data] == nil) { 223 | [self setData:[newData mutableCopy]]; 224 | } 225 | else { 226 | [[self data] appendData:newData]; 227 | } 228 | } 229 | 230 | + (NSSet *)supportedSchemes { 231 | NSSet *supportedSchemes; 232 | @synchronized(RNCachingSupportedSchemesMonitor) 233 | { 234 | supportedSchemes = RNCachingSupportedSchemes; 235 | } 236 | return supportedSchemes; 237 | } 238 | 239 | + (void)setSupportedSchemes:(NSSet *)supportedSchemes 240 | { 241 | @synchronized(RNCachingSupportedSchemesMonitor) 242 | { 243 | RNCachingSupportedSchemes = supportedSchemes; 244 | } 245 | } 246 | 247 | @end 248 | 249 | static NSString *const kDataKey = @"data"; 250 | static NSString *const kResponseKey = @"response"; 251 | static NSString *const kRedirectRequestKey = @"redirectRequest"; 252 | 253 | @implementation RNCachedData 254 | @synthesize data = data_; 255 | @synthesize response = response_; 256 | @synthesize redirectRequest = redirectRequest_; 257 | 258 | - (void)encodeWithCoder:(NSCoder *)aCoder 259 | { 260 | [aCoder encodeObject:[self data] forKey:kDataKey]; 261 | [aCoder encodeObject:[self response] forKey:kResponseKey]; 262 | [aCoder encodeObject:[self redirectRequest] forKey:kRedirectRequestKey]; 263 | } 264 | 265 | - (id)initWithCoder:(NSCoder *)aDecoder 266 | { 267 | self = [super init]; 268 | if (self != nil) { 269 | [self setData:[aDecoder decodeObjectForKey:kDataKey]]; 270 | [self setResponse:[aDecoder decodeObjectForKey:kResponseKey]]; 271 | [self setRedirectRequest:[aDecoder decodeObjectForKey:kRedirectRequestKey]]; 272 | } 273 | 274 | return self; 275 | } 276 | 277 | @end 278 | 279 | #if WORKAROUND_MUTABLE_COPY_LEAK 280 | @implementation NSURLRequest(MutableCopyWorkaround) 281 | 282 | - (id) mutableCopyWorkaround { 283 | NSMutableURLRequest *mutableURLRequest = [[NSMutableURLRequest alloc] initWithURL:[self URL] 284 | cachePolicy:[self cachePolicy] 285 | timeoutInterval:[self timeoutInterval]]; 286 | [mutableURLRequest setAllHTTPHeaderFields:[self allHTTPHeaderFields]]; 287 | if ([self HTTPBodyStream]) { 288 | [mutableURLRequest setHTTPBodyStream:[self HTTPBodyStream]]; 289 | } else { 290 | [mutableURLRequest setHTTPBody:[self HTTPBody]]; 291 | } 292 | [mutableURLRequest setHTTPMethod:[self HTTPMethod]]; 293 | 294 | return mutableURLRequest; 295 | } 296 | 297 | @end 298 | #endif 299 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## UIWebView长按保存图片和识别图片二维码的实现方案(使用缓存) 2 | ### 0x00 需求:长按识别UIWebView中的二维码,如下图 3 | 4 | ![长按识别二维码](./snapshot/003.png) 5 | 6 | ### 0x01 方案1: 7 | 给UIWebView增加一个长按手势,激活长按手势时获取当前UIWebView的截图,分析是否包含二维码。 8 | 9 | **核心代码**:略 10 | 11 | **优点**:流程简单,可以快速实现。 12 | 13 | **不足**:无法实现保存UIWebView中图片,如果当前WebView二维码显示不全或者多个二维码,使用这种方式实现的二维码识别也会有问题; 14 | 15 | ### 0x02 方案2: 16 | 长按UIWebView时,获取手指单击位置的图片的URL地址。这种方案是通过获取手指点击的位置,然后获取该位置的标签的src属性,进而获取到url。 17 | 18 | **核心代码**: 19 | 20 | ``` 21 | @interface CVWebViewController () 22 | 23 | @property (weak, nonatomic) IBOutlet UIWebView *webView; 24 | 25 | @end 26 | 27 | @implementation CVWebViewController 28 | 29 | - (void)viewDidLoad 30 | { 31 | [self.webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://mp.weixin.qq.com/s?__biz=MzI2ODAzODAzMw==&mid=2650057120&idx=2&sn=c875f7d03ea3823e8dcb3dc4d0cff51d&scene=0#wechat_redirect"]]]; 32 | UILongPressGestureRecognizer *longPressed = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(longPressed:)]; 33 | longPressed.delegate = self; 34 | [self.webView addGestureRecognizer:longPressed]; 35 | } 36 | 37 | - (void)longPressed:(UITapGestureRecognizer*)recognizer 38 | { 39 | if (recognizer.state != UIGestureRecognizerStateBegan) { 40 | return; 41 | } 42 | CGPoint touchPoint = [recognizer locationInView:self.webView]; 43 | NSString *js = [NSString stringWithFormat:@"document.elementFromPoint(%f, %f).src", touchPoint.x, touchPoint.y]; 44 | NSString *imageUrl = [self.webView stringByEvaluatingJavaScriptFromString:js]; 45 | if (imageUrl.length == 0) { 46 | return; 47 | } 48 | NSLog(@"image url:%@",imageUrl); 49 | NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:imageUrl]]; 50 | 51 | UIImage *image = [UIImage imageWithData:data]; 52 | if (image) { 53 | //...... 54 | //save image or Extract QR code 55 | } 56 | } 57 | 58 | -(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer 59 | { 60 | return YES; 61 | } 62 | ``` 63 | 上述代码实现的核心部分就是 64 | 65 | ``` 66 | NSString *js = [NSString stringWithFormat:@"document.elementFromPoint(%f, %f).src", touchPoint.x, touchPoint.y]; 67 | NSString *imageUrl = [self.webView stringByEvaluatingJavaScriptFromString:js]; 68 | ``` 69 | 第一行代码是通过js获取点击位置的标签的src属性; 70 | 71 | 第二行代码是接受向webview注入第一行的js代码后返回的src属性。 72 | 73 | 如果点击位置是图片,那么久可以通过img.src拿到图片的url地址,如果不是就返回空值。 74 | 75 | **效果**: 76 | ![](./snapshot/001.png) 77 | **注意**:由于UIWebView内部是有一个ScrollView,默认情况下不支持多个手势的,因此需要实现UIGestureRecognizerDelegate中的gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:协议,以支持多个手势(增加长按手势)。 78 | 79 | **优点**:通过识别img标签的url属性,既可以实现保存图片的功能,也可以实现识别图片中二维码的功能;该方案不仅仅可以获取img标签的属性,也可以根据需要获取其他标签,例如链接标签\的属性(需调整部分代码,识别tagName)。 80 | 81 | **不足**:每次获取图片,都需要根据url获取,相当于从网络获取,万一图片太大或者网络不好,势必会影响用户体验,方案3中会介绍如何从缓存中获取image数据。 82 | 83 | ### 0x03 方案3: 84 | 利用Runtime,动态地为UIWebView注入一段js代码,获取IMG标签的src属性,然后从UIWebView的缓存中获取image数据。 85 | 86 | webview加载完成图片完成之后,图片数据已经缓存在webview里了,只需找到从缓存中获取这些数据的方法。 87 | 88 | 方案3使用的从缓存中获取image数据原理是:使用NSURLProtocol,webview在处理请求的过程中会调用 89 | 90 | ``` 91 | - (NSURLRequest *)connection:(NSURLConnection *)connection willSendRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)response 92 | ``` 93 | NSURLProtocol把webView请求返回来的data用压缩的方式的存储在cache的文件夹下, 发出请求的时候会先去读取缓存。 94 | 95 | 在github上找到了一个[RNCachingURLProtocol](https://github.com/rnapier/RNCachingURLProtocol),可以方便地从缓存中获取数据。 96 | 97 | 关于[NSURLProtocol](http://www.jianshu.com/p/7c89b8c5482a),能够让你去重新定义苹果的URL[加载系统](https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/URLLoadingSystem/URLLoadingSystem.html#//apple_ref/doc/uid/10000165-BCICJDHA) (URL Loading System)的行为,URL Loading System里有许多类用于处理URL请求,比如NSURL,NSURLRequest,NSURLConnection和NSURLSession等,当URL Loading System使用NSURLRequest去获取资源的时候,它会创建一个NSURLProtocol子类的实例,你不应该直接实例化一个NSURLProtocol,NSURLProtocol看起来像是一个协议,但其实这是一个类,而且必须使用该类的子类,并且需要被注册。 98 | 99 | **核心代码**: 100 | 101 | ``` 102 | static NSString *const kTouchJavaScriptString = 103 | @"document.ontouchstart=function(event){\ 104 | x=event.targetTouches[0].clientX;\ 105 | y=event.targetTouches[0].clientY;\ 106 | document.location=\"myweb:touch:start:\"+x+\":\"+y;};\ 107 | document.ontouchmove=function(event){\ 108 | x=event.targetTouches[0].clientX;\ 109 | y=event.targetTouches[0].clientY;\ 110 | document.location=\"myweb:touch:move:\"+x+\":\"+y;};\ 111 | document.ontouchcancel=function(event){\ 112 | document.location=\"myweb:touch:cancel\";};\ 113 | document.ontouchend=function(event){\ 114 | document.location=\"myweb:touch:end\";};"; 115 | 116 | static NSString *const kImageJS = @"keyForImageJS"; 117 | static NSString *const kImage = @"keyForImage"; 118 | static NSString *const kImageQRString = @"keyForQR"; 119 | 120 | static const NSTimeInterval KLongGestureInterval = 0.8f; 121 | ...... 122 | SwizzlingMethod([self class], @selector(webViewDidStartLoad:), @selector(sl_webViewDidStartLoad:)); 123 | SwizzlingMethod([self class], @selector(webView:shouldStartLoadWithRequest:navigationType:), @selector(sl_webView:shouldStartLoadWithRequest:navigationType:)); 124 | SwizzlingMethod([self class], @selector(webViewDidFinishLoad:), @selector(sl_webViewDidFinishLoad:)); 125 | ...... 126 | - (void)sl_webViewDidStartLoad:(UIWebView *)webView 127 | { 128 | //Add long press gresture for web view 129 | UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleLongPress:)]; 130 | longPress.minimumPressDuration = KLongGestureInterval; 131 | longPress.delegate = self; 132 | [self.webView addGestureRecognizer:longPress]; 133 | 134 | [self sl_webViewDidStartLoad:webView]; 135 | } 136 | 137 | - (BOOL)sl_webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType 138 | { 139 | NSString *requestString = [[request URL] absoluteString]; 140 | 141 | NSArray *components = [requestString componentsSeparatedByString:@":"]; 142 | 143 | if ([components count] > 1 && [(NSString *)[components objectAtIndex:0] isEqualToString:@"myweb"]) { 144 | 145 | if([(NSString *)[components objectAtIndex:1] isEqualToString:@"touch"]) { 146 | 147 | if ([(NSString *)[components objectAtIndex:2] isEqualToString:@"start"]) { 148 | 149 | NSLog(@"touch start!"); 150 | 151 | float pointX = [[components objectAtIndex:3] floatValue]; 152 | float pointY = [[components objectAtIndex:4] floatValue]; 153 | 154 | NSLog(@"touch point (%f, %f)", pointX, pointY); 155 | 156 | NSString *js = [NSString stringWithFormat:@"document.elementFromPoint(%f, %f).tagName", pointX, pointY]; 157 | 158 | NSString * tagName = [self.webView stringByEvaluatingJavaScriptFromString:js]; 159 | 160 | self.imageJS = nil; 161 | if ([tagName isEqualToString:@"IMG"]) { 162 | 163 | self.imageJS = [NSString stringWithFormat:@"document.elementFromPoint(%f, %f).src", pointX, pointY]; 164 | 165 | } 166 | 167 | } else { 168 | 169 | if ([(NSString *)[components objectAtIndex:2] isEqualToString:@"move"]) { 170 | NSLog(@"you are move"); 171 | } else { 172 | if ([(NSString *)[components objectAtIndex:2] isEqualToString:@"end"]) { 173 | NSLog(@"touch end"); 174 | } 175 | } 176 | } 177 | } 178 | 179 | if (self.imageJS) { 180 | NSLog(@"touching image"); 181 | } 182 | 183 | return NO; 184 | } 185 | 186 | return [self sl_webView:webView shouldStartLoadWithRequest:request navigationType:navigationType]; 187 | } 188 | - (void)sl_webViewDidFinishLoad:(UIWebView *)webView 189 | { 190 | //inject js 191 | [webView stringByEvaluatingJavaScriptFromString:kTouchJavaScriptString]; 192 | 193 | [self sl_webViewDidFinishLoad:webView]; 194 | } 195 | ...... 196 | - (void)handleLongPress:(UILongPressGestureRecognizer *)sender 197 | { 198 | if (sender.state != UIGestureRecognizerStateBegan) { 199 | return; 200 | } 201 | 202 | NSString *imageUrl = [self.webView stringByEvaluatingJavaScriptFromString:self.imageJS]; 203 | 204 | if (imageUrl) { 205 | 206 | NSData *data = nil; 207 | NSString *fileName = [RNCachingURLProtocol cachePathForURLString:imageUrl]; 208 | 209 | RNCachedData *cache = [NSKeyedUnarchiver unarchiveObjectWithFile:fileName]; 210 | 211 | if (cache) { 212 | NSLog(@"read from cache"); 213 | data = cache.data; 214 | } else{ 215 | NSLog(@"read from url"); 216 | data = [NSData dataWithContentsOfURL:[NSURL URLWithString:imageUrl]]; 217 | } 218 | 219 | UIImage *image = [UIImage imageWithData:data]; 220 | if (!image) { 221 | NSLog(@"read fail"); 222 | return; 223 | } 224 | self.image = image; 225 | 226 | FSActionSheet *actionSheet = nil; 227 | 228 | if ([self isAvailableQRcodeIn:image]) { 229 | 230 | actionSheet = [[FSActionSheet alloc] initWithTitle:nil 231 | delegate:self 232 | cancelButtonTitle:@"Cancel" 233 | highlightedButtonTitle:nil 234 | otherButtonTitles:@[@"Save Image", @"Extract QR code"]]; 235 | 236 | } else { 237 | 238 | actionSheet = [[FSActionSheet alloc] initWithTitle:nil 239 | delegate:self 240 | cancelButtonTitle:@"Cancel" 241 | highlightedButtonTitle:nil 242 | otherButtonTitles:@[@"Save Image"]]; 243 | } 244 | [actionSheet show]; 245 | 246 | } 247 | } 248 | ...... 249 | - (BOOL)isAvailableQRcodeIn:(UIImage *)img 250 | { 251 | if (iOS7_OR_EARLY) { 252 | return NO; 253 | } 254 | 255 | //Extract QR code by screenshot 256 | //UIImage *image = [self snapshot:self.view]; 257 | 258 | // IF image is a full qr code, CIDetector can not detect qr string, I am not sure why. 259 | UIImage *image = [self imageByInsetEdge:UIEdgeInsetsMake(-20, -20, -20, -20) withColor:[UIColor lightGrayColor] withImage:img]; 260 | 261 | CIDetector *detector = [CIDetector detectorOfType:CIDetectorTypeQRCode context:nil options:@{}]; 262 | 263 | NSArray *features = [detector featuresInImage:[CIImage imageWithCGImage:image.CGImage]]; 264 | 265 | if (features.count >= 1) { 266 | CIQRCodeFeature *feature = [features objectAtIndex:0]; 267 | 268 | self.qrCodeString = [feature.messageString copy]; 269 | 270 | NSLog(@"QR result :%@", self.qrCodeString); 271 | 272 | return YES; 273 | } else { 274 | NSLog(@"No QR"); 275 | return NO; 276 | } 277 | } 278 | ...... 279 | 280 | ``` 281 | **效果**: 282 | 283 | ![图片中不包含二维码](./snapshot/002.png) 284 | ![图片中包含二维码](./snapshot/003.png) 285 | 286 | **优点**:充分利用了缓存,提高了用户体验。 287 | 288 | **不足**:实现略复杂。 289 | ### 0x04 思考 290 | 如果是非IMG标签提供的图片,例如div的background image,该如何获取和保存? 291 | 292 | WKWebView上的实现。 293 | 294 | ### 0x05 Demo地址 295 | 296 | [https://github.com/guoxiucai/WebViewLongPress](https://github.com/guoxiucai/WebViewLongPress) 297 | 298 | ### 0x06 参考和致谢 299 | [iOS QRcode识别及相册图片二维码读取识别](http://www.jianshu.com/p/48e44fe67c1d) 300 | 301 | [UIWebView保存图片](http://www.cocoachina.com/ios/20160616/16660.html) 302 | 303 | [RNCachingURLProtocol](https://github.com/rnapier/RNCachingURLProtocol) 304 | 305 | [EndLess](https://github.com/jcs/endless) 306 | 307 | [FSActionSheet](https://github.com/lifution/FSActionSheet) 308 | 309 | 310 | 311 | 312 | -------------------------------------------------------------------------------- /WebViewLongPress/CVWebViewController+ImageHelper.m: -------------------------------------------------------------------------------- 1 | // 2 | // CVWebViewController+ImageHelper.m 3 | // WebViewLongPress 4 | // 5 | // Created by guoqingwei on 16/6/14. 6 | // Copyright © 2016年 cvte. All rights reserved. 7 | // 8 | 9 | #import "CVWebViewController+ImageHelper.h" 10 | #import "SwizzeMethod.h" 11 | #import "RNCachingURLProtocol.h" 12 | #import 13 | 14 | typedef NS_ENUM(NSInteger, SelectItem) { 15 | SelectItemSaveImage, 16 | SelectItemQRExtract 17 | }; 18 | 19 | #define iOS7_OR_EARLY ([[[UIDevice currentDevice] systemVersion] floatValue] < 8.0) 20 | 21 | //injected javascript 22 | static NSString *const kTouchJavaScriptString = 23 | @"document.ontouchstart=function(event){\ 24 | x=event.targetTouches[0].clientX;\ 25 | y=event.targetTouches[0].clientY;\ 26 | document.location=\"myweb:touch:start:\"+x+\":\"+y;};\ 27 | document.ontouchmove=function(event){\ 28 | x=event.targetTouches[0].clientX;\ 29 | y=event.targetTouches[0].clientY;\ 30 | document.location=\"myweb:touch:move:\"+x+\":\"+y;};\ 31 | document.ontouchcancel=function(event){\ 32 | document.location=\"myweb:touch:cancel\";};\ 33 | document.ontouchend=function(event){\ 34 | document.location=\"myweb:touch:end\";};"; 35 | 36 | static NSString *const kImageJS = @"keyForImageJS"; 37 | static NSString *const kImage = @"keyForImage"; 38 | static NSString *const kImageQRString = @"keyForQR"; 39 | 40 | static const NSTimeInterval KLongGestureInterval = 0.8f; 41 | 42 | 43 | @implementation CVWebViewController (ImageHelper) 44 | 45 | +(void)load 46 | { 47 | [super load]; 48 | static dispatch_once_t onceToken; 49 | dispatch_once(&onceToken, ^{ 50 | [self hookWebView]; 51 | }); 52 | } 53 | 54 | + (void)hookWebView 55 | { 56 | SwizzlingMethod([self class], @selector(webViewDidStartLoad:), @selector(sl_webViewDidStartLoad:)); 57 | SwizzlingMethod([self class], @selector(webView:shouldStartLoadWithRequest:navigationType:), @selector(sl_webView:shouldStartLoadWithRequest:navigationType:)); 58 | SwizzlingMethod([self class], @selector(webViewDidFinishLoad:), @selector(sl_webViewDidFinishLoad:)); 59 | } 60 | 61 | #pragma mark - seter and getter 62 | 63 | - (void)setImageJS:(NSString *)imageJS 64 | { 65 | objc_setAssociatedObject(self, &kImageJS, imageJS, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 66 | } 67 | 68 | - (NSString *)imageJS 69 | { 70 | return objc_getAssociatedObject(self, &kImageJS); 71 | } 72 | 73 | - (void)setQrCodeString:(NSString *)qrCodeString 74 | { 75 | objc_setAssociatedObject(self, &kImageQRString, qrCodeString, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 76 | } 77 | 78 | - (NSString *)qrCodeString 79 | { 80 | return objc_getAssociatedObject(self, &kImageQRString); 81 | } 82 | 83 | - (void)setImage:(UIImage *)image 84 | { 85 | objc_setAssociatedObject(self, &kImage, image, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 86 | } 87 | 88 | - (UIImage *)image 89 | { 90 | return objc_getAssociatedObject(self, &kImage); 91 | } 92 | 93 | #pragma mark - Save image callback 94 | 95 | - (void)image:(UIImage *)image didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo 96 | { 97 | NSString *message = @"Succeed"; 98 | 99 | if (error) { 100 | message = @"Fail"; 101 | } 102 | NSLog(@"save result :%@", message); 103 | } 104 | 105 | #pragma mark - FSActionSheetDelegate 106 | 107 | - (void)FSActionSheet:(FSActionSheet *)actionSheet selectedIndex:(NSInteger)selectedIndex 108 | { 109 | [self.webView stringByEvaluatingJavaScriptFromString:@"document.documentElement.style.webkitUserSelect='text';"]; 110 | 111 | switch (selectedIndex) { 112 | case SelectItemSaveImage: 113 | { 114 | UIImageWriteToSavedPhotosAlbum(self.image, self, @selector(image:didFinishSavingWithError:contextInfo:), nil); 115 | } 116 | break; 117 | case SelectItemQRExtract: 118 | { 119 | NSURL *qrUrl = [NSURL URLWithString:self.qrCodeString]; 120 | //open with safari 121 | if ([[UIApplication sharedApplication] canOpenURL:qrUrl]) { 122 | [[UIApplication sharedApplication] openURL:qrUrl]; 123 | } 124 | // open in inner webview 125 | //[self.webView loadRequest:[NSURLRequest requestWithURL:qrUrl]]; 126 | } 127 | break; 128 | 129 | default: 130 | break; 131 | } 132 | } 133 | 134 | #pragma mark - swizing 135 | 136 | - (BOOL)sl_webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType 137 | { 138 | NSString *requestString = [[request URL] absoluteString]; 139 | 140 | NSArray *components = [requestString componentsSeparatedByString:@":"]; 141 | 142 | if ([components count] > 1 && [(NSString *)[components objectAtIndex:0] isEqualToString:@"myweb"]) { 143 | 144 | if([(NSString *)[components objectAtIndex:1] isEqualToString:@"touch"]) { 145 | 146 | if ([(NSString *)[components objectAtIndex:2] isEqualToString:@"start"]) { 147 | 148 | NSLog(@"touch start!"); 149 | 150 | float pointX = [[components objectAtIndex:3] floatValue]; 151 | float pointY = [[components objectAtIndex:4] floatValue]; 152 | 153 | NSLog(@"touch point (%f, %f)", pointX, pointY); 154 | 155 | NSString *js = [NSString stringWithFormat:@"document.elementFromPoint(%f, %f).tagName", pointX, pointY]; 156 | 157 | NSString * tagName = [self.webView stringByEvaluatingJavaScriptFromString:js]; 158 | 159 | self.imageJS = nil; 160 | if ([tagName isEqualToString:@"IMG"]) { 161 | 162 | self.imageJS = [NSString stringWithFormat:@"document.elementFromPoint(%f, %f).src", pointX, pointY]; 163 | 164 | } 165 | 166 | } else { 167 | 168 | if ([(NSString *)[components objectAtIndex:2] isEqualToString:@"move"]) { 169 | NSLog(@"you are move"); 170 | } else { 171 | if ([(NSString *)[components objectAtIndex:2] isEqualToString:@"end"]) { 172 | NSLog(@"touch end"); 173 | } 174 | } 175 | } 176 | } 177 | 178 | if (self.imageJS) { 179 | NSLog(@"touching image"); 180 | } 181 | 182 | return NO; 183 | } 184 | 185 | return [self sl_webView:webView shouldStartLoadWithRequest:request navigationType:navigationType]; 186 | } 187 | 188 | - (void)sl_webViewDidStartLoad:(UIWebView *)webView 189 | { 190 | //Add long press gresture for web view 191 | UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleLongPress:)]; 192 | longPress.minimumPressDuration = KLongGestureInterval; 193 | longPress.allowableMovement = 20; 194 | longPress.delegate = self; 195 | [self.webView addGestureRecognizer:longPress]; 196 | 197 | [self sl_webViewDidStartLoad:webView]; 198 | } 199 | 200 | - (void)sl_webViewDidFinishLoad:(UIWebView *)webView 201 | { 202 | //cache manager 203 | [[NSUserDefaults standardUserDefaults] setInteger:0 forKey:@"WebKitCacheModelPreferenceKey"]; 204 | 205 | //inject js 206 | [webView stringByEvaluatingJavaScriptFromString:kTouchJavaScriptString]; 207 | 208 | [self sl_webViewDidFinishLoad:webView]; 209 | } 210 | 211 | #pragma mark - UIGestureRecognizerDelegate 212 | - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer 213 | { 214 | if (![gestureRecognizer isKindOfClass:[UILongPressGestureRecognizer class]]) 215 | return NO; 216 | 217 | if ([self isTouchingImage]) { 218 | if ([otherGestureRecognizer isKindOfClass:[UILongPressGestureRecognizer class]]) { 219 | otherGestureRecognizer.enabled = NO; 220 | otherGestureRecognizer.enabled = YES; 221 | } 222 | 223 | return YES; 224 | } 225 | 226 | return NO; 227 | } 228 | 229 | #pragma mark - private Method 230 | - (BOOL)isTouchingImage 231 | { 232 | if (self.imageJS) { 233 | return YES; 234 | } 235 | return NO; 236 | } 237 | 238 | - (void)handleLongPress:(UILongPressGestureRecognizer *)sender 239 | { 240 | if (sender.state != UIGestureRecognizerStateBegan) { 241 | return; 242 | } 243 | 244 | NSString *imageUrl = [self.webView stringByEvaluatingJavaScriptFromString:self.imageJS]; 245 | 246 | if (imageUrl) { 247 | 248 | NSData *data = nil; 249 | NSString *fileName = [RNCachingURLProtocol cachePathForURLString:imageUrl]; 250 | 251 | RNCachedData *cache = [NSKeyedUnarchiver unarchiveObjectWithFile:fileName]; 252 | 253 | if (cache) { 254 | NSLog(@"read from cache"); 255 | data = cache.data; 256 | } else{ 257 | NSLog(@"read from url"); 258 | data = [NSData dataWithContentsOfURL:[NSURL URLWithString:imageUrl]]; 259 | } 260 | 261 | UIImage *image = [UIImage imageWithData:data]; 262 | if (!image) { 263 | NSLog(@"read fail"); 264 | return; 265 | } 266 | self.image = image; 267 | 268 | FSActionSheet *actionSheet = nil; 269 | 270 | if ([self isAvailableQRcodeIn:image]) { 271 | 272 | actionSheet = [[FSActionSheet alloc] initWithTitle:nil 273 | delegate:self 274 | cancelButtonTitle:@"Cancel" 275 | highlightedButtonTitle:nil 276 | otherButtonTitles:@[@"Save Image", @"Extract QR code"]]; 277 | 278 | } else { 279 | 280 | actionSheet = [[FSActionSheet alloc] initWithTitle:nil 281 | delegate:self 282 | cancelButtonTitle:@"Cancel" 283 | highlightedButtonTitle:nil 284 | otherButtonTitles:@[@"Save Image"]]; 285 | } 286 | [actionSheet show]; 287 | 288 | } 289 | 290 | } 291 | 292 | - (BOOL)isAvailableQRcodeIn:(UIImage *)img 293 | { 294 | if (iOS7_OR_EARLY) { 295 | return NO; 296 | } 297 | 298 | //Extract QR code by screenshot 299 | //UIImage *image = [self snapshot:self.view]; 300 | 301 | UIImage *image = [self imageByInsetEdge:UIEdgeInsetsMake(-20, -20, -20, -20) withColor:[UIColor lightGrayColor] withImage:img]; 302 | 303 | CIDetector *detector = [CIDetector detectorOfType:CIDetectorTypeQRCode context:nil options:@{}]; 304 | 305 | NSArray *features = [detector featuresInImage:[CIImage imageWithCGImage:image.CGImage]]; 306 | 307 | if (features.count >= 1) { 308 | CIQRCodeFeature *feature = [features objectAtIndex:0]; 309 | 310 | self.qrCodeString = [feature.messageString copy]; 311 | 312 | NSLog(@"QR result :%@", self.qrCodeString); 313 | 314 | return YES; 315 | } else { 316 | NSLog(@"No QR"); 317 | return NO; 318 | } 319 | } 320 | 321 | // you can also implement by UIView category 322 | - (UIImage *)snapshot:(UIView *)view 323 | { 324 | UIGraphicsBeginImageContextWithOptions(view.bounds.size, YES, view.window.screen.scale); 325 | 326 | if ([view respondsToSelector:@selector(drawViewHierarchyInRect:afterScreenUpdates:)]) { 327 | [view drawViewHierarchyInRect:view.bounds afterScreenUpdates:YES]; 328 | } 329 | UIImage* image = UIGraphicsGetImageFromCurrentImageContext(); 330 | 331 | UIGraphicsEndImageContext(); 332 | 333 | return image; 334 | } 335 | 336 | // you can also implement by UIImage category 337 | - (UIImage *)imageByInsetEdge:(UIEdgeInsets)insets withColor:(UIColor *)color withImage:(UIImage *)image 338 | { 339 | CGSize size = image.size; 340 | size.width -= insets.left + insets.right; 341 | size.height -= insets.top + insets.bottom; 342 | if (size.width <= 0 || size.height <= 0) { 343 | return nil; 344 | } 345 | CGRect rect = CGRectMake(-insets.left, -insets.top, image.size.width, image.size.height); 346 | UIGraphicsBeginImageContextWithOptions(size, NO, image.scale); 347 | CGContextRef context = UIGraphicsGetCurrentContext(); 348 | if (color) { 349 | CGContextSetFillColorWithColor(context, color.CGColor); 350 | CGMutablePathRef path = CGPathCreateMutable(); 351 | CGPathAddRect(path, NULL, CGRectMake(0, 0, size.width, size.height)); 352 | CGPathAddRect(path, NULL, rect); 353 | CGContextAddPath(context, path); 354 | CGContextEOFillPath(context); 355 | CGPathRelease(path); 356 | } 357 | [image drawInRect:rect]; 358 | UIImage *insetEdgedImage = UIGraphicsGetImageFromCurrentImageContext(); 359 | UIGraphicsEndImageContext(); 360 | 361 | return insetEdgedImage; 362 | } 363 | 364 | @end 365 | -------------------------------------------------------------------------------- /WebViewLongPress/Vendor/FSActionSheet/FSActionSheet.m: -------------------------------------------------------------------------------- 1 | // 2 | // FSActionSheet.m 3 | // FSActionSheet 4 | // 5 | // Created by Steven on 6/7/16. 6 | // Copyright © 2016年 Steven. All rights reserved. 7 | // 8 | 9 | #import "FSActionSheet.h" 10 | #import "FSActionSheetCell.h" 11 | 12 | CGFloat const kFSActionSheetSectionHeight = 10; ///< 分区间距 13 | 14 | @interface FSActionSheet () 15 | 16 | @property (nonatomic, copy) NSString *title; 17 | @property (nonatomic, copy) NSString *cancelTitle; 18 | @property (nonatomic, copy) NSArray *items; 19 | @property (nonatomic, copy) FSActionSheetHandler selectedHandler; 20 | 21 | @property (nonatomic, strong) UIWindow *window; 22 | @property (nonatomic, weak) UIView *controllerView; 23 | @property (nonatomic, strong) UIView *backView; 24 | @property (nonatomic, strong) UITableView *tableView; 25 | @property (nonatomic, weak) UILabel *titleLabel; 26 | 27 | @property (nonatomic, strong) NSLayoutConstraint *heightConstraint; ///< 内容高度约束 28 | 29 | @end 30 | 31 | static NSString * const kFSActionSheetCellIdentifier = @"kFSActionSheetCellIdentifier"; 32 | 33 | @implementation FSActionSheet 34 | 35 | - (instancetype)init { 36 | return [self initWithTitle:nil cancelTitle:nil items:nil]; 37 | } 38 | 39 | - (void)dealloc { 40 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 41 | 42 | // NSLog(@"-- FSActionSheet did dealloc -- [%p] -- [%p] -- [%p]", _window, _backView, _tableView); 43 | } 44 | 45 | /*! @brief 单文本选项快速初始化 */ 46 | - (instancetype)initWithTitle:(NSString *)title delegate:(id)delegate cancelButtonTitle:(NSString *)cancelButtonTitle highlightedButtonTitle:(NSString *)highlightedButtonTitle otherButtonTitles:(NSArray *)otherButtonTitles { 47 | if (!(self = [super init])) return nil; 48 | 49 | [self baseSetting]; 50 | 51 | NSMutableArray *titleItems = [@[] mutableCopy]; 52 | // 普通按钮 53 | for (NSString *otherTitle in otherButtonTitles) { 54 | if (otherTitle && otherTitle.length > 0) { 55 | [titleItems addObject:FSActionSheetTitleItemMake(FSActionSheetTypeNormal, otherTitle)]; 56 | } 57 | } 58 | // 高亮按钮, 高亮按钮放在最下面. 59 | if (highlightedButtonTitle && highlightedButtonTitle.length > 0) { 60 | [titleItems addObject:FSActionSheetTitleItemMake(FSActionSheetTypeHighlighted, highlightedButtonTitle)]; 61 | } 62 | 63 | self.title = title?:@""; 64 | self.delegate = delegate; 65 | self.cancelTitle = (cancelButtonTitle && cancelButtonTitle.length != 0)?cancelButtonTitle:@"取消"; 66 | self.items = titleItems; 67 | 68 | [self addSubview:self.tableView]; 69 | 70 | return self; 71 | } 72 | 73 | /*! @brief 在外部组装选项按钮item */ 74 | - (instancetype)initWithTitle:(NSString *)title cancelTitle:(NSString *)cancelTitle items:(NSArray *)items { 75 | if (!(self = [super init])) return nil; 76 | 77 | [self baseSetting]; 78 | 79 | self.title = title?:@""; 80 | self.cancelTitle = (cancelTitle && cancelTitle.length != 0)?cancelTitle:@"取消"; 81 | self.items = items?:@[]; 82 | 83 | [self addSubview:self.tableView]; 84 | 85 | return self; 86 | } 87 | 88 | - (void)layoutSubviews { 89 | [super layoutSubviews]; 90 | self.tableView.frame = self.bounds; 91 | } 92 | 93 | // 默认设置 94 | - (void)baseSetting { 95 | self.backgroundColor = FSColorWithString(FSActionSheetBackColor); 96 | self.translatesAutoresizingMaskIntoConstraints = NO; // 允许约束 97 | 98 | _contentAlignment = FSContentAlignmentCenter; // 默认样式为居中 99 | _hideOnTouchOutside = YES; // 默认点击半透明层隐藏弹窗 100 | 101 | // 监听屏幕旋转 102 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(orientationDidChange:) name:UIDeviceOrientationDidChangeNotification object:nil]; 103 | } 104 | 105 | // 屏幕旋转通知回调 106 | - (void)orientationDidChange:(NSNotification *)notification { 107 | if (_title.length > 0) { 108 | // 更新头部标题的高度 109 | CGFloat newHeaderHeight = [self heightForHeaderView]; 110 | CGRect newHeaderRect = _tableView.tableHeaderView.frame; 111 | newHeaderRect.size.height = newHeaderHeight; 112 | _tableView.tableHeaderView.frame = newHeaderRect; 113 | self.tableView.tableHeaderView = self.tableView.tableHeaderView; 114 | // 适配当前内容高度 115 | [self fixContentHeight]; 116 | } 117 | } 118 | 119 | #pragma mark - private 120 | // 计算title在设定宽度下的富文本高度 121 | - (CGFloat)heightForHeaderView { 122 | CGFloat labelHeight = [_titleLabel.attributedText boundingRectWithSize:CGSizeMake(CGRectGetWidth(self.window.frame)-FSActionSheetDefaultMargin*2, MAXFLOAT) options:NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading context:nil].size.height; 123 | CGFloat headerHeight = ceil(labelHeight)+FSActionSheetDefaultMargin*2; 124 | 125 | return headerHeight; 126 | } 127 | 128 | // 整个弹窗内容的高度 129 | - (CGFloat)contentHeight { 130 | CGFloat titleHeight = (_title.length > 0)?[self heightForHeaderView]:0; 131 | CGFloat rowHeightSum = (_items.count+1)*FSActionSheetRowHeight+kFSActionSheetSectionHeight; 132 | CGFloat contentHeight = titleHeight+rowHeightSum; 133 | 134 | return contentHeight; 135 | } 136 | 137 | // 适配屏幕高度, 弹出窗高度不应该高于屏幕的设定比例 138 | - (void)fixContentHeight { 139 | CGFloat contentMaxHeight = CGRectGetHeight(self.window.frame)*FSActionSheetContentMaxScale; 140 | CGFloat contentHeight = [self contentHeight]; 141 | if (contentHeight > contentMaxHeight) { 142 | contentHeight = contentMaxHeight; 143 | self.tableView.scrollEnabled = YES; 144 | } else { 145 | self.tableView.scrollEnabled = NO; 146 | } 147 | 148 | _heightConstraint.constant = contentHeight; 149 | } 150 | 151 | // 适配标题偏移方向 152 | - (void)updateTitleAttributeText { 153 | if (_title.length == 0 || !_titleLabel) return; 154 | 155 | // 富文本相关配置 156 | NSRange attributeRange = NSMakeRange(0, _title.length); 157 | UIFont *titleFont = [UIFont systemFontOfSize:14]; 158 | UIColor *titleTextColor = FSColorWithString(FSActionSheetTitleColor); 159 | CGFloat lineSpacing = FSActionSheetTitleLineSpacing; 160 | CGFloat kernSpacing = FSActionSheetTitleKernSpacing; 161 | 162 | NSMutableAttributedString *titleAttributeString = [[NSMutableAttributedString alloc] initWithString:_title]; 163 | NSMutableParagraphStyle *titleStyle = [[NSMutableParagraphStyle alloc] init]; 164 | // 行距 165 | titleStyle.lineSpacing = lineSpacing; 166 | // 内容偏移样式 167 | switch (_contentAlignment) { 168 | case FSContentAlignmentLeft: { 169 | titleStyle.alignment = NSTextAlignmentLeft; 170 | break; 171 | } 172 | case FSContentAlignmentCenter: { 173 | titleStyle.alignment = NSTextAlignmentCenter; 174 | break; 175 | } 176 | case FSContentAlignmentRight: { 177 | titleStyle.alignment = NSTextAlignmentRight; 178 | break; 179 | } 180 | } 181 | [titleAttributeString addAttribute:NSParagraphStyleAttributeName value:titleStyle range:attributeRange]; 182 | // 字距 183 | [titleAttributeString addAttribute:NSKernAttributeName value:@(kernSpacing) range:attributeRange]; 184 | // 字体 185 | [titleAttributeString addAttribute:NSFontAttributeName value:titleFont range:attributeRange]; 186 | // 颜色 187 | [titleAttributeString addAttribute:NSForegroundColorAttributeName value:titleTextColor range:attributeRange]; 188 | _titleLabel.attributedText = titleAttributeString; 189 | } 190 | 191 | // 点击背景半透明遮罩层隐藏 192 | - (void)backViewGesture { 193 | [self hideWithCompletion:nil]; 194 | } 195 | 196 | // 隐藏 197 | - (void)hideWithCompletion:(void(^)())completion { 198 | [UIView animateWithDuration:0.25 delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{ 199 | _backView.alpha = 0; 200 | CGRect newFrame = self.frame; 201 | newFrame.origin.y = CGRectGetMaxY(_controllerView.frame); 202 | self.frame = newFrame; 203 | } completion:^(BOOL finished) { 204 | [[[UIApplication sharedApplication].delegate window] makeKeyWindow]; 205 | if (completion) completion(); 206 | [_backView removeFromSuperview]; 207 | _backView = nil; 208 | [_tableView removeFromSuperview]; 209 | _tableView = nil; 210 | [self removeFromSuperview]; 211 | self.window = nil; 212 | self.selectedHandler = nil; 213 | }]; 214 | } 215 | 216 | #pragma mark - public 217 | /*! @brief 单展示, 不绑定block回调 */ 218 | - (void)show { 219 | [self showWithSelectedCompletion:NULL]; 220 | } 221 | 222 | /*! @brief 展示并绑定block回调 */ 223 | - (void)showWithSelectedCompletion:(FSActionSheetHandler)selectedHandler { 224 | self.selectedHandler = selectedHandler; 225 | 226 | _backView = [[UIView alloc] init]; 227 | _backView.alpha = 0; 228 | _backView.backgroundColor = [UIColor blackColor]; 229 | _backView.userInteractionEnabled = _hideOnTouchOutside; 230 | _backView.translatesAutoresizingMaskIntoConstraints = NO; 231 | [_backView addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(backViewGesture)]]; 232 | [_controllerView addSubview:_backView]; 233 | [_controllerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[_backView]|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(_backView)]]; 234 | [_controllerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[_backView]|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(_backView)]]; 235 | 236 | [self.tableView reloadData]; 237 | // 内容高度 238 | CGFloat contentHeight = self.tableView.contentSize.height; 239 | // 适配屏幕高度 240 | CGFloat contentMaxHeight = CGRectGetHeight(self.window.frame)*FSActionSheetContentMaxScale; 241 | if (contentHeight > contentMaxHeight) { 242 | self.tableView.scrollEnabled = YES; 243 | contentHeight = contentMaxHeight; 244 | } 245 | [_controllerView addSubview:self]; 246 | 247 | CGFloat selfW = CGRectGetWidth(_controllerView.frame); 248 | CGFloat selfH = contentHeight; 249 | CGFloat selfX = 0; 250 | CGFloat selfY = CGRectGetMaxY(_controllerView.frame); 251 | self.frame = CGRectMake(selfX, selfY, selfW, selfH); 252 | 253 | [self.window makeKeyAndVisible]; 254 | 255 | [UIView animateWithDuration:0.25 delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{ 256 | _backView.alpha = 0.38; 257 | CGRect newFrame = self.frame; 258 | newFrame.origin.y = CGRectGetMaxY(_controllerView.frame)-selfH; 259 | self.frame = newFrame; 260 | } completion:^(BOOL finished) { 261 | // constraint 262 | [_controllerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[self]|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(self)]]; 263 | [_controllerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[self]|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(self)]]; 264 | self.heightConstraint = [NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1 constant:contentHeight]; 265 | [_controllerView addConstraint:_heightConstraint]; 266 | }]; 267 | } 268 | 269 | #pragma mark - getter 270 | - (UIWindow *)window { 271 | if (_window) return _window; 272 | 273 | _window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; 274 | _window.windowLevel = UIWindowLevelStatusBar+1; 275 | _window.rootViewController = [[UIViewController alloc] init]; 276 | self.controllerView = _window.rootViewController.view; 277 | 278 | return _window; 279 | } 280 | - (UITableView *)tableView { 281 | if (_tableView) return _tableView; 282 | 283 | _tableView = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain]; 284 | _tableView.delegate = self; 285 | _tableView.dataSource = self; 286 | _tableView.scrollEnabled = NO; 287 | _tableView.separatorStyle = UITableViewCellSeparatorStyleNone; 288 | _tableView.backgroundColor = self.backgroundColor; 289 | _tableView.autoresizingMask = UIViewAutoresizingFlexibleWidth; 290 | 291 | [_tableView registerClass:[FSActionSheetCell class] forCellReuseIdentifier:kFSActionSheetCellIdentifier]; 292 | 293 | if (_title.length > 0) { 294 | _tableView.tableHeaderView = [self headerView]; 295 | } 296 | 297 | return _tableView; 298 | } 299 | 300 | // tableHeaderView作为title部分 301 | - (UIView *)headerView { 302 | UIView *headerView = [[UIView alloc] init]; 303 | headerView.backgroundColor = FSColorWithString(FSActionSheetRowNormalColor); 304 | 305 | // 标题 306 | UILabel *titleLabel = [[UILabel alloc] init]; 307 | titleLabel.numberOfLines = 0; 308 | titleLabel.backgroundColor = headerView.backgroundColor; 309 | titleLabel.translatesAutoresizingMaskIntoConstraints = NO; 310 | [headerView addSubview:titleLabel]; 311 | self.titleLabel = titleLabel; 312 | // 设置富文本标题内容 313 | [self updateTitleAttributeText]; 314 | 315 | // 标题内容边距 (ps: 要修改这个边距不要在这里修改这个labelMargin, 要到配置类中修改 FSActionSheetDefaultMargin, 不然可能出现界面适配错乱). 316 | CGFloat labelMargin = FSActionSheetDefaultMargin; 317 | // 计算内容高度 318 | CGFloat headerHeight = [self heightForHeaderView]; 319 | headerView.frame = CGRectMake(0, 0, CGRectGetWidth(self.window.frame), headerHeight); 320 | 321 | // titleLabel constraint 322 | [headerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-labelMargin-[titleLabel]-labelMargin-|" options:0 metrics:@{@"labelMargin":@(labelMargin)} views:NSDictionaryOfVariableBindings(titleLabel)]]; 323 | [headerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-labelMargin-[titleLabel]" options:0 metrics:@{@"labelMargin":@(labelMargin)} views:NSDictionaryOfVariableBindings(titleLabel)]]; 324 | 325 | return headerView; 326 | } 327 | 328 | #pragma mark - setter 329 | - (void)setContentAlignment:(FSContentAlignment)contentAlignment { 330 | if (_contentAlignment != contentAlignment) { 331 | _contentAlignment = contentAlignment; 332 | [self updateTitleAttributeText]; 333 | } 334 | } 335 | 336 | #pragma mark - delegate 337 | - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { 338 | return 2; 339 | } 340 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { 341 | return (section == 1)?1:_items.count; 342 | } 343 | - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { 344 | return FSActionSheetRowHeight; 345 | } 346 | - (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section { 347 | return (section == 1)?kFSActionSheetSectionHeight:CGFLOAT_MIN; 348 | } 349 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { 350 | FSActionSheetCell *cell = [tableView dequeueReusableCellWithIdentifier:kFSActionSheetCellIdentifier]; 351 | return cell; 352 | } 353 | - (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath { 354 | FSActionSheetCell *sheetCell = (FSActionSheetCell *)cell; 355 | if (indexPath.section == 0) { 356 | sheetCell.item = _items[indexPath.row]; 357 | sheetCell.hideTopLine = NO; 358 | // 当有标题时隐藏第一单元格的顶部线条 359 | if (indexPath.row == 0 && (!_title || _title.length == 0)) { 360 | sheetCell.hideTopLine = YES; 361 | } 362 | } else { 363 | // 默认取消的单元格没有附带icon 364 | FSActionSheetItem *cancelItem = FSActionSheetTitleItemMake(FSActionSheetTypeNormal, _cancelTitle); 365 | // 如果其它单元格中附带icon的话则添加上默认的取消icon. 366 | for (FSActionSheetItem *item in _items) { 367 | if (item.image) { 368 | cancelItem = FSActionSheetTitleWithImageItemMake(FSActionSheetTypeNormal, [UIImage imageNamed:@"FSActionSheet_cancel"], _cancelTitle); 369 | break; 370 | } 371 | } 372 | sheetCell.item = cancelItem; 373 | sheetCell.hideTopLine = YES; 374 | } 375 | sheetCell.contentAlignment = _contentAlignment; 376 | } 377 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { 378 | // 延迟0.1秒隐藏让用户既看到点击效果又不影响体验 379 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1*NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 380 | __weak __typeof(&*self)weakSelf = self; 381 | [self hideWithCompletion:^{ 382 | if (indexPath.section == 0) { 383 | if (_selectedHandler) { 384 | _selectedHandler(indexPath.row); 385 | } 386 | if (weakSelf.delegate && [weakSelf.delegate respondsToSelector:@selector(FSActionSheet:selectedIndex:)]) { 387 | [weakSelf.delegate FSActionSheet:self selectedIndex:indexPath.row]; 388 | } 389 | } 390 | }]; 391 | }); 392 | } 393 | 394 | @end 395 | -------------------------------------------------------------------------------- /WebViewLongPress.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 4D6DF1FA1D0F96AC00299D2D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 4D6DF1F91D0F96AC00299D2D /* main.m */; }; 11 | 4D6DF1FD1D0F96AC00299D2D /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 4D6DF1FC1D0F96AC00299D2D /* AppDelegate.m */; }; 12 | 4D6DF2031D0F96AC00299D2D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 4D6DF2011D0F96AC00299D2D /* Main.storyboard */; }; 13 | 4D6DF2051D0F96AC00299D2D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4D6DF2041D0F96AC00299D2D /* Assets.xcassets */; }; 14 | 4D6DF2081D0F96AC00299D2D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 4D6DF2061D0F96AC00299D2D /* LaunchScreen.storyboard */; }; 15 | 4D6DF2131D0F96AC00299D2D /* WebViewLongPressTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 4D6DF2121D0F96AC00299D2D /* WebViewLongPressTests.m */; }; 16 | 4D6DF2361D0F9C4800299D2D /* FSActionSheet.m in Sources */ = {isa = PBXBuildFile; fileRef = 4D6DF2241D0F9C4800299D2D /* FSActionSheet.m */; }; 17 | 4D6DF2371D0F9C4800299D2D /* FSActionSheetCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 4D6DF2261D0F9C4800299D2D /* FSActionSheetCell.m */; }; 18 | 4D6DF2381D0F9C4800299D2D /* FSActionSheetConfig.m in Sources */ = {isa = PBXBuildFile; fileRef = 4D6DF2281D0F9C4800299D2D /* FSActionSheetConfig.m */; }; 19 | 4D6DF2391D0F9C4800299D2D /* FSActionSheetItem.m in Sources */ = {isa = PBXBuildFile; fileRef = 4D6DF22A1D0F9C4800299D2D /* FSActionSheetItem.m */; }; 20 | 4D6DF23A1D0F9C4800299D2D /* FSActionSheet_cancel@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 4D6DF22C1D0F9C4800299D2D /* FSActionSheet_cancel@2x.png */; }; 21 | 4D6DF23B1D0F9C4800299D2D /* FSActionSheet_cancel@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 4D6DF22D1D0F9C4800299D2D /* FSActionSheet_cancel@3x.png */; }; 22 | 4D6DF23C1D0F9C4800299D2D /* NSString+Sha1.m in Sources */ = {isa = PBXBuildFile; fileRef = 4D6DF2301D0F9C4800299D2D /* NSString+Sha1.m */; }; 23 | 4D6DF23D1D0F9C4800299D2D /* Reachability.m in Sources */ = {isa = PBXBuildFile; fileRef = 4D6DF2321D0F9C4800299D2D /* Reachability.m */; }; 24 | 4D6DF23E1D0F9C4800299D2D /* RNCachingURLProtocol.m in Sources */ = {isa = PBXBuildFile; fileRef = 4D6DF2341D0F9C4800299D2D /* RNCachingURLProtocol.m */; }; 25 | 4D6DF2411D0F9EF400299D2D /* SwizzeMethod.m in Sources */ = {isa = PBXBuildFile; fileRef = 4D6DF2401D0F9EF400299D2D /* SwizzeMethod.m */; }; 26 | 4D6DF2471D0FA3F900299D2D /* CVWebViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 4D6DF2461D0FA3F900299D2D /* CVWebViewController.m */; }; 27 | 4D6DF24A1D0FA47F00299D2D /* CVWebViewController+ImageHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 4D6DF2491D0FA47F00299D2D /* CVWebViewController+ImageHelper.m */; }; 28 | 4DE18C321E53F31C004C75DB /* MainViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 4DE18C311E53F31C004C75DB /* MainViewController.m */; }; 29 | 4DE18C351E53F3F8004C75DB /* WKWebViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 4DE18C341E53F3F8004C75DB /* WKWebViewController.m */; }; 30 | /* End PBXBuildFile section */ 31 | 32 | /* Begin PBXContainerItemProxy section */ 33 | 4D6DF20F1D0F96AC00299D2D /* PBXContainerItemProxy */ = { 34 | isa = PBXContainerItemProxy; 35 | containerPortal = 4D6DF1ED1D0F96AC00299D2D /* Project object */; 36 | proxyType = 1; 37 | remoteGlobalIDString = 4D6DF1F41D0F96AC00299D2D; 38 | remoteInfo = WebViewLongPress; 39 | }; 40 | /* End PBXContainerItemProxy section */ 41 | 42 | /* Begin PBXFileReference section */ 43 | 4D6DF1F51D0F96AC00299D2D /* WebViewLongPress.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = WebViewLongPress.app; sourceTree = BUILT_PRODUCTS_DIR; }; 44 | 4D6DF1F91D0F96AC00299D2D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 45 | 4D6DF1FB1D0F96AC00299D2D /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 46 | 4D6DF1FC1D0F96AC00299D2D /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 47 | 4D6DF2021D0F96AC00299D2D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 48 | 4D6DF2041D0F96AC00299D2D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 49 | 4D6DF2071D0F96AC00299D2D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 50 | 4D6DF2091D0F96AC00299D2D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 51 | 4D6DF20E1D0F96AC00299D2D /* WebViewLongPressTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = WebViewLongPressTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 52 | 4D6DF2121D0F96AC00299D2D /* WebViewLongPressTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = WebViewLongPressTests.m; sourceTree = ""; }; 53 | 4D6DF2141D0F96AC00299D2D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 54 | 4D6DF2231D0F9C4800299D2D /* FSActionSheet.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FSActionSheet.h; sourceTree = ""; }; 55 | 4D6DF2241D0F9C4800299D2D /* FSActionSheet.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FSActionSheet.m; sourceTree = ""; }; 56 | 4D6DF2251D0F9C4800299D2D /* FSActionSheetCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FSActionSheetCell.h; sourceTree = ""; }; 57 | 4D6DF2261D0F9C4800299D2D /* FSActionSheetCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FSActionSheetCell.m; sourceTree = ""; }; 58 | 4D6DF2271D0F9C4800299D2D /* FSActionSheetConfig.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FSActionSheetConfig.h; sourceTree = ""; }; 59 | 4D6DF2281D0F9C4800299D2D /* FSActionSheetConfig.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FSActionSheetConfig.m; sourceTree = ""; }; 60 | 4D6DF2291D0F9C4800299D2D /* FSActionSheetItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FSActionSheetItem.h; sourceTree = ""; }; 61 | 4D6DF22A1D0F9C4800299D2D /* FSActionSheetItem.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FSActionSheetItem.m; sourceTree = ""; }; 62 | 4D6DF22C1D0F9C4800299D2D /* FSActionSheet_cancel@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "FSActionSheet_cancel@2x.png"; sourceTree = ""; }; 63 | 4D6DF22D1D0F9C4800299D2D /* FSActionSheet_cancel@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "FSActionSheet_cancel@3x.png"; sourceTree = ""; }; 64 | 4D6DF22F1D0F9C4800299D2D /* NSString+Sha1.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSString+Sha1.h"; sourceTree = ""; }; 65 | 4D6DF2301D0F9C4800299D2D /* NSString+Sha1.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSString+Sha1.m"; sourceTree = ""; }; 66 | 4D6DF2311D0F9C4800299D2D /* Reachability.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Reachability.h; sourceTree = ""; }; 67 | 4D6DF2321D0F9C4800299D2D /* Reachability.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Reachability.m; sourceTree = ""; }; 68 | 4D6DF2331D0F9C4800299D2D /* RNCachingURLProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNCachingURLProtocol.h; sourceTree = ""; }; 69 | 4D6DF2341D0F9C4800299D2D /* RNCachingURLProtocol.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNCachingURLProtocol.m; sourceTree = ""; }; 70 | 4D6DF23F1D0F9EF400299D2D /* SwizzeMethod.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SwizzeMethod.h; sourceTree = ""; }; 71 | 4D6DF2401D0F9EF400299D2D /* SwizzeMethod.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SwizzeMethod.m; sourceTree = ""; }; 72 | 4D6DF2451D0FA3F900299D2D /* CVWebViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CVWebViewController.h; sourceTree = ""; }; 73 | 4D6DF2461D0FA3F900299D2D /* CVWebViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CVWebViewController.m; sourceTree = ""; }; 74 | 4D6DF2481D0FA47F00299D2D /* CVWebViewController+ImageHelper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "CVWebViewController+ImageHelper.h"; sourceTree = ""; }; 75 | 4D6DF2491D0FA47F00299D2D /* CVWebViewController+ImageHelper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "CVWebViewController+ImageHelper.m"; sourceTree = ""; }; 76 | 4DE18C301E53F31C004C75DB /* MainViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MainViewController.h; sourceTree = ""; }; 77 | 4DE18C311E53F31C004C75DB /* MainViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MainViewController.m; sourceTree = ""; }; 78 | 4DE18C331E53F3F8004C75DB /* WKWebViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WKWebViewController.h; sourceTree = ""; }; 79 | 4DE18C341E53F3F8004C75DB /* WKWebViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WKWebViewController.m; sourceTree = ""; }; 80 | /* End PBXFileReference section */ 81 | 82 | /* Begin PBXFrameworksBuildPhase section */ 83 | 4D6DF1F21D0F96AC00299D2D /* Frameworks */ = { 84 | isa = PBXFrameworksBuildPhase; 85 | buildActionMask = 2147483647; 86 | files = ( 87 | ); 88 | runOnlyForDeploymentPostprocessing = 0; 89 | }; 90 | 4D6DF20B1D0F96AC00299D2D /* Frameworks */ = { 91 | isa = PBXFrameworksBuildPhase; 92 | buildActionMask = 2147483647; 93 | files = ( 94 | ); 95 | runOnlyForDeploymentPostprocessing = 0; 96 | }; 97 | /* End PBXFrameworksBuildPhase section */ 98 | 99 | /* Begin PBXGroup section */ 100 | 4D6DF1EC1D0F96AC00299D2D = { 101 | isa = PBXGroup; 102 | children = ( 103 | 4D6DF1F71D0F96AC00299D2D /* WebViewLongPress */, 104 | 4D6DF2111D0F96AC00299D2D /* WebViewLongPressTests */, 105 | 4D6DF1F61D0F96AC00299D2D /* Products */, 106 | ); 107 | sourceTree = ""; 108 | }; 109 | 4D6DF1F61D0F96AC00299D2D /* Products */ = { 110 | isa = PBXGroup; 111 | children = ( 112 | 4D6DF1F51D0F96AC00299D2D /* WebViewLongPress.app */, 113 | 4D6DF20E1D0F96AC00299D2D /* WebViewLongPressTests.xctest */, 114 | ); 115 | name = Products; 116 | sourceTree = ""; 117 | }; 118 | 4D6DF1F71D0F96AC00299D2D /* WebViewLongPress */ = { 119 | isa = PBXGroup; 120 | children = ( 121 | 4DE18C2F1E53EFCF004C75DB /* WebKit */, 122 | 4DE18C2E1E53EFC0004C75DB /* UIWeb */, 123 | 4D6DF21D1D0F9C4800299D2D /* Vendor */, 124 | 4D6DF23F1D0F9EF400299D2D /* SwizzeMethod.h */, 125 | 4D6DF2401D0F9EF400299D2D /* SwizzeMethod.m */, 126 | 4D6DF1FB1D0F96AC00299D2D /* AppDelegate.h */, 127 | 4D6DF1FC1D0F96AC00299D2D /* AppDelegate.m */, 128 | 4DE18C301E53F31C004C75DB /* MainViewController.h */, 129 | 4DE18C311E53F31C004C75DB /* MainViewController.m */, 130 | 4D6DF2011D0F96AC00299D2D /* Main.storyboard */, 131 | 4D6DF2041D0F96AC00299D2D /* Assets.xcassets */, 132 | 4D6DF2061D0F96AC00299D2D /* LaunchScreen.storyboard */, 133 | 4D6DF2091D0F96AC00299D2D /* Info.plist */, 134 | 4D6DF1F81D0F96AC00299D2D /* Supporting Files */, 135 | ); 136 | path = WebViewLongPress; 137 | sourceTree = ""; 138 | }; 139 | 4D6DF1F81D0F96AC00299D2D /* Supporting Files */ = { 140 | isa = PBXGroup; 141 | children = ( 142 | 4D6DF1F91D0F96AC00299D2D /* main.m */, 143 | ); 144 | name = "Supporting Files"; 145 | sourceTree = ""; 146 | }; 147 | 4D6DF2111D0F96AC00299D2D /* WebViewLongPressTests */ = { 148 | isa = PBXGroup; 149 | children = ( 150 | 4D6DF2121D0F96AC00299D2D /* WebViewLongPressTests.m */, 151 | 4D6DF2141D0F96AC00299D2D /* Info.plist */, 152 | ); 153 | path = WebViewLongPressTests; 154 | sourceTree = ""; 155 | }; 156 | 4D6DF21D1D0F9C4800299D2D /* Vendor */ = { 157 | isa = PBXGroup; 158 | children = ( 159 | 4D6DF2221D0F9C4800299D2D /* FSActionSheet */, 160 | 4D6DF22E1D0F9C4800299D2D /* RNCachingURLProtocol */, 161 | ); 162 | path = Vendor; 163 | sourceTree = ""; 164 | }; 165 | 4D6DF2221D0F9C4800299D2D /* FSActionSheet */ = { 166 | isa = PBXGroup; 167 | children = ( 168 | 4D6DF2231D0F9C4800299D2D /* FSActionSheet.h */, 169 | 4D6DF2241D0F9C4800299D2D /* FSActionSheet.m */, 170 | 4D6DF2251D0F9C4800299D2D /* FSActionSheetCell.h */, 171 | 4D6DF2261D0F9C4800299D2D /* FSActionSheetCell.m */, 172 | 4D6DF2271D0F9C4800299D2D /* FSActionSheetConfig.h */, 173 | 4D6DF2281D0F9C4800299D2D /* FSActionSheetConfig.m */, 174 | 4D6DF2291D0F9C4800299D2D /* FSActionSheetItem.h */, 175 | 4D6DF22A1D0F9C4800299D2D /* FSActionSheetItem.m */, 176 | 4D6DF22B1D0F9C4800299D2D /* FSActionSheetResources */, 177 | ); 178 | path = FSActionSheet; 179 | sourceTree = ""; 180 | }; 181 | 4D6DF22B1D0F9C4800299D2D /* FSActionSheetResources */ = { 182 | isa = PBXGroup; 183 | children = ( 184 | 4D6DF22C1D0F9C4800299D2D /* FSActionSheet_cancel@2x.png */, 185 | 4D6DF22D1D0F9C4800299D2D /* FSActionSheet_cancel@3x.png */, 186 | ); 187 | path = FSActionSheetResources; 188 | sourceTree = ""; 189 | }; 190 | 4D6DF22E1D0F9C4800299D2D /* RNCachingURLProtocol */ = { 191 | isa = PBXGroup; 192 | children = ( 193 | 4D6DF22F1D0F9C4800299D2D /* NSString+Sha1.h */, 194 | 4D6DF2301D0F9C4800299D2D /* NSString+Sha1.m */, 195 | 4D6DF2311D0F9C4800299D2D /* Reachability.h */, 196 | 4D6DF2321D0F9C4800299D2D /* Reachability.m */, 197 | 4D6DF2331D0F9C4800299D2D /* RNCachingURLProtocol.h */, 198 | 4D6DF2341D0F9C4800299D2D /* RNCachingURLProtocol.m */, 199 | ); 200 | path = RNCachingURLProtocol; 201 | sourceTree = ""; 202 | }; 203 | 4DE18C2E1E53EFC0004C75DB /* UIWeb */ = { 204 | isa = PBXGroup; 205 | children = ( 206 | 4D6DF2481D0FA47F00299D2D /* CVWebViewController+ImageHelper.h */, 207 | 4D6DF2491D0FA47F00299D2D /* CVWebViewController+ImageHelper.m */, 208 | 4D6DF2451D0FA3F900299D2D /* CVWebViewController.h */, 209 | 4D6DF2461D0FA3F900299D2D /* CVWebViewController.m */, 210 | ); 211 | name = UIWeb; 212 | sourceTree = ""; 213 | }; 214 | 4DE18C2F1E53EFCF004C75DB /* WebKit */ = { 215 | isa = PBXGroup; 216 | children = ( 217 | 4DE18C331E53F3F8004C75DB /* WKWebViewController.h */, 218 | 4DE18C341E53F3F8004C75DB /* WKWebViewController.m */, 219 | ); 220 | name = WebKit; 221 | sourceTree = ""; 222 | }; 223 | /* End PBXGroup section */ 224 | 225 | /* Begin PBXNativeTarget section */ 226 | 4D6DF1F41D0F96AC00299D2D /* WebViewLongPress */ = { 227 | isa = PBXNativeTarget; 228 | buildConfigurationList = 4D6DF2171D0F96AC00299D2D /* Build configuration list for PBXNativeTarget "WebViewLongPress" */; 229 | buildPhases = ( 230 | 4D6DF1F11D0F96AC00299D2D /* Sources */, 231 | 4D6DF1F21D0F96AC00299D2D /* Frameworks */, 232 | 4D6DF1F31D0F96AC00299D2D /* Resources */, 233 | ); 234 | buildRules = ( 235 | ); 236 | dependencies = ( 237 | ); 238 | name = WebViewLongPress; 239 | productName = WebViewLongPress; 240 | productReference = 4D6DF1F51D0F96AC00299D2D /* WebViewLongPress.app */; 241 | productType = "com.apple.product-type.application"; 242 | }; 243 | 4D6DF20D1D0F96AC00299D2D /* WebViewLongPressTests */ = { 244 | isa = PBXNativeTarget; 245 | buildConfigurationList = 4D6DF21A1D0F96AC00299D2D /* Build configuration list for PBXNativeTarget "WebViewLongPressTests" */; 246 | buildPhases = ( 247 | 4D6DF20A1D0F96AC00299D2D /* Sources */, 248 | 4D6DF20B1D0F96AC00299D2D /* Frameworks */, 249 | 4D6DF20C1D0F96AC00299D2D /* Resources */, 250 | ); 251 | buildRules = ( 252 | ); 253 | dependencies = ( 254 | 4D6DF2101D0F96AC00299D2D /* PBXTargetDependency */, 255 | ); 256 | name = WebViewLongPressTests; 257 | productName = WebViewLongPressTests; 258 | productReference = 4D6DF20E1D0F96AC00299D2D /* WebViewLongPressTests.xctest */; 259 | productType = "com.apple.product-type.bundle.unit-test"; 260 | }; 261 | /* End PBXNativeTarget section */ 262 | 263 | /* Begin PBXProject section */ 264 | 4D6DF1ED1D0F96AC00299D2D /* Project object */ = { 265 | isa = PBXProject; 266 | attributes = { 267 | LastUpgradeCheck = 0730; 268 | ORGANIZATIONNAME = cvte; 269 | TargetAttributes = { 270 | 4D6DF1F41D0F96AC00299D2D = { 271 | CreatedOnToolsVersion = 7.3; 272 | DevelopmentTeam = E48S8MXDB9; 273 | }; 274 | 4D6DF20D1D0F96AC00299D2D = { 275 | CreatedOnToolsVersion = 7.3; 276 | TestTargetID = 4D6DF1F41D0F96AC00299D2D; 277 | }; 278 | }; 279 | }; 280 | buildConfigurationList = 4D6DF1F01D0F96AC00299D2D /* Build configuration list for PBXProject "WebViewLongPress" */; 281 | compatibilityVersion = "Xcode 3.2"; 282 | developmentRegion = English; 283 | hasScannedForEncodings = 0; 284 | knownRegions = ( 285 | en, 286 | Base, 287 | ); 288 | mainGroup = 4D6DF1EC1D0F96AC00299D2D; 289 | productRefGroup = 4D6DF1F61D0F96AC00299D2D /* Products */; 290 | projectDirPath = ""; 291 | projectRoot = ""; 292 | targets = ( 293 | 4D6DF1F41D0F96AC00299D2D /* WebViewLongPress */, 294 | 4D6DF20D1D0F96AC00299D2D /* WebViewLongPressTests */, 295 | ); 296 | }; 297 | /* End PBXProject section */ 298 | 299 | /* Begin PBXResourcesBuildPhase section */ 300 | 4D6DF1F31D0F96AC00299D2D /* Resources */ = { 301 | isa = PBXResourcesBuildPhase; 302 | buildActionMask = 2147483647; 303 | files = ( 304 | 4D6DF2081D0F96AC00299D2D /* LaunchScreen.storyboard in Resources */, 305 | 4D6DF23A1D0F9C4800299D2D /* FSActionSheet_cancel@2x.png in Resources */, 306 | 4D6DF2051D0F96AC00299D2D /* Assets.xcassets in Resources */, 307 | 4D6DF2031D0F96AC00299D2D /* Main.storyboard in Resources */, 308 | 4D6DF23B1D0F9C4800299D2D /* FSActionSheet_cancel@3x.png in Resources */, 309 | ); 310 | runOnlyForDeploymentPostprocessing = 0; 311 | }; 312 | 4D6DF20C1D0F96AC00299D2D /* Resources */ = { 313 | isa = PBXResourcesBuildPhase; 314 | buildActionMask = 2147483647; 315 | files = ( 316 | ); 317 | runOnlyForDeploymentPostprocessing = 0; 318 | }; 319 | /* End PBXResourcesBuildPhase section */ 320 | 321 | /* Begin PBXSourcesBuildPhase section */ 322 | 4D6DF1F11D0F96AC00299D2D /* Sources */ = { 323 | isa = PBXSourcesBuildPhase; 324 | buildActionMask = 2147483647; 325 | files = ( 326 | 4D6DF2381D0F9C4800299D2D /* FSActionSheetConfig.m in Sources */, 327 | 4D6DF2361D0F9C4800299D2D /* FSActionSheet.m in Sources */, 328 | 4D6DF2411D0F9EF400299D2D /* SwizzeMethod.m in Sources */, 329 | 4DE18C351E53F3F8004C75DB /* WKWebViewController.m in Sources */, 330 | 4D6DF2371D0F9C4800299D2D /* FSActionSheetCell.m in Sources */, 331 | 4DE18C321E53F31C004C75DB /* MainViewController.m in Sources */, 332 | 4D6DF23D1D0F9C4800299D2D /* Reachability.m in Sources */, 333 | 4D6DF23C1D0F9C4800299D2D /* NSString+Sha1.m in Sources */, 334 | 4D6DF23E1D0F9C4800299D2D /* RNCachingURLProtocol.m in Sources */, 335 | 4D6DF2391D0F9C4800299D2D /* FSActionSheetItem.m in Sources */, 336 | 4D6DF1FD1D0F96AC00299D2D /* AppDelegate.m in Sources */, 337 | 4D6DF24A1D0FA47F00299D2D /* CVWebViewController+ImageHelper.m in Sources */, 338 | 4D6DF1FA1D0F96AC00299D2D /* main.m in Sources */, 339 | 4D6DF2471D0FA3F900299D2D /* CVWebViewController.m in Sources */, 340 | ); 341 | runOnlyForDeploymentPostprocessing = 0; 342 | }; 343 | 4D6DF20A1D0F96AC00299D2D /* Sources */ = { 344 | isa = PBXSourcesBuildPhase; 345 | buildActionMask = 2147483647; 346 | files = ( 347 | 4D6DF2131D0F96AC00299D2D /* WebViewLongPressTests.m in Sources */, 348 | ); 349 | runOnlyForDeploymentPostprocessing = 0; 350 | }; 351 | /* End PBXSourcesBuildPhase section */ 352 | 353 | /* Begin PBXTargetDependency section */ 354 | 4D6DF2101D0F96AC00299D2D /* PBXTargetDependency */ = { 355 | isa = PBXTargetDependency; 356 | target = 4D6DF1F41D0F96AC00299D2D /* WebViewLongPress */; 357 | targetProxy = 4D6DF20F1D0F96AC00299D2D /* PBXContainerItemProxy */; 358 | }; 359 | /* End PBXTargetDependency section */ 360 | 361 | /* Begin PBXVariantGroup section */ 362 | 4D6DF2011D0F96AC00299D2D /* Main.storyboard */ = { 363 | isa = PBXVariantGroup; 364 | children = ( 365 | 4D6DF2021D0F96AC00299D2D /* Base */, 366 | ); 367 | name = Main.storyboard; 368 | sourceTree = ""; 369 | }; 370 | 4D6DF2061D0F96AC00299D2D /* LaunchScreen.storyboard */ = { 371 | isa = PBXVariantGroup; 372 | children = ( 373 | 4D6DF2071D0F96AC00299D2D /* Base */, 374 | ); 375 | name = LaunchScreen.storyboard; 376 | sourceTree = ""; 377 | }; 378 | /* End PBXVariantGroup section */ 379 | 380 | /* Begin XCBuildConfiguration section */ 381 | 4D6DF2151D0F96AC00299D2D /* Debug */ = { 382 | isa = XCBuildConfiguration; 383 | buildSettings = { 384 | ALWAYS_SEARCH_USER_PATHS = NO; 385 | CLANG_ANALYZER_NONNULL = YES; 386 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 387 | CLANG_CXX_LIBRARY = "libc++"; 388 | CLANG_ENABLE_MODULES = YES; 389 | CLANG_ENABLE_OBJC_ARC = YES; 390 | CLANG_WARN_BOOL_CONVERSION = YES; 391 | CLANG_WARN_CONSTANT_CONVERSION = YES; 392 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 393 | CLANG_WARN_EMPTY_BODY = YES; 394 | CLANG_WARN_ENUM_CONVERSION = YES; 395 | CLANG_WARN_INT_CONVERSION = YES; 396 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 397 | CLANG_WARN_UNREACHABLE_CODE = YES; 398 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 399 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 400 | COPY_PHASE_STRIP = NO; 401 | DEBUG_INFORMATION_FORMAT = dwarf; 402 | ENABLE_STRICT_OBJC_MSGSEND = YES; 403 | ENABLE_TESTABILITY = YES; 404 | GCC_C_LANGUAGE_STANDARD = gnu99; 405 | GCC_DYNAMIC_NO_PIC = NO; 406 | GCC_NO_COMMON_BLOCKS = YES; 407 | GCC_OPTIMIZATION_LEVEL = 0; 408 | GCC_PREPROCESSOR_DEFINITIONS = ( 409 | "DEBUG=1", 410 | "$(inherited)", 411 | ); 412 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 413 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 414 | GCC_WARN_UNDECLARED_SELECTOR = YES; 415 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 416 | GCC_WARN_UNUSED_FUNCTION = YES; 417 | GCC_WARN_UNUSED_VARIABLE = YES; 418 | IPHONEOS_DEPLOYMENT_TARGET = 9.3; 419 | MTL_ENABLE_DEBUG_INFO = YES; 420 | ONLY_ACTIVE_ARCH = YES; 421 | SDKROOT = iphoneos; 422 | TARGETED_DEVICE_FAMILY = "1,2"; 423 | }; 424 | name = Debug; 425 | }; 426 | 4D6DF2161D0F96AC00299D2D /* Release */ = { 427 | isa = XCBuildConfiguration; 428 | buildSettings = { 429 | ALWAYS_SEARCH_USER_PATHS = NO; 430 | CLANG_ANALYZER_NONNULL = YES; 431 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 432 | CLANG_CXX_LIBRARY = "libc++"; 433 | CLANG_ENABLE_MODULES = YES; 434 | CLANG_ENABLE_OBJC_ARC = YES; 435 | CLANG_WARN_BOOL_CONVERSION = YES; 436 | CLANG_WARN_CONSTANT_CONVERSION = YES; 437 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 438 | CLANG_WARN_EMPTY_BODY = YES; 439 | CLANG_WARN_ENUM_CONVERSION = YES; 440 | CLANG_WARN_INT_CONVERSION = YES; 441 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 442 | CLANG_WARN_UNREACHABLE_CODE = YES; 443 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 444 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 445 | COPY_PHASE_STRIP = NO; 446 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 447 | ENABLE_NS_ASSERTIONS = NO; 448 | ENABLE_STRICT_OBJC_MSGSEND = YES; 449 | GCC_C_LANGUAGE_STANDARD = gnu99; 450 | GCC_NO_COMMON_BLOCKS = YES; 451 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 452 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 453 | GCC_WARN_UNDECLARED_SELECTOR = YES; 454 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 455 | GCC_WARN_UNUSED_FUNCTION = YES; 456 | GCC_WARN_UNUSED_VARIABLE = YES; 457 | IPHONEOS_DEPLOYMENT_TARGET = 9.3; 458 | MTL_ENABLE_DEBUG_INFO = NO; 459 | SDKROOT = iphoneos; 460 | TARGETED_DEVICE_FAMILY = "1,2"; 461 | VALIDATE_PRODUCT = YES; 462 | }; 463 | name = Release; 464 | }; 465 | 4D6DF2181D0F96AC00299D2D /* Debug */ = { 466 | isa = XCBuildConfiguration; 467 | buildSettings = { 468 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 469 | CODE_SIGN_IDENTITY = "iPhone Developer"; 470 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 471 | DEVELOPMENT_TEAM = E48S8MXDB9; 472 | INFOPLIST_FILE = WebViewLongPress/Info.plist; 473 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 474 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 475 | PRODUCT_BUNDLE_IDENTIFIER = com.cvte.WebViewLongPress; 476 | PRODUCT_NAME = "$(TARGET_NAME)"; 477 | PROVISIONING_PROFILE = ""; 478 | }; 479 | name = Debug; 480 | }; 481 | 4D6DF2191D0F96AC00299D2D /* Release */ = { 482 | isa = XCBuildConfiguration; 483 | buildSettings = { 484 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 485 | CODE_SIGN_IDENTITY = "iPhone Developer"; 486 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 487 | DEVELOPMENT_TEAM = E48S8MXDB9; 488 | INFOPLIST_FILE = WebViewLongPress/Info.plist; 489 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 490 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 491 | PRODUCT_BUNDLE_IDENTIFIER = com.cvte.WebViewLongPress; 492 | PRODUCT_NAME = "$(TARGET_NAME)"; 493 | PROVISIONING_PROFILE = ""; 494 | }; 495 | name = Release; 496 | }; 497 | 4D6DF21B1D0F96AC00299D2D /* Debug */ = { 498 | isa = XCBuildConfiguration; 499 | buildSettings = { 500 | BUNDLE_LOADER = "$(TEST_HOST)"; 501 | INFOPLIST_FILE = WebViewLongPressTests/Info.plist; 502 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 503 | PRODUCT_BUNDLE_IDENTIFIER = com.cvte.WebViewLongPressTests; 504 | PRODUCT_NAME = "$(TARGET_NAME)"; 505 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/WebViewLongPress.app/WebViewLongPress"; 506 | }; 507 | name = Debug; 508 | }; 509 | 4D6DF21C1D0F96AC00299D2D /* Release */ = { 510 | isa = XCBuildConfiguration; 511 | buildSettings = { 512 | BUNDLE_LOADER = "$(TEST_HOST)"; 513 | INFOPLIST_FILE = WebViewLongPressTests/Info.plist; 514 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 515 | PRODUCT_BUNDLE_IDENTIFIER = com.cvte.WebViewLongPressTests; 516 | PRODUCT_NAME = "$(TARGET_NAME)"; 517 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/WebViewLongPress.app/WebViewLongPress"; 518 | }; 519 | name = Release; 520 | }; 521 | /* End XCBuildConfiguration section */ 522 | 523 | /* Begin XCConfigurationList section */ 524 | 4D6DF1F01D0F96AC00299D2D /* Build configuration list for PBXProject "WebViewLongPress" */ = { 525 | isa = XCConfigurationList; 526 | buildConfigurations = ( 527 | 4D6DF2151D0F96AC00299D2D /* Debug */, 528 | 4D6DF2161D0F96AC00299D2D /* Release */, 529 | ); 530 | defaultConfigurationIsVisible = 0; 531 | defaultConfigurationName = Release; 532 | }; 533 | 4D6DF2171D0F96AC00299D2D /* Build configuration list for PBXNativeTarget "WebViewLongPress" */ = { 534 | isa = XCConfigurationList; 535 | buildConfigurations = ( 536 | 4D6DF2181D0F96AC00299D2D /* Debug */, 537 | 4D6DF2191D0F96AC00299D2D /* Release */, 538 | ); 539 | defaultConfigurationIsVisible = 0; 540 | defaultConfigurationName = Release; 541 | }; 542 | 4D6DF21A1D0F96AC00299D2D /* Build configuration list for PBXNativeTarget "WebViewLongPressTests" */ = { 543 | isa = XCConfigurationList; 544 | buildConfigurations = ( 545 | 4D6DF21B1D0F96AC00299D2D /* Debug */, 546 | 4D6DF21C1D0F96AC00299D2D /* Release */, 547 | ); 548 | defaultConfigurationIsVisible = 0; 549 | defaultConfigurationName = Release; 550 | }; 551 | /* End XCConfigurationList section */ 552 | }; 553 | rootObject = 4D6DF1ED1D0F96AC00299D2D /* Project object */; 554 | } 555 | --------------------------------------------------------------------------------