├── RSTWebViewController.xcworkspace ├── xcuserdata │ └── Riley.xcuserdatad │ │ └── xcdebugger │ │ └── Breakpoints_v2.xcbkptlist └── contents.xcworkspacedata ├── RSTWebViewController ├── RSTWebViewController │ ├── Images.xcassets │ │ ├── back_button.imageset │ │ │ ├── back_button.png │ │ │ ├── back_button@2x.png │ │ │ ├── back_button@3x.png │ │ │ └── Contents.json │ │ ├── chrome_activity.imageset │ │ │ ├── Icon-152.png │ │ │ ├── Icon-76.png │ │ │ ├── chrome_activity@2x.png │ │ │ ├── chrome_activity@3x.png │ │ │ └── Contents.json │ │ ├── forward_button.imageset │ │ │ ├── forward_button.png │ │ │ ├── forward_button@2x.png │ │ │ ├── forward_button@3x.png │ │ │ └── Contents.json │ │ └── safari_activity.imageset │ │ │ ├── safari_activity@2x.png │ │ │ ├── safari_activity@3x.png │ │ │ ├── safari_activity_ipad.png │ │ │ ├── safari_activity_ipad@2x.png │ │ │ └── Contents.json │ ├── UIApplication+ExtensionSafe.h │ ├── RSTActivities.h │ ├── RSTActivities.m │ ├── RSTWebViewController.h │ ├── UIApplication+ExtensionSafe.m │ ├── Info.plist │ ├── RSTSafariActivity.swift │ ├── RSTURLActivityItem.swift │ ├── RSTChromeActivity.swift │ ├── RSTOnePasswordExtension.h │ ├── RSTWebViewController.swift │ └── RSTOnePasswordExtension.m ├── RSTWebViewControllerTests │ ├── Info.plist │ └── RSTWebViewControllerTests.swift └── RSTWebViewController.xcodeproj │ └── project.pbxproj ├── Demo ├── Demo │ ├── ViewController.h │ ├── AppDelegate.h │ ├── main.m │ ├── Images.xcassets │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── ViewController.m │ ├── Info.plist │ ├── AppDelegate.m │ └── Base.lproj │ │ ├── LaunchScreen.xib │ │ └── Main.storyboard ├── DemoTests │ ├── Info.plist │ └── DemoTests.m └── Demo.xcodeproj │ └── project.pbxproj ├── .gitignore ├── LICENSE └── README.md /RSTWebViewController.xcworkspace/xcuserdata/Riley.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /RSTWebViewController/RSTWebViewController/Images.xcassets/back_button.imageset/back_button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rileytestut/RSTWebViewController/HEAD/RSTWebViewController/RSTWebViewController/Images.xcassets/back_button.imageset/back_button.png -------------------------------------------------------------------------------- /RSTWebViewController/RSTWebViewController/Images.xcassets/chrome_activity.imageset/Icon-152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rileytestut/RSTWebViewController/HEAD/RSTWebViewController/RSTWebViewController/Images.xcassets/chrome_activity.imageset/Icon-152.png -------------------------------------------------------------------------------- /RSTWebViewController/RSTWebViewController/Images.xcassets/chrome_activity.imageset/Icon-76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rileytestut/RSTWebViewController/HEAD/RSTWebViewController/RSTWebViewController/Images.xcassets/chrome_activity.imageset/Icon-76.png -------------------------------------------------------------------------------- /RSTWebViewController/RSTWebViewController/Images.xcassets/back_button.imageset/back_button@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rileytestut/RSTWebViewController/HEAD/RSTWebViewController/RSTWebViewController/Images.xcassets/back_button.imageset/back_button@2x.png -------------------------------------------------------------------------------- /RSTWebViewController/RSTWebViewController/Images.xcassets/back_button.imageset/back_button@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rileytestut/RSTWebViewController/HEAD/RSTWebViewController/RSTWebViewController/Images.xcassets/back_button.imageset/back_button@3x.png -------------------------------------------------------------------------------- /RSTWebViewController/RSTWebViewController/Images.xcassets/forward_button.imageset/forward_button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rileytestut/RSTWebViewController/HEAD/RSTWebViewController/RSTWebViewController/Images.xcassets/forward_button.imageset/forward_button.png -------------------------------------------------------------------------------- /RSTWebViewController/RSTWebViewController/Images.xcassets/chrome_activity.imageset/chrome_activity@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rileytestut/RSTWebViewController/HEAD/RSTWebViewController/RSTWebViewController/Images.xcassets/chrome_activity.imageset/chrome_activity@2x.png -------------------------------------------------------------------------------- /RSTWebViewController/RSTWebViewController/Images.xcassets/chrome_activity.imageset/chrome_activity@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rileytestut/RSTWebViewController/HEAD/RSTWebViewController/RSTWebViewController/Images.xcassets/chrome_activity.imageset/chrome_activity@3x.png -------------------------------------------------------------------------------- /RSTWebViewController/RSTWebViewController/Images.xcassets/forward_button.imageset/forward_button@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rileytestut/RSTWebViewController/HEAD/RSTWebViewController/RSTWebViewController/Images.xcassets/forward_button.imageset/forward_button@2x.png -------------------------------------------------------------------------------- /RSTWebViewController/RSTWebViewController/Images.xcassets/forward_button.imageset/forward_button@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rileytestut/RSTWebViewController/HEAD/RSTWebViewController/RSTWebViewController/Images.xcassets/forward_button.imageset/forward_button@3x.png -------------------------------------------------------------------------------- /RSTWebViewController/RSTWebViewController/Images.xcassets/safari_activity.imageset/safari_activity@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rileytestut/RSTWebViewController/HEAD/RSTWebViewController/RSTWebViewController/Images.xcassets/safari_activity.imageset/safari_activity@2x.png -------------------------------------------------------------------------------- /RSTWebViewController/RSTWebViewController/Images.xcassets/safari_activity.imageset/safari_activity@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rileytestut/RSTWebViewController/HEAD/RSTWebViewController/RSTWebViewController/Images.xcassets/safari_activity.imageset/safari_activity@3x.png -------------------------------------------------------------------------------- /RSTWebViewController/RSTWebViewController/Images.xcassets/safari_activity.imageset/safari_activity_ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rileytestut/RSTWebViewController/HEAD/RSTWebViewController/RSTWebViewController/Images.xcassets/safari_activity.imageset/safari_activity_ipad.png -------------------------------------------------------------------------------- /RSTWebViewController/RSTWebViewController/Images.xcassets/safari_activity.imageset/safari_activity_ipad@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rileytestut/RSTWebViewController/HEAD/RSTWebViewController/RSTWebViewController/Images.xcassets/safari_activity.imageset/safari_activity_ipad@2x.png -------------------------------------------------------------------------------- /Demo/Demo/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // Demo 4 | // 5 | // Created by Riley Testut on 12/23/14. 6 | // Copyright (c) 2014 Riley Testut. All rights reserved. 7 | // 8 | 9 | @import UIKit; 10 | 11 | @interface ViewController : UIViewController 12 | 13 | @end 14 | 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Mac OS X 2 | # 3 | *.DS_Store 4 | 5 | # Xcode 6 | # 7 | build/ 8 | *.pbxuser 9 | !default.pbxuser 10 | *.mode1v3 11 | !default.mode1v3 12 | *.mode2v3 13 | !default.mode2v3 14 | *.perspectivev3 15 | !default.perspectivev3 16 | xcuserdata 17 | *.xccheckout 18 | *.moved-aside 19 | DerivedData 20 | *.hmap 21 | *.ipa 22 | *.xcuserstate 23 | -------------------------------------------------------------------------------- /Demo/Demo/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // Demo 4 | // 5 | // Created by Riley Testut on 12/23/14. 6 | // Copyright (c) 2014 Riley Testut. All rights reserved. 7 | // 8 | 9 | @import UIKit; 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | @end 16 | 17 | -------------------------------------------------------------------------------- /RSTWebViewController.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Demo/Demo/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // Demo 4 | // 5 | // Created by Riley Testut on 12/23/14. 6 | // Copyright (c) 2014 Riley Testut. 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 | -------------------------------------------------------------------------------- /RSTWebViewController/RSTWebViewController/UIApplication+ExtensionSafe.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIApplication+ExtensionSafe.h 3 | // RSTWebViewController 4 | // 5 | // Created by Riley Testut on 12/26/14. 6 | // Copyright (c) 2014 Riley Testut. All rights reserved. 7 | // 8 | 9 | @import UIKit; 10 | 11 | @interface UIApplication (ExtensionSafe) 12 | 13 | + (instancetype)rst_sharedApplication; 14 | 15 | - (BOOL)rst_openURL:(NSURL *)URL; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /RSTWebViewController/RSTWebViewController/RSTActivities.h: -------------------------------------------------------------------------------- 1 | // 2 | // RSTActivities.h 3 | // RSTWebViewController 4 | // 5 | // Created by Riley Testut on 12/27/14. 6 | // Copyright (c) 2014 Riley Testut. All rights reserved. 7 | // 8 | 9 | @import Foundation; 10 | 11 | FOUNDATION_EXPORT NSString *const RSTActivityTypeSafari; 12 | FOUNDATION_EXPORT NSString *const RSTActivityTypeChrome; 13 | FOUNDATION_EXPORT NSString *const RSTActivityTypeOnePassword; 14 | -------------------------------------------------------------------------------- /RSTWebViewController/RSTWebViewController/Images.xcassets/back_button.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "back_button.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x", 11 | "filename" : "back_button@2x.png" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x", 16 | "filename" : "back_button@3x.png" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /RSTWebViewController/RSTWebViewController/Images.xcassets/forward_button.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "forward_button.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x", 11 | "filename" : "forward_button@2x.png" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x", 16 | "filename" : "forward_button@3x.png" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /RSTWebViewController/RSTWebViewController/RSTActivities.m: -------------------------------------------------------------------------------- 1 | // 2 | // RSTActivities.m 3 | // RSTWebViewController 4 | // 5 | // Created by Riley Testut on 12/27/14. 6 | // Copyright (c) 2014 Riley Testut. All rights reserved. 7 | // 8 | 9 | #import "RSTActivities.h" 10 | #import "RSTOnePasswordExtension.h" 11 | 12 | NSString *const RSTActivityTypeSafari = @"com.rileytestut.RSTWebViewController.activity.Safari"; 13 | NSString *const RSTActivityTypeChrome = @"com.rileytestut.RSTWebViewController.activity.Chrome"; 14 | NSString *const RSTActivityTypeOnePassword = @"com.rileytestut.RSTWebViewController.activity.OnePassword"; 15 | -------------------------------------------------------------------------------- /RSTWebViewController/RSTWebViewController/Images.xcassets/chrome_activity.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "iphone", 9 | "scale" : "2x", 10 | "filename" : "chrome_activity@2x.png" 11 | }, 12 | { 13 | "idiom" : "iphone", 14 | "scale" : "3x", 15 | "filename" : "chrome_activity@3x.png" 16 | }, 17 | { 18 | "idiom" : "ipad", 19 | "scale" : "1x", 20 | "filename" : "Icon-76.png" 21 | }, 22 | { 23 | "idiom" : "ipad", 24 | "scale" : "2x", 25 | "filename" : "Icon-152.png" 26 | } 27 | ], 28 | "info" : { 29 | "version" : 1, 30 | "author" : "xcode" 31 | } 32 | } -------------------------------------------------------------------------------- /RSTWebViewController/RSTWebViewController/Images.xcassets/safari_activity.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "iphone", 9 | "scale" : "2x", 10 | "filename" : "safari_activity@2x.png" 11 | }, 12 | { 13 | "idiom" : "iphone", 14 | "scale" : "3x", 15 | "filename" : "safari_activity@3x.png" 16 | }, 17 | { 18 | "idiom" : "ipad", 19 | "scale" : "1x", 20 | "filename" : "safari_activity_ipad.png" 21 | }, 22 | { 23 | "idiom" : "ipad", 24 | "scale" : "2x", 25 | "filename" : "safari_activity_ipad@2x.png" 26 | } 27 | ], 28 | "info" : { 29 | "version" : 1, 30 | "author" : "xcode" 31 | } 32 | } -------------------------------------------------------------------------------- /Demo/Demo/Images.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 | "info" : { 35 | "version" : 1, 36 | "author" : "xcode" 37 | } 38 | } -------------------------------------------------------------------------------- /Demo/DemoTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | com.rileytestut.$(PRODUCT_NAME:rfc1034identifier) 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 | -------------------------------------------------------------------------------- /RSTWebViewController/RSTWebViewController/RSTWebViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // RSTWebViewController.h 3 | // RSTWebViewController 4 | // 5 | // Created by Riley Testut on 12/23/14. 6 | // Copyright (c) 2014 Riley Testut. All rights reserved. 7 | // 8 | 9 | @import Foundation; 10 | 11 | //! Project version number for RSTWebViewController. 12 | FOUNDATION_EXPORT double RSTWebViewControllerVersionNumber; 13 | 14 | //! Project version string for RSTWebViewController. 15 | FOUNDATION_EXPORT const unsigned char RSTWebViewControllerVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | #import 19 | #import 20 | #import 21 | 22 | 23 | -------------------------------------------------------------------------------- /RSTWebViewController/RSTWebViewControllerTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | com.rileytestut.$(PRODUCT_NAME:rfc1034identifier) 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 | -------------------------------------------------------------------------------- /RSTWebViewController/RSTWebViewController/UIApplication+ExtensionSafe.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIApplication+ExtensionSafe.m 3 | // RSTWebViewController 4 | // 5 | // Created by Riley Testut on 12/26/14. 6 | // Copyright (c) 2014 Riley Testut. All rights reserved. 7 | // 8 | 9 | #import "UIApplication+ExtensionSafe.h" 10 | 11 | @implementation UIApplication (ExtensionSafe) 12 | 13 | + (instancetype)rst_sharedApplication 14 | { 15 | BOOL isApplicationExtension = ([[[NSBundle mainBundle] executablePath] containsString:@".appex/"]); 16 | 17 | if (isApplicationExtension) 18 | { 19 | return nil; 20 | } 21 | 22 | UIApplication *application = [UIApplication performSelector:@selector(sharedApplication)]; 23 | return application; 24 | } 25 | 26 | - (BOOL)rst_openURL:(NSURL *)URL 27 | { 28 | return [self performSelector:@selector(openURL:) withObject:URL]; 29 | } 30 | 31 | @end 32 | -------------------------------------------------------------------------------- /RSTWebViewController/RSTWebViewController/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | com.rileytestut.$(PRODUCT_NAME:rfc1034identifier) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Demo/DemoTests/DemoTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // DemoTests.m 3 | // DemoTests 4 | // 5 | // Created by Riley Testut on 12/23/14. 6 | // Copyright (c) 2014 Riley Testut. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @interface DemoTests : XCTestCase 13 | 14 | @end 15 | 16 | @implementation DemoTests 17 | 18 | - (void)setUp { 19 | [super setUp]; 20 | // Put setup code here. This method is called before the invocation of each test method in the class. 21 | } 22 | 23 | - (void)tearDown { 24 | // Put teardown code here. This method is called after the invocation of each test method in the class. 25 | [super tearDown]; 26 | } 27 | 28 | - (void)testExample { 29 | // This is an example of a functional test case. 30 | XCTAssert(YES, @"Pass"); 31 | } 32 | 33 | - (void)testPerformanceExample { 34 | // This is an example of a performance test case. 35 | [self measureBlock:^{ 36 | // Put the code you want to measure the time of here. 37 | }]; 38 | } 39 | 40 | @end 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Riley Testut 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. -------------------------------------------------------------------------------- /RSTWebViewController/RSTWebViewControllerTests/RSTWebViewControllerTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RSTWebViewControllerTests.swift 3 | // RSTWebViewControllerTests 4 | // 5 | // Created by Riley Testut on 12/23/14. 6 | // Copyright (c) 2014 Riley Testut. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import XCTest 11 | 12 | class RSTWebViewControllerTests: XCTestCase { 13 | 14 | override func setUp() { 15 | super.setUp() 16 | // Put setup code here. This method is called before the invocation of each test method in the class. 17 | } 18 | 19 | override func tearDown() { 20 | // Put teardown code here. This method is called after the invocation of each test method in the class. 21 | super.tearDown() 22 | } 23 | 24 | func testExample() { 25 | // This is an example of a functional test case. 26 | XCTAssert(true, "Pass") 27 | } 28 | 29 | func testPerformanceExample() { 30 | // This is an example of a performance test case. 31 | self.measureBlock() { 32 | // Put the code you want to measure the time of here. 33 | } 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /Demo/Demo/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // Demo 4 | // 5 | // Created by Riley Testut on 12/23/14. 6 | // Copyright (c) 2014 Riley Testut. All rights reserved. 7 | // 8 | 9 | #import "ViewController.h" 10 | 11 | @import RSTWebViewController; 12 | 13 | @interface ViewController () 14 | 15 | @end 16 | 17 | @implementation ViewController 18 | 19 | - (void)viewDidLoad 20 | { 21 | [super viewDidLoad]; 22 | } 23 | 24 | - (void)didReceiveMemoryWarning 25 | { 26 | [super didReceiveMemoryWarning]; 27 | } 28 | 29 | - (IBAction)presentWebViewController:(UIButton *)sender 30 | { 31 | RSTWebViewController *webViewController = [[RSTWebViewController alloc] initWithAddress:@"http://rileytestut.com"]; 32 | webViewController.showsDoneButton = YES; 33 | 34 | UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:webViewController]; 35 | [self presentViewController:navigationController animated:YES completion:nil]; 36 | } 37 | 38 | - (IBAction)pushWebViewController:(UIButton *)sender 39 | { 40 | RSTWebViewController *webViewController = [[RSTWebViewController alloc] initWithAddress:@"http://nytimes.com"]; 41 | [self.navigationController pushViewController:webViewController animated:YES]; 42 | } 43 | 44 | @end 45 | -------------------------------------------------------------------------------- /Demo/Demo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | com.rileytestut.$(PRODUCT_NAME:rfc1034identifier) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | 39 | UTImportedTypeDeclarations 40 | 41 | 42 | UTTypeConformsTo 43 | 44 | UTTypeDescription 45 | 1Password Fill Web View Action 46 | UTTypeIdentifier 47 | org.appextension.fill-webview-action 48 | 49 | 50 | UTTypeConformsTo 51 | 52 | public.url 53 | org.appextension.fill-webview-action 54 | 55 | UTTypeDescription 56 | RSTWebViewController URL 57 | UTTypeIdentifier 58 | com.rileytestut.RSTWebViewController.url 59 | 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /Demo/Demo/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // Demo 4 | // 5 | // Created by Riley Testut on 12/23/14. 6 | // Copyright (c) 2014 Riley Testut. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | 11 | @interface AppDelegate () 12 | 13 | @end 14 | 15 | @implementation AppDelegate 16 | 17 | 18 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 19 | { 20 | // Override point for customization after application launch. 21 | return YES; 22 | } 23 | 24 | - (void)applicationWillResignActive:(UIApplication *)application 25 | { 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 | { 32 | // 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. 33 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 34 | } 35 | 36 | - (void)applicationWillEnterForeground:(UIApplication *)application 37 | { 38 | // 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. 39 | } 40 | 41 | - (void)applicationDidBecomeActive:(UIApplication *)application 42 | { 43 | // 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. 44 | } 45 | 46 | - (void)applicationWillTerminate:(UIApplication *)application 47 | { 48 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 49 | } 50 | 51 | @end 52 | -------------------------------------------------------------------------------- /RSTWebViewController/RSTWebViewController/RSTSafariActivity.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RSTSafariActivity.swift 3 | // RSTWebViewController 4 | // 5 | // Created by Riley Testut on 12/27/14. 6 | // Copyright (c) 2014 Riley Testut. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | internal class RSTSafariActivity: UIActivity { 12 | 13 | private var URL: NSURL? 14 | 15 | override class func activityCategory() -> UIActivityCategory 16 | { 17 | return .Share 18 | } 19 | 20 | override func activityType() -> String? 21 | { 22 | return RSTActivityTypeSafari 23 | } 24 | 25 | override func activityTitle() -> String? 26 | { 27 | return NSLocalizedString("Safari", comment: "") 28 | } 29 | 30 | override func activityImage() -> UIImage? 31 | { 32 | let bundle = NSBundle(forClass: RSTSafariActivity.self) 33 | return UIImage(named: "safari_activity", inBundle: bundle, compatibleWithTraitCollection: nil) 34 | } 35 | 36 | override func canPerformWithActivityItems(activityItems: [AnyObject]) -> Bool 37 | { 38 | if let application = UIApplication.rst_sharedApplication() 39 | { 40 | if let safariURLScheme = NSURL(string: "http://") 41 | { 42 | let activityItem: AnyObject? = self.firstValidActivityItemInActivityItems(activityItems) 43 | 44 | if application.canOpenURL(safariURLScheme) && activityItem != nil 45 | { 46 | return true 47 | } 48 | } 49 | } 50 | 51 | return false 52 | } 53 | 54 | override func prepareWithActivityItems(activityItems: [AnyObject]) 55 | { 56 | if let activityItem: AnyObject = self.firstValidActivityItemInActivityItems(activityItems) 57 | { 58 | if activityItem is String 59 | { 60 | self.URL = NSURL(string: activityItem as! String) 61 | } 62 | else if activityItem is NSURL 63 | { 64 | self.URL = activityItem as? NSURL 65 | } 66 | } 67 | } 68 | 69 | override func performActivity() 70 | { 71 | let application = UIApplication.rst_sharedApplication() 72 | 73 | if self.URL == nil || application == nil 74 | { 75 | return self.activityDidFinish(false) 76 | } 77 | 78 | let finished = application.rst_openURL(self.URL) 79 | self.activityDidFinish(finished) 80 | } 81 | 82 | func firstValidActivityItemInActivityItems(activityItems: [AnyObject]) -> AnyObject? 83 | { 84 | if let application = UIApplication.rst_sharedApplication() 85 | { 86 | for activityItem in activityItems 87 | { 88 | var URL: NSURL? 89 | 90 | if activityItem is String 91 | { 92 | URL = NSURL(string: activityItem as! String) 93 | } 94 | else if activityItem is NSURL 95 | { 96 | URL = activityItem as? NSURL 97 | } 98 | 99 | if let URL = URL 100 | { 101 | if application.canOpenURL(URL) 102 | { 103 | return activityItem 104 | } 105 | } 106 | } 107 | } 108 | 109 | return nil 110 | } 111 | 112 | } 113 | -------------------------------------------------------------------------------- /Demo/Demo/Base.lproj/LaunchScreen.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 20 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /RSTWebViewController/RSTWebViewController/RSTURLActivityItem.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RSTURLActivityItem.swift 3 | // RSTWebViewController 4 | // 5 | // Created by Riley Testut on 12/26/14. 6 | // Copyright (c) 2014 Riley Testut. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import MobileCoreServices 11 | 12 | internal extension RSTURLActivityItem 13 | { 14 | // Set an item to be provided for the given activityType 15 | func setItem(item: AnyObject?, forActivityType activityType: String) 16 | { 17 | if let item: AnyObject = item 18 | { 19 | if self.itemDictionary == nil 20 | { 21 | self.itemDictionary = [String: NSExtensionItem]() 22 | } 23 | 24 | self.itemDictionary![activityType] = item 25 | } 26 | else 27 | { 28 | self.itemDictionary?[activityType] = nil 29 | 30 | if self.itemDictionary?.count == 0 31 | { 32 | self.itemDictionary = nil 33 | } 34 | } 35 | } 36 | 37 | // Returns item that will be provided for the given activityType 38 | func itemForActivityType(activityType: String) -> AnyObject? 39 | { 40 | return self.itemDictionary?[activityType] 41 | } 42 | } 43 | 44 | internal class RSTURLActivityItem: NSObject, UIActivityItemSource 45 | { 46 | internal var title: String? 47 | internal var URL: NSURL 48 | internal var typeIdentifier = kUTTypeURL 49 | 50 | private var itemDictionary: [String: AnyObject]? 51 | 52 | init(URL: NSURL) 53 | { 54 | self.URL = URL 55 | 56 | super.init() 57 | } 58 | 59 | func activityViewControllerPlaceholderItem(activityViewController: UIActivityViewController) -> AnyObject 60 | { 61 | return self.URL 62 | } 63 | 64 | func activityViewController(activityViewController: UIActivityViewController, itemForActivityType activityType: String) -> AnyObject? 65 | { 66 | if let item: AnyObject = self.itemDictionary?[activityType] 67 | { 68 | return item 69 | } 70 | 71 | let extensionActivityTypes: [String] = [UIActivityTypePostToTwitter, UIActivityTypePostToFacebook, UIActivityTypePostToWeibo, UIActivityTypePostToFlickr, UIActivityTypePostToVimeo, UIActivityTypePostToTencentWeibo] 72 | let applicationActivityTypes: [String] = [RSTActivityTypeSafari, RSTActivityTypeChrome] 73 | 74 | if self.title != nil && !contains(applicationActivityTypes, activityType) && (!activityType.lowercaseString.hasPrefix("com.apple") || contains(extensionActivityTypes, activityType)) 75 | { 76 | let item = NSExtensionItem() 77 | 78 | // Theoretically, attributedTitle would be most appropriate for a URL title, but Apple supplies URL titles as attributedContentText from Safari 79 | // In addition, Apple's own share extensions (Twitter, Facebook, etc.) only use the attributedContentText property to fill in their compose view 80 | // So, to ensure all share/action extensions can access the URL title, we set it for both attributedTitle and attributedContentText 81 | item.attributedTitle = NSAttributedString(string: self.title!) 82 | item.attributedContentText = item.attributedTitle 83 | 84 | item.attachments = [NSItemProvider(item: self.URL, typeIdentifier: kUTTypeURL as String)] 85 | 86 | return item 87 | } 88 | 89 | return self.URL 90 | } 91 | 92 | func activityViewController(activityViewController: UIActivityViewController, subjectForActivityType activityType: String?) -> String 93 | { 94 | return self.title ?? "" 95 | } 96 | 97 | func activityViewController(activityViewController: UIActivityViewController, dataTypeIdentifierForActivityType activityType: String?) -> String 98 | { 99 | return self.typeIdentifier as String 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /RSTWebViewController/RSTWebViewController/RSTChromeActivity.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RSTChromeActivity.swift 3 | // RSTWebViewController 4 | // 5 | // Created by Riley Testut on 12/26/14. 6 | // Copyright (c) 2014 Riley Testut. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | internal class RSTChromeActivity: UIActivity { 12 | 13 | private var URL: NSURL? 14 | 15 | override class func activityCategory() -> UIActivityCategory 16 | { 17 | return .Share 18 | } 19 | 20 | override func activityType() -> String? 21 | { 22 | return RSTActivityTypeChrome 23 | } 24 | 25 | override func activityTitle() -> String? 26 | { 27 | return NSLocalizedString("Chrome", comment: "") 28 | } 29 | 30 | override func activityImage() -> UIImage? 31 | { 32 | let bundle = NSBundle(forClass: RSTChromeActivity.self) 33 | return UIImage(named: "chrome_activity", inBundle: bundle, compatibleWithTraitCollection: nil) 34 | } 35 | 36 | override func canPerformWithActivityItems(activityItems: [AnyObject]) -> Bool 37 | { 38 | if let application = UIApplication.rst_sharedApplication() 39 | { 40 | if let chromeURLScheme = NSURL(string: "googlechrome://") 41 | { 42 | let activityItem: AnyObject? = self.firstValidActivityItemInActivityItems(activityItems) 43 | 44 | if application.canOpenURL(chromeURLScheme) && activityItem != nil 45 | { 46 | return true 47 | } 48 | } 49 | } 50 | 51 | return false 52 | } 53 | 54 | override func prepareWithActivityItems(activityItems: [AnyObject]) 55 | { 56 | if let activityItem: AnyObject = self.firstValidActivityItemInActivityItems(activityItems) 57 | { 58 | if activityItem is String 59 | { 60 | self.URL = NSURL(string: activityItem as! String) 61 | } 62 | else if activityItem is NSURL 63 | { 64 | self.URL = activityItem as? NSURL 65 | } 66 | } 67 | } 68 | 69 | override func performActivity() 70 | { 71 | let application = UIApplication.rst_sharedApplication() 72 | 73 | if self.URL == nil || application == nil 74 | { 75 | return self.activityDidFinish(false) 76 | } 77 | 78 | if let components = NSURLComponents(URL: self.URL!, resolvingAgainstBaseURL: false) 79 | { 80 | let scheme = components.scheme?.lowercaseString 81 | 82 | if scheme != nil && scheme == "https" 83 | { 84 | components.scheme = "googlechromes" 85 | } 86 | else 87 | { 88 | components.scheme = "googlechrome" 89 | } 90 | 91 | let finished = application.rst_openURL(components.URL) 92 | self.activityDidFinish(finished) 93 | } 94 | else 95 | { 96 | self.activityDidFinish(false) 97 | } 98 | } 99 | 100 | func firstValidActivityItemInActivityItems(activityItems: [AnyObject]) -> AnyObject? 101 | { 102 | if let application = UIApplication.rst_sharedApplication() 103 | { 104 | for activityItem in activityItems 105 | { 106 | var URL: NSURL? 107 | 108 | if activityItem is String 109 | { 110 | URL = NSURL(string: activityItem as! String) 111 | } 112 | else if activityItem is NSURL 113 | { 114 | URL = activityItem as? NSURL 115 | } 116 | 117 | if let URL = URL 118 | { 119 | if application.canOpenURL(URL) 120 | { 121 | return activityItem 122 | } 123 | } 124 | } 125 | } 126 | 127 | return nil 128 | } 129 | 130 | } 131 | -------------------------------------------------------------------------------- /Demo/Demo/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 28 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /RSTWebViewController/RSTWebViewController/RSTOnePasswordExtension.h: -------------------------------------------------------------------------------- 1 | // 2 | // 1Password Extension 3 | // 4 | // Lovingly handcrafted by Dave Teare, Michael Fey, Rad Azzouz, and Roustem Karimov. 5 | // Copyright (c) 2014 AgileBits. All rights reserved. 6 | // 7 | 8 | #import 9 | #import 10 | #import 11 | 12 | #ifdef __IPHONE_8_0 13 | #import 14 | #endif 15 | 16 | // Login Dictionary keys 17 | #define AppExtensionURLStringKey @"url_string" 18 | #define AppExtensionUsernameKey @"username" 19 | #define AppExtensionPasswordKey @"password" 20 | #define AppExtensionTitleKey @"login_title" 21 | #define AppExtensionNotesKey @"notes" 22 | #define AppExtensionSectionTitleKey @"section_title" 23 | #define AppExtensionFieldsKey @"fields" 24 | #define AppExtensionReturnedFieldsKey @"returned_fields" 25 | #define AppExtensionOldPasswordKey @"old_password" 26 | #define AppExtensionPasswordGereratorOptionsKey @"password_generator_options" 27 | 28 | // Password Generator options 29 | #define AppExtensionGeneratedPasswordMinLengthKey @"password_min_length" 30 | #define AppExtensionGeneratedPasswordMaxLengthKey @"password_max_length" 31 | 32 | // Errors 33 | #define AppExtensionErrorDomain @"OnePasswordExtension" 34 | 35 | #define AppExtensionErrorCodeCancelledByUser 0 36 | #define AppExtensionErrorCodeAPINotAvailable 1 37 | #define AppExtensionErrorCodeFailedToContactExtension 2 38 | #define AppExtensionErrorCodeFailedToLoadItemProviderData 3 39 | #define AppExtensionErrorCodeCollectFieldsScriptFailed 4 40 | #define AppExtensionErrorCodeFillFieldsScriptFailed 5 41 | #define AppExtensionErrorCodeUnexpectedData 6 42 | #define AppExtensionErrorCodeFailedToObtainURLStringFromWebView 7 43 | 44 | // Note to creators of libraries or frameworks: 45 | // If you include this code within your library, then to prevent potential duplicate symbol 46 | // conflicts for adopters of your library, you should rename the OnePasswordExtension class. 47 | // You might to so by adding your own project prefix, e.g., MyLibraryOnePasswordExtension. 48 | 49 | @interface RSTOnePasswordExtension : NSObject 50 | 51 | + (RSTOnePasswordExtension *)sharedExtension; 52 | 53 | /*! 54 | Determines if the 1Password Extension is available. Allows you to only show the 1Password login button to those 55 | that can use it. Of course, you could leave the button enabled and educate users about the virtues of strong, unique 56 | passwords instead :) 57 | 58 | Note that this returns YES if any app that supports the generic `org-appextension-feature-password-management` feature 59 | is installed. 60 | */ 61 | #ifdef __IPHONE_8_0 62 | - (BOOL)isAppExtensionAvailable NS_EXTENSION_UNAVAILABLE_IOS("Not available in an extension. Check if org-appextension-feature-password-management:// URL can be opened by the app."); 63 | #else 64 | - (BOOL)isAppExtensionAvailable; 65 | #endif 66 | 67 | /*! 68 | Called from your login page, this method will find all available logins for the given URLString. After the user selects 69 | a login, it is stored into an NSDictionary and given to your completion handler. Use the `Login Dictionary keys` above to 70 | extract the needed information and update your UI. The completion block is guaranteed to be called on the main thread. 71 | */ 72 | - (void)findLoginForURLString:(NSString *)URLString forViewController:(UIViewController *)viewController sender:(id)sender completion:(void (^)(NSDictionary *loginDict, NSError *error))completion; 73 | 74 | /*! 75 | Create a new login within 1Password and allow the user to generate a new password before saving. The provided URLString should be 76 | unique to your app or service and be identical to what you pass into the find login method. 77 | 78 | Details about the saved login, including the generated password, are stored in an NSDictionary and given to your completion handler. 79 | Use the `Login Dictionary keys` above to extract the needed information and update your UI. For example, updating the UI with the 80 | newly generated password lets the user know their action was successful. The completion block is guaranteed to be called on the main 81 | thread. 82 | */ 83 | - (void)storeLoginForURLString:(NSString *)URLString loginDetails:(NSDictionary *)loginDetailsDict passwordGenerationOptions:(NSDictionary *)passwordGenerationOptionsOrNil forViewController:(UIViewController *)viewController sender:(id)sender completion:(void (^)(NSDictionary *loginDict, NSError *error))completion; 84 | 85 | /*! 86 | Change the password for an existing login within 1Password. The provided URLString should be 87 | unique to your app or service and be identical to what you pass into the find login method. The username must be the one that the user is currently logged in with. 88 | 89 | Details about the saved login, including the newly generated and the old password, are stored in an NSDictionary and given to your completion handler. 90 | Use the `Login Dictionary keys` above to extract the needed information and update your UI. For example, updating the UI with the 91 | newly generated password lets the user know their action was successful. The completion block is guaranteed to be called on the main 92 | thread. 93 | */ 94 | - (void)changePasswordForLoginForURLString:(NSString *)URLString loginDetails:(NSDictionary *)loginDetailsDict passwordGenerationOptions:(NSDictionary *)passwordGenerationOptions forViewController:(UIViewController *)viewController sender:(id)sender completion:(void (^)(NSDictionary *loginDict, NSError *error))completion; 95 | 96 | /*! 97 | Called from your web view controller, this method will show all the saved logins for the active page in the provided web 98 | view, and automatically fill the HTML form fields. Supports both WKWebView and UIWebView. 99 | */ 100 | - (void)fillLoginIntoWebView:(id)webView forViewController:(UIViewController *)viewController sender:(id)sender completion:(void (^)(BOOL success, NSError *error))completion; 101 | 102 | /*! 103 | * Low-level method used in the UIActivityViewController completion block to find if the activity was 104 | * performed by 1Password Extension. 105 | */ 106 | - (BOOL)isOnePasswordExtensionActivityType:(NSString *)activityType; 107 | 108 | /*! 109 | * Low-level method used instead of `findLoginForURLString:forViewController:sender:completion:` 110 | * 111 | * The returned NSExtensionItem can be used to create your own UIActivityViewController. Use `isOnePasswordExtensionActivityType:` and `processReturnedItems:completion:` in the activity view controller completion block to process the result. 112 | */ 113 | - (NSExtensionItem *)createExtensionItemToFindLoginForURLString:(NSString *)URLString; 114 | 115 | /*! 116 | * Low-level method used instead of `storeLoginForURLString:loginDetails:passwordGenerationOptions:forViewController:sender:completion:` 117 | * 118 | * The returned NSExtensionItem can be used to create your own UIActivityViewController. Use `isOnePasswordExtensionActivityType:` and `processReturnedItems:completion:` in the activity view controller completion block to process the result. 119 | */ 120 | - (NSExtensionItem *)createExtensionItemToStoreLoginForURLString:(NSString *)URLString loginDetails:(NSDictionary *)loginDetailsDict passwordGenerationOptions:(NSDictionary *)passwordGenerationOptionsOrNil; 121 | 122 | /*! 123 | * Low-level method used instead of `changePasswordForLoginForURLString:loginDetails:passwordGenerationOptions:forViewController:sender:completion:` 124 | * 125 | * The returned NSExtensionItem can be used to create your own UIActivityViewController. Use `isOnePasswordExtensionActivityType:` and `processReturnedItems:completion:` in the activity view controller completion block to process the result. 126 | */ 127 | 128 | - (NSExtensionItem *)createExtensionItemToChangePasswordForLoginForURLString:(NSString *)URLString loginDetails:(NSDictionary *)loginDetailsDict passwordGenerationOptions:(NSDictionary *)passwordGenerationOptionsOrNil; 129 | /*! 130 | * Low-level method used in the UIActivityViewController completion block to process the returnedItems. 131 | */ 132 | - (void)processReturnedItems:(NSArray *)returnedItems completion:(void (^)(NSDictionary *loginDict, NSError *error))completion; 133 | 134 | /*! 135 | * Low-level method used instead of `fillLoginIntoWebView:forViewController:sender:completion` 136 | * 137 | * The returned NSExtensionItem can be used to create your own UIActivityViewController. Use `isOnePasswordExtensionActivityType:` and `fillReturnedItems:intoWebView:completion:` in the activity view controller completion block to process the result. 138 | */ 139 | - (void)createExtensionItemForWebView:(id)webView completion:(void (^)(NSExtensionItem *extensionItem, NSError *error))completion; 140 | 141 | /*! 142 | * Low-level method used in the UIActivityViewController completion block to fill information into a web view. 143 | */ 144 | - (void)fillReturnedItems:(NSArray *)returnedItems intoWebView:(id)webView completion:(void (^)(BOOL success, NSError *error))completion; 145 | 146 | @end 147 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RSTWebViewController 2 | 3 | ![RSTWebViewController](https://cloud.githubusercontent.com/assets/705880/5564786/3ea66d44-8e9a-11e4-932c-2940225754f5.png) 4 | 5 | RSTWebViewController is a simple, yet powerful, in-app web browser for your iOS application. Unlike countless other in-app web browsers you can find, RSTWebViewController has been designed from the ground-up to take full advantage of iOS 8 features, notably the vastly superior WebKit.framework, adaptability APIs, as well as full support for both Share and Action Extensions, including the incredibly useful [1Password Extension](https://github.com/AgileBits/onepassword-app-extension). 6 | 7 | ## Features 8 | 9 | • Uses brand new WKWebView class to ensure blazingly fast performance. (**Exclusive**!) 10 | • Built-in support for 1Password, integrated directly into the share sheet without the need to display a separate button. (**Exclusive**!) 11 | • Beautiful, full color Share icons to open the current link in either Safari or Chrome (if installed). (**Exclusive**!) 12 | • Customizable share sheet allowing you to disable any existing UIActivities, or to add your own application-specific UIActivities. (**Exclusive**!) 13 | • Adaptable UI, ensuring the layout is optimized for the device you’re on, and dynamically adjusting the layout should certain size classes change (looking at you, iPhone 6 Plus). (**Exclusive**!) 14 | • Fully App-Extension-Safe API compliant, dynamically enabling and disabling features depending on whether RSTWebViewController is being used in an Application or an Application Extension. (**Exclusive**!) 15 | • Displays progress bar to give a better indication of when web pages will finish loading. 16 | • Written (almost) entirely in Swift (because why not), yet fully supports being called by both Objective-C and Swift code. 17 | 18 | ## Requirements 19 | 20 | • iOS 8.0+ 21 | • Xcode 6.1 22 | 23 | ## Installation 24 | 25 | I am a huge fan of [CocoaPods](http://cocoapods.org), and I strongly recommend you trying it out for yourself as a better way to use open source code. That being said, while the upcoming CocoaPods 0.36 [will bring full support for Swift code](http://blog.cocoapods.org/Pod-Authors-Guide-to-CocoaPods-Frameworks/), it is not yet ready for everyone to use (reliably). Because of this, for the time being I recommend dragging the RSTWebViewController Xcode project into your project/workspace, and linking against the built framework. 26 | 27 | ### Installing as Sub-Project 28 | 29 | 1. Drag `RSTWebViewController.xcodeproj` into a subfolder of your Xcode project in the Files pane. 30 | ![Drag .xcodeproj](https://cloud.githubusercontent.com/assets/705880/5563568/9fed5018-8e45-11e4-8403-6ccc8b598ced.png) 31 | 2. Navigate to the Target Configuration Window by clicking the blue project icon, then select your app target in the “Targets” section of the sidebar. 32 | 3. Select the “General” tab at the top. 33 | 4. In the “Embedded Binaries” section, click the “+”, and select `RSTWebViewController.framework` from within your app project section. Afterwards, you should see this: 34 | ![Embedded RSTWebViewController.framework](https://cloud.githubusercontent.com/assets/705880/5563577/4fbdc158-8e46-11e4-8b43-e0d87189de0b.png) 35 | 5. Click the "Build Settings" tab at the top. 36 | 6. Ensure "Embedded Content Contains Swift Code" is set to YES for your target. 37 | 38 | ### Installing as Top-Level Workspace Project 39 | (Note: this is _slightly_ more complicated than installing as a sub-project, and it provides practically no benefit. I only do this because for whatever reason I much prefer having all dependencies as top-level projects in my workspace as opposed to them being contained within another project’s file hierarchy.) 40 | 41 | 1. Drag `RSTWebViewController.xcodeproj` into the Files pane _above_ your Xcode project. 42 | ![Drag .xcodeproj](https://cloud.githubusercontent.com/assets/705880/5563572/d13c24e6-8e45-11e4-86eb-fc0220df6bdd.png) 43 | 2. If you are not currently working in a Workspace, Xcode will ask you if you would like to save the project in a new workspace. Click “Save”, and name your new workspace (typically I use the same name as my app .xcodeproj). 44 | 3. Navigate to the Target Configuration Window of your app project by clicking your app project’s blue icon, then select your app target in the “Targets” section of the sidebar. 45 | 4. Select the “General” tab at the top. 46 | 5. In the “Embedded Binaries” section, click the “+”, and select `RSTWebViewController.framework` from within the RSTWebViewController section. Afterwards, you should see something like this: 47 | ![Embedded RSTWebViewController.framework](https://cloud.githubusercontent.com/assets/705880/5563578/5128f4f4-8e46-11e4-9e6d-a312c9d52303.png) 48 | 5. Select `RSTWebViewController.framework` in your app project in the Files pane. 49 | 6. Open the right “File Inspector” pane if it is not already open, and change “Location” from “Absolute Path” to “Relative to Build Products” like so: 50 | ![Change Location](https://cloud.githubusercontent.com/assets/705880/5563745/e4ee8246-8e53-11e4-9d81-8480745bec95.png) 51 | This ensures the correct build of the framework will be included with your application. Once you've done that, you should see the grayed out path of `RSTWebViewController.framework` has changed to this (you may need to select another file then return to the project file for it to update): 52 | ![Embedded RSTWebViewController.framework](https://cloud.githubusercontent.com/assets/705880/5563577/4fbdc158-8e46-11e4-8b43-e0d87189de0b.png) 53 | 7. Click the "Build Settings" tab at the top. 54 | 8. Ensure "Embedded Content Contains Swift Code" is set to YES for your target. 55 | 56 | ## Usage 57 | 58 | ### General 59 | 60 | First, ensure you import the framework into your current file: 61 | 62 | // Objective-C 63 | @import RSTWebViewController; 64 | 65 | // Swift 66 | import RSTWebViewController 67 | 68 | Once you’ve imported the framework, actually using it is rather straightforward. However, one thing to note is that RSTWebViewController _must_ be contained within a UINavigationController. If you’re pushing it onto a navigation stack, it will work perfectly fine, but if presenting modally, make sure to first initialize a UINavigationController with RSTWebViewController as the rootViewController, and then present the UINavigationController. 69 | 70 | // Objective-C 71 | - (IBAction)presentWebViewController:(UIButton *)sender 72 | { 73 | RSTWebViewController *webViewController = [[RSTWebViewController alloc] initWithAddress:@"http://rileytestut.com"]; 74 | webViewController.showsDoneButton = YES; 75 | 76 | UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:webViewController]; 77 | [self presentViewController:navigationController animated:YES completion:nil]; 78 | } 79 | 80 | // Swift 81 | @IBAction func presentWebViewController(sender: UIButton) { 82 | let webViewController = RSTWebViewController(address: "http://rileytestut.com") 83 | webViewController.showsDoneButton = true 84 | 85 | let navigationController = UINavigationController(rootViewController: webViewController) 86 | self.presentViewController(navigationController, animated: true, completion: nil) 87 | } 88 | 89 | ### Share Sheet 90 | 91 | RSTWebViewController presents a standard UIActivityViewController when the user taps the Share button, allowing the user to select from a multitude of sharing options, including 3rd-party Share and Action Extensions. However, you're not limited to just these options, as you can supply RSTWebViewController with your own application-specific UIActivities. To do so, simply set `applicationActivities` to a non-nil NSArray of custom UIActivity subclasses. 92 | 93 | // Objective-C 94 | webViewController.applicationActivities = @[[RSTCustomActivity new], [GBADownloadActivity new]]; 95 | 96 | // Swift 97 | webViewController.applicationActivities = [RSTCustomActivity(), GBADownloadActivity()] 98 | 99 | Additionally, you can choose to exclude certain built-in activities from the share sheet. To do so, simply set `excludedActivityTypes` to a non-nil NSArray of UIActivity types you wish to prevent from appearing in the share sheet (currently, there is no way to exclude 3rd-party App Extensions). 100 | 101 | // Objective-C 102 | webViewController.excludedActivityTypes = @[UIActivityTypePostToTwitter, UIActivityTypeMail, RSTActivityTypeChrome]; 103 | 104 | // Swift 105 | webViewController.excludedActivityTypes = [UIActivityTypePostToTwitter, UIActivityTypeMail, RSTActivityTypeChrome] 106 | 107 | ### 1Password 108 | 109 | Unfortunately, even the new WKWebView class doesn't provide the user access to any of their iCloud Saved Passwords, so whenever the user comes to a login page, they need to manually enter their credentials themself. However, the amazing folks at 1Password have put together an Action Extension that helps with this very problem. Included in the share sheet is said Action Extension which allows the user to pick from their stored 1Password logins and automatically fill their credentials into whatever web page they're on. 110 | 111 | Unlike other implementations of this 1Password extension, RSTWebViewController is unique in that it can display the extension in the _same_ share sheet as the other sharing options, _without_ accidentally disabling certain share activities such as posting to Twitter and Facebook. However, for this to work, you have to modify your app project file ever so slightly: 112 | 113 | 1. Click your app project's blue icon in the Files pane, then select your app target from the "Targets" section. 114 | 2. Click the "Info" tab at the top. 115 | 3. Expand the "Imported UTIs" section. 116 | 4. Click the "+" twice, and fill the sections out exactly like this screenshot: ![1Password UTI](https://cloud.githubusercontent.com/assets/705880/5585026/3c88e890-9064-11e4-8911-bd1cf25f1e67.png) 117 | 118 | Once you've done this, the 1Password extension will automatically show up in the share sheet if 1Password is installed, and the rest will be handled for you automatically by RSTWebViewController. 119 | 120 | ## License 121 | 122 | RSTWebViewController is licensed under the MIT License, which is reproduced in full in the LICENSE file. While not required, any attribution in your project is much appreciated, since I love to see how my code is being used in all of your projects! 123 | 124 | ## Contact 125 | 126 | I'm [@rileytestut on Twitter](http://twitter.com/rileytestut), and I tend to handle simple questions there. However, you may also choose to email me at [riley@rileytestut.com](mailto:riley@rileytestut.com) if you have any questions that might be harder to answer in just 140 characters. 127 | -------------------------------------------------------------------------------- /Demo/Demo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | BF7E5F1F1A4A15DB00106AAB /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = BF7E5F1E1A4A15DB00106AAB /* AppDelegate.m */; }; 11 | BF7E5F221A4A15DB00106AAB /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = BF7E5F211A4A15DB00106AAB /* ViewController.m */; }; 12 | BF7E5F251A4A15DB00106AAB /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = BF7E5F231A4A15DB00106AAB /* Main.storyboard */; }; 13 | BF7E5F271A4A15DB00106AAB /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = BF7E5F261A4A15DB00106AAB /* Images.xcassets */; }; 14 | BF7E5F2A1A4A15DB00106AAB /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = BF7E5F281A4A15DB00106AAB /* LaunchScreen.xib */; }; 15 | BF7E5F361A4A15DB00106AAB /* DemoTests.m in Sources */ = {isa = PBXBuildFile; fileRef = BF7E5F351A4A15DB00106AAB /* DemoTests.m */; }; 16 | BF7E5F431A4A18A600106AAB /* RSTWebViewController.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF7E5F421A4A18A600106AAB /* RSTWebViewController.framework */; }; 17 | BF7E5F441A4A18A600106AAB /* RSTWebViewController.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = BF7E5F421A4A18A600106AAB /* RSTWebViewController.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 18 | BF8D33C51A4B5126004CE026 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = BF7E5F1B1A4A15DB00106AAB /* main.m */; }; 19 | /* End PBXBuildFile section */ 20 | 21 | /* Begin PBXContainerItemProxy section */ 22 | BF7E5F301A4A15DB00106AAB /* PBXContainerItemProxy */ = { 23 | isa = PBXContainerItemProxy; 24 | containerPortal = BF7E5F0E1A4A15DB00106AAB /* Project object */; 25 | proxyType = 1; 26 | remoteGlobalIDString = BF7E5F151A4A15DB00106AAB; 27 | remoteInfo = Demo; 28 | }; 29 | /* End PBXContainerItemProxy section */ 30 | 31 | /* Begin PBXCopyFilesBuildPhase section */ 32 | BF7E5F451A4A18A600106AAB /* Embed Frameworks */ = { 33 | isa = PBXCopyFilesBuildPhase; 34 | buildActionMask = 2147483647; 35 | dstPath = ""; 36 | dstSubfolderSpec = 10; 37 | files = ( 38 | BF7E5F441A4A18A600106AAB /* RSTWebViewController.framework in Embed Frameworks */, 39 | ); 40 | name = "Embed Frameworks"; 41 | runOnlyForDeploymentPostprocessing = 0; 42 | }; 43 | /* End PBXCopyFilesBuildPhase section */ 44 | 45 | /* Begin PBXFileReference section */ 46 | BF7E5F161A4A15DB00106AAB /* Demo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Demo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 47 | BF7E5F1A1A4A15DB00106AAB /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 48 | BF7E5F1B1A4A15DB00106AAB /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 49 | BF7E5F1D1A4A15DB00106AAB /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 50 | BF7E5F1E1A4A15DB00106AAB /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 51 | BF7E5F201A4A15DB00106AAB /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; 52 | BF7E5F211A4A15DB00106AAB /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; 53 | BF7E5F241A4A15DB00106AAB /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 54 | BF7E5F261A4A15DB00106AAB /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 55 | BF7E5F291A4A15DB00106AAB /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; }; 56 | BF7E5F2F1A4A15DB00106AAB /* DemoTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = DemoTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 57 | BF7E5F341A4A15DB00106AAB /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 58 | BF7E5F351A4A15DB00106AAB /* DemoTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DemoTests.m; sourceTree = ""; }; 59 | BF7E5F421A4A18A600106AAB /* RSTWebViewController.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = RSTWebViewController.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 60 | /* End PBXFileReference section */ 61 | 62 | /* Begin PBXFrameworksBuildPhase section */ 63 | BF7E5F131A4A15DB00106AAB /* Frameworks */ = { 64 | isa = PBXFrameworksBuildPhase; 65 | buildActionMask = 2147483647; 66 | files = ( 67 | BF7E5F431A4A18A600106AAB /* RSTWebViewController.framework in Frameworks */, 68 | ); 69 | runOnlyForDeploymentPostprocessing = 0; 70 | }; 71 | BF7E5F2C1A4A15DB00106AAB /* Frameworks */ = { 72 | isa = PBXFrameworksBuildPhase; 73 | buildActionMask = 2147483647; 74 | files = ( 75 | ); 76 | runOnlyForDeploymentPostprocessing = 0; 77 | }; 78 | /* End PBXFrameworksBuildPhase section */ 79 | 80 | /* Begin PBXGroup section */ 81 | BF7E5F0D1A4A15DB00106AAB = { 82 | isa = PBXGroup; 83 | children = ( 84 | BF7E5F181A4A15DB00106AAB /* Demo */, 85 | BF7E5F321A4A15DB00106AAB /* DemoTests */, 86 | BF7E5F461A4A18AE00106AAB /* Frameworks */, 87 | BF7E5F171A4A15DB00106AAB /* Products */, 88 | ); 89 | sourceTree = ""; 90 | }; 91 | BF7E5F171A4A15DB00106AAB /* Products */ = { 92 | isa = PBXGroup; 93 | children = ( 94 | BF7E5F161A4A15DB00106AAB /* Demo.app */, 95 | BF7E5F2F1A4A15DB00106AAB /* DemoTests.xctest */, 96 | ); 97 | name = Products; 98 | sourceTree = ""; 99 | }; 100 | BF7E5F181A4A15DB00106AAB /* Demo */ = { 101 | isa = PBXGroup; 102 | children = ( 103 | BF7E5F1D1A4A15DB00106AAB /* AppDelegate.h */, 104 | BF7E5F1E1A4A15DB00106AAB /* AppDelegate.m */, 105 | BF7E5F201A4A15DB00106AAB /* ViewController.h */, 106 | BF7E5F211A4A15DB00106AAB /* ViewController.m */, 107 | BF7E5F231A4A15DB00106AAB /* Main.storyboard */, 108 | BF7E5F3F1A4A161300106AAB /* Resources */, 109 | BF7E5F191A4A15DB00106AAB /* Supporting Files */, 110 | ); 111 | path = Demo; 112 | sourceTree = ""; 113 | }; 114 | BF7E5F191A4A15DB00106AAB /* Supporting Files */ = { 115 | isa = PBXGroup; 116 | children = ( 117 | BF7E5F1A1A4A15DB00106AAB /* Info.plist */, 118 | BF7E5F1B1A4A15DB00106AAB /* main.m */, 119 | ); 120 | name = "Supporting Files"; 121 | sourceTree = ""; 122 | }; 123 | BF7E5F321A4A15DB00106AAB /* DemoTests */ = { 124 | isa = PBXGroup; 125 | children = ( 126 | BF7E5F351A4A15DB00106AAB /* DemoTests.m */, 127 | BF7E5F331A4A15DB00106AAB /* Supporting Files */, 128 | ); 129 | path = DemoTests; 130 | sourceTree = ""; 131 | }; 132 | BF7E5F331A4A15DB00106AAB /* Supporting Files */ = { 133 | isa = PBXGroup; 134 | children = ( 135 | BF7E5F341A4A15DB00106AAB /* Info.plist */, 136 | ); 137 | name = "Supporting Files"; 138 | sourceTree = ""; 139 | }; 140 | BF7E5F3F1A4A161300106AAB /* Resources */ = { 141 | isa = PBXGroup; 142 | children = ( 143 | BF7E5F261A4A15DB00106AAB /* Images.xcassets */, 144 | BF7E5F281A4A15DB00106AAB /* LaunchScreen.xib */, 145 | ); 146 | name = Resources; 147 | sourceTree = ""; 148 | }; 149 | BF7E5F461A4A18AE00106AAB /* Frameworks */ = { 150 | isa = PBXGroup; 151 | children = ( 152 | BF7E5F421A4A18A600106AAB /* RSTWebViewController.framework */, 153 | ); 154 | name = Frameworks; 155 | sourceTree = ""; 156 | }; 157 | /* End PBXGroup section */ 158 | 159 | /* Begin PBXNativeTarget section */ 160 | BF7E5F151A4A15DB00106AAB /* Demo */ = { 161 | isa = PBXNativeTarget; 162 | buildConfigurationList = BF7E5F391A4A15DB00106AAB /* Build configuration list for PBXNativeTarget "Demo" */; 163 | buildPhases = ( 164 | BF7E5F121A4A15DB00106AAB /* Sources */, 165 | BF7E5F131A4A15DB00106AAB /* Frameworks */, 166 | BF7E5F141A4A15DB00106AAB /* Resources */, 167 | BF7E5F451A4A18A600106AAB /* Embed Frameworks */, 168 | ); 169 | buildRules = ( 170 | ); 171 | dependencies = ( 172 | ); 173 | name = Demo; 174 | productName = Demo; 175 | productReference = BF7E5F161A4A15DB00106AAB /* Demo.app */; 176 | productType = "com.apple.product-type.application"; 177 | }; 178 | BF7E5F2E1A4A15DB00106AAB /* DemoTests */ = { 179 | isa = PBXNativeTarget; 180 | buildConfigurationList = BF7E5F3C1A4A15DB00106AAB /* Build configuration list for PBXNativeTarget "DemoTests" */; 181 | buildPhases = ( 182 | BF7E5F2B1A4A15DB00106AAB /* Sources */, 183 | BF7E5F2C1A4A15DB00106AAB /* Frameworks */, 184 | BF7E5F2D1A4A15DB00106AAB /* Resources */, 185 | ); 186 | buildRules = ( 187 | ); 188 | dependencies = ( 189 | BF7E5F311A4A15DB00106AAB /* PBXTargetDependency */, 190 | ); 191 | name = DemoTests; 192 | productName = DemoTests; 193 | productReference = BF7E5F2F1A4A15DB00106AAB /* DemoTests.xctest */; 194 | productType = "com.apple.product-type.bundle.unit-test"; 195 | }; 196 | /* End PBXNativeTarget section */ 197 | 198 | /* Begin PBXProject section */ 199 | BF7E5F0E1A4A15DB00106AAB /* Project object */ = { 200 | isa = PBXProject; 201 | attributes = { 202 | LastUpgradeCheck = 0620; 203 | ORGANIZATIONNAME = "Riley Testut"; 204 | TargetAttributes = { 205 | BF7E5F151A4A15DB00106AAB = { 206 | CreatedOnToolsVersion = 6.2; 207 | }; 208 | BF7E5F2E1A4A15DB00106AAB = { 209 | CreatedOnToolsVersion = 6.2; 210 | TestTargetID = BF7E5F151A4A15DB00106AAB; 211 | }; 212 | }; 213 | }; 214 | buildConfigurationList = BF7E5F111A4A15DB00106AAB /* Build configuration list for PBXProject "Demo" */; 215 | compatibilityVersion = "Xcode 3.2"; 216 | developmentRegion = English; 217 | hasScannedForEncodings = 0; 218 | knownRegions = ( 219 | en, 220 | Base, 221 | ); 222 | mainGroup = BF7E5F0D1A4A15DB00106AAB; 223 | productRefGroup = BF7E5F171A4A15DB00106AAB /* Products */; 224 | projectDirPath = ""; 225 | projectRoot = ""; 226 | targets = ( 227 | BF7E5F151A4A15DB00106AAB /* Demo */, 228 | BF7E5F2E1A4A15DB00106AAB /* DemoTests */, 229 | ); 230 | }; 231 | /* End PBXProject section */ 232 | 233 | /* Begin PBXResourcesBuildPhase section */ 234 | BF7E5F141A4A15DB00106AAB /* Resources */ = { 235 | isa = PBXResourcesBuildPhase; 236 | buildActionMask = 2147483647; 237 | files = ( 238 | BF7E5F251A4A15DB00106AAB /* Main.storyboard in Resources */, 239 | BF7E5F2A1A4A15DB00106AAB /* LaunchScreen.xib in Resources */, 240 | BF7E5F271A4A15DB00106AAB /* Images.xcassets in Resources */, 241 | ); 242 | runOnlyForDeploymentPostprocessing = 0; 243 | }; 244 | BF7E5F2D1A4A15DB00106AAB /* Resources */ = { 245 | isa = PBXResourcesBuildPhase; 246 | buildActionMask = 2147483647; 247 | files = ( 248 | ); 249 | runOnlyForDeploymentPostprocessing = 0; 250 | }; 251 | /* End PBXResourcesBuildPhase section */ 252 | 253 | /* Begin PBXSourcesBuildPhase section */ 254 | BF7E5F121A4A15DB00106AAB /* Sources */ = { 255 | isa = PBXSourcesBuildPhase; 256 | buildActionMask = 2147483647; 257 | files = ( 258 | BF7E5F221A4A15DB00106AAB /* ViewController.m in Sources */, 259 | BF8D33C51A4B5126004CE026 /* main.m in Sources */, 260 | BF7E5F1F1A4A15DB00106AAB /* AppDelegate.m in Sources */, 261 | ); 262 | runOnlyForDeploymentPostprocessing = 0; 263 | }; 264 | BF7E5F2B1A4A15DB00106AAB /* Sources */ = { 265 | isa = PBXSourcesBuildPhase; 266 | buildActionMask = 2147483647; 267 | files = ( 268 | BF7E5F361A4A15DB00106AAB /* DemoTests.m in Sources */, 269 | ); 270 | runOnlyForDeploymentPostprocessing = 0; 271 | }; 272 | /* End PBXSourcesBuildPhase section */ 273 | 274 | /* Begin PBXTargetDependency section */ 275 | BF7E5F311A4A15DB00106AAB /* PBXTargetDependency */ = { 276 | isa = PBXTargetDependency; 277 | target = BF7E5F151A4A15DB00106AAB /* Demo */; 278 | targetProxy = BF7E5F301A4A15DB00106AAB /* PBXContainerItemProxy */; 279 | }; 280 | /* End PBXTargetDependency section */ 281 | 282 | /* Begin PBXVariantGroup section */ 283 | BF7E5F231A4A15DB00106AAB /* Main.storyboard */ = { 284 | isa = PBXVariantGroup; 285 | children = ( 286 | BF7E5F241A4A15DB00106AAB /* Base */, 287 | ); 288 | name = Main.storyboard; 289 | sourceTree = ""; 290 | }; 291 | BF7E5F281A4A15DB00106AAB /* LaunchScreen.xib */ = { 292 | isa = PBXVariantGroup; 293 | children = ( 294 | BF7E5F291A4A15DB00106AAB /* Base */, 295 | ); 296 | name = LaunchScreen.xib; 297 | sourceTree = ""; 298 | }; 299 | /* End PBXVariantGroup section */ 300 | 301 | /* Begin XCBuildConfiguration section */ 302 | BF7E5F371A4A15DB00106AAB /* Debug */ = { 303 | isa = XCBuildConfiguration; 304 | buildSettings = { 305 | ALWAYS_SEARCH_USER_PATHS = NO; 306 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 307 | CLANG_CXX_LIBRARY = "libc++"; 308 | CLANG_ENABLE_MODULES = YES; 309 | CLANG_ENABLE_OBJC_ARC = YES; 310 | CLANG_WARN_BOOL_CONVERSION = YES; 311 | CLANG_WARN_CONSTANT_CONVERSION = YES; 312 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 313 | CLANG_WARN_EMPTY_BODY = YES; 314 | CLANG_WARN_ENUM_CONVERSION = YES; 315 | CLANG_WARN_INT_CONVERSION = YES; 316 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 317 | CLANG_WARN_UNREACHABLE_CODE = YES; 318 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 319 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 320 | COPY_PHASE_STRIP = NO; 321 | EMBEDDED_CONTENT_CONTAINS_SWIFT = YES; 322 | ENABLE_STRICT_OBJC_MSGSEND = YES; 323 | GCC_C_LANGUAGE_STANDARD = gnu99; 324 | GCC_DYNAMIC_NO_PIC = NO; 325 | GCC_OPTIMIZATION_LEVEL = 0; 326 | GCC_PREPROCESSOR_DEFINITIONS = ( 327 | "DEBUG=1", 328 | "$(inherited)", 329 | ); 330 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 331 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 332 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 333 | GCC_WARN_UNDECLARED_SELECTOR = YES; 334 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 335 | GCC_WARN_UNUSED_FUNCTION = YES; 336 | GCC_WARN_UNUSED_VARIABLE = YES; 337 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 338 | MTL_ENABLE_DEBUG_INFO = YES; 339 | ONLY_ACTIVE_ARCH = YES; 340 | SDKROOT = iphoneos; 341 | }; 342 | name = Debug; 343 | }; 344 | BF7E5F381A4A15DB00106AAB /* Release */ = { 345 | isa = XCBuildConfiguration; 346 | buildSettings = { 347 | ALWAYS_SEARCH_USER_PATHS = NO; 348 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 349 | CLANG_CXX_LIBRARY = "libc++"; 350 | CLANG_ENABLE_MODULES = YES; 351 | CLANG_ENABLE_OBJC_ARC = YES; 352 | CLANG_WARN_BOOL_CONVERSION = YES; 353 | CLANG_WARN_CONSTANT_CONVERSION = YES; 354 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 355 | CLANG_WARN_EMPTY_BODY = YES; 356 | CLANG_WARN_ENUM_CONVERSION = YES; 357 | CLANG_WARN_INT_CONVERSION = YES; 358 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 359 | CLANG_WARN_UNREACHABLE_CODE = YES; 360 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 361 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 362 | COPY_PHASE_STRIP = NO; 363 | EMBEDDED_CONTENT_CONTAINS_SWIFT = YES; 364 | ENABLE_NS_ASSERTIONS = NO; 365 | ENABLE_STRICT_OBJC_MSGSEND = YES; 366 | GCC_C_LANGUAGE_STANDARD = gnu99; 367 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 368 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 369 | GCC_WARN_UNDECLARED_SELECTOR = YES; 370 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 371 | GCC_WARN_UNUSED_FUNCTION = YES; 372 | GCC_WARN_UNUSED_VARIABLE = YES; 373 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 374 | MTL_ENABLE_DEBUG_INFO = NO; 375 | SDKROOT = iphoneos; 376 | VALIDATE_PRODUCT = YES; 377 | }; 378 | name = Release; 379 | }; 380 | BF7E5F3A1A4A15DB00106AAB /* Debug */ = { 381 | isa = XCBuildConfiguration; 382 | buildSettings = { 383 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 384 | INFOPLIST_FILE = Demo/Info.plist; 385 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 386 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 387 | PRODUCT_NAME = "$(TARGET_NAME)"; 388 | TARGETED_DEVICE_FAMILY = "1,2"; 389 | }; 390 | name = Debug; 391 | }; 392 | BF7E5F3B1A4A15DB00106AAB /* Release */ = { 393 | isa = XCBuildConfiguration; 394 | buildSettings = { 395 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 396 | INFOPLIST_FILE = Demo/Info.plist; 397 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 398 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 399 | PRODUCT_NAME = "$(TARGET_NAME)"; 400 | TARGETED_DEVICE_FAMILY = "1,2"; 401 | }; 402 | name = Release; 403 | }; 404 | BF7E5F3D1A4A15DB00106AAB /* Debug */ = { 405 | isa = XCBuildConfiguration; 406 | buildSettings = { 407 | BUNDLE_LOADER = "$(TEST_HOST)"; 408 | FRAMEWORK_SEARCH_PATHS = ( 409 | "$(SDKROOT)/Developer/Library/Frameworks", 410 | "$(inherited)", 411 | ); 412 | GCC_PREPROCESSOR_DEFINITIONS = ( 413 | "DEBUG=1", 414 | "$(inherited)", 415 | ); 416 | INFOPLIST_FILE = DemoTests/Info.plist; 417 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 418 | PRODUCT_NAME = "$(TARGET_NAME)"; 419 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Demo.app/Demo"; 420 | }; 421 | name = Debug; 422 | }; 423 | BF7E5F3E1A4A15DB00106AAB /* Release */ = { 424 | isa = XCBuildConfiguration; 425 | buildSettings = { 426 | BUNDLE_LOADER = "$(TEST_HOST)"; 427 | FRAMEWORK_SEARCH_PATHS = ( 428 | "$(SDKROOT)/Developer/Library/Frameworks", 429 | "$(inherited)", 430 | ); 431 | INFOPLIST_FILE = DemoTests/Info.plist; 432 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 433 | PRODUCT_NAME = "$(TARGET_NAME)"; 434 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Demo.app/Demo"; 435 | }; 436 | name = Release; 437 | }; 438 | /* End XCBuildConfiguration section */ 439 | 440 | /* Begin XCConfigurationList section */ 441 | BF7E5F111A4A15DB00106AAB /* Build configuration list for PBXProject "Demo" */ = { 442 | isa = XCConfigurationList; 443 | buildConfigurations = ( 444 | BF7E5F371A4A15DB00106AAB /* Debug */, 445 | BF7E5F381A4A15DB00106AAB /* Release */, 446 | ); 447 | defaultConfigurationIsVisible = 0; 448 | defaultConfigurationName = Release; 449 | }; 450 | BF7E5F391A4A15DB00106AAB /* Build configuration list for PBXNativeTarget "Demo" */ = { 451 | isa = XCConfigurationList; 452 | buildConfigurations = ( 453 | BF7E5F3A1A4A15DB00106AAB /* Debug */, 454 | BF7E5F3B1A4A15DB00106AAB /* Release */, 455 | ); 456 | defaultConfigurationIsVisible = 0; 457 | defaultConfigurationName = Release; 458 | }; 459 | BF7E5F3C1A4A15DB00106AAB /* Build configuration list for PBXNativeTarget "DemoTests" */ = { 460 | isa = XCConfigurationList; 461 | buildConfigurations = ( 462 | BF7E5F3D1A4A15DB00106AAB /* Debug */, 463 | BF7E5F3E1A4A15DB00106AAB /* Release */, 464 | ); 465 | defaultConfigurationIsVisible = 0; 466 | defaultConfigurationName = Release; 467 | }; 468 | /* End XCConfigurationList section */ 469 | }; 470 | rootObject = BF7E5F0E1A4A15DB00106AAB /* Project object */; 471 | } 472 | -------------------------------------------------------------------------------- /RSTWebViewController/RSTWebViewController.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | BF3403991A4D569300FCABE3 /* RSTURLActivityItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF3403981A4D569300FCABE3 /* RSTURLActivityItem.swift */; }; 11 | BF7E5EF51A4A13E800106AAB /* RSTWebViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = BF7E5EF41A4A13E800106AAB /* RSTWebViewController.h */; settings = {ATTRIBUTES = (Public, ); }; }; 12 | BF7E5EFB1A4A13E800106AAB /* RSTWebViewController.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF7E5EEF1A4A13E800106AAB /* RSTWebViewController.framework */; }; 13 | BF7E5F021A4A13E800106AAB /* RSTWebViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF7E5F011A4A13E800106AAB /* RSTWebViewControllerTests.swift */; }; 14 | BF8D33C31A4B50E2004CE026 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = BF8D33BA1A4B4E14004CE026 /* Images.xcassets */; }; 15 | BF8D33C41A4B50F9004CE026 /* RSTWebViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF7E5F0B1A4A148E00106AAB /* RSTWebViewController.swift */; }; 16 | BFC3AA371A4F81730040AE42 /* RSTActivities.h in Headers */ = {isa = PBXBuildFile; fileRef = BFC3AA361A4F81730040AE42 /* RSTActivities.h */; settings = {ATTRIBUTES = (Public, ); }; }; 17 | BFC3AA381A4F81EF0040AE42 /* RSTActivities.m in Sources */ = {isa = PBXBuildFile; fileRef = BFC3AA321A4F81310040AE42 /* RSTActivities.m */; }; 18 | BFCC03C91A4F98B400DE7D64 /* RSTOnePasswordExtension.h in Headers */ = {isa = PBXBuildFile; fileRef = BFCC03C61A4F98B400DE7D64 /* RSTOnePasswordExtension.h */; settings = {ATTRIBUTES = (Public, ); }; }; 19 | BFCC03CA1A4F98B400DE7D64 /* RSTOnePasswordExtension.m in Sources */ = {isa = PBXBuildFile; fileRef = BFCC03C71A4F98B400DE7D64 /* RSTOnePasswordExtension.m */; }; 20 | BFD57BF11A4DF29600EB3F32 /* RSTChromeActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFD57BF01A4DF29600EB3F32 /* RSTChromeActivity.swift */; }; 21 | BFD57BF41A4E10A900EB3F32 /* UIApplication+ExtensionSafe.h in Headers */ = {isa = PBXBuildFile; fileRef = BFD57BF21A4E10A900EB3F32 /* UIApplication+ExtensionSafe.h */; settings = {ATTRIBUTES = (Public, ); }; }; 22 | BFD57BF51A4E10A900EB3F32 /* UIApplication+ExtensionSafe.m in Sources */ = {isa = PBXBuildFile; fileRef = BFD57BF31A4E10A900EB3F32 /* UIApplication+ExtensionSafe.m */; }; 23 | BFD57BF71A4F369900EB3F32 /* RSTSafariActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFD57BF61A4F369900EB3F32 /* RSTSafariActivity.swift */; }; 24 | /* End PBXBuildFile section */ 25 | 26 | /* Begin PBXContainerItemProxy section */ 27 | BF7E5EFC1A4A13E800106AAB /* PBXContainerItemProxy */ = { 28 | isa = PBXContainerItemProxy; 29 | containerPortal = BF7E5EE61A4A13E700106AAB /* Project object */; 30 | proxyType = 1; 31 | remoteGlobalIDString = BF7E5EEE1A4A13E800106AAB; 32 | remoteInfo = RSTWebViewController; 33 | }; 34 | /* End PBXContainerItemProxy section */ 35 | 36 | /* Begin PBXFileReference section */ 37 | BF3403981A4D569300FCABE3 /* RSTURLActivityItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RSTURLActivityItem.swift; sourceTree = ""; }; 38 | BF7E5EEF1A4A13E800106AAB /* RSTWebViewController.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = RSTWebViewController.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 39 | BF7E5EF31A4A13E800106AAB /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 40 | BF7E5EF41A4A13E800106AAB /* RSTWebViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RSTWebViewController.h; sourceTree = ""; }; 41 | BF7E5EFA1A4A13E800106AAB /* RSTWebViewControllerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RSTWebViewControllerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 42 | BF7E5F001A4A13E800106AAB /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 43 | BF7E5F011A4A13E800106AAB /* RSTWebViewControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RSTWebViewControllerTests.swift; sourceTree = ""; }; 44 | BF7E5F0B1A4A148E00106AAB /* RSTWebViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RSTWebViewController.swift; sourceTree = ""; }; 45 | BF8D33BA1A4B4E14004CE026 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 46 | BFC3AA321A4F81310040AE42 /* RSTActivities.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RSTActivities.m; sourceTree = ""; }; 47 | BFC3AA361A4F81730040AE42 /* RSTActivities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RSTActivities.h; sourceTree = ""; }; 48 | BFCC03C61A4F98B400DE7D64 /* RSTOnePasswordExtension.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RSTOnePasswordExtension.h; sourceTree = ""; }; 49 | BFCC03C71A4F98B400DE7D64 /* RSTOnePasswordExtension.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RSTOnePasswordExtension.m; sourceTree = ""; }; 50 | BFD57BF01A4DF29600EB3F32 /* RSTChromeActivity.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RSTChromeActivity.swift; sourceTree = ""; }; 51 | BFD57BF21A4E10A900EB3F32 /* UIApplication+ExtensionSafe.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIApplication+ExtensionSafe.h"; sourceTree = ""; }; 52 | BFD57BF31A4E10A900EB3F32 /* UIApplication+ExtensionSafe.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIApplication+ExtensionSafe.m"; sourceTree = ""; }; 53 | BFD57BF61A4F369900EB3F32 /* RSTSafariActivity.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RSTSafariActivity.swift; sourceTree = ""; }; 54 | /* End PBXFileReference section */ 55 | 56 | /* Begin PBXFrameworksBuildPhase section */ 57 | BF7E5EEB1A4A13E800106AAB /* Frameworks */ = { 58 | isa = PBXFrameworksBuildPhase; 59 | buildActionMask = 2147483647; 60 | files = ( 61 | ); 62 | runOnlyForDeploymentPostprocessing = 0; 63 | }; 64 | BF7E5EF71A4A13E800106AAB /* Frameworks */ = { 65 | isa = PBXFrameworksBuildPhase; 66 | buildActionMask = 2147483647; 67 | files = ( 68 | BF7E5EFB1A4A13E800106AAB /* RSTWebViewController.framework in Frameworks */, 69 | ); 70 | runOnlyForDeploymentPostprocessing = 0; 71 | }; 72 | /* End PBXFrameworksBuildPhase section */ 73 | 74 | /* Begin PBXGroup section */ 75 | BF34039A1A4D5C5D00FCABE3 /* Sharing */ = { 76 | isa = PBXGroup; 77 | children = ( 78 | BF3403981A4D569300FCABE3 /* RSTURLActivityItem.swift */, 79 | BFD57BF61A4F369900EB3F32 /* RSTSafariActivity.swift */, 80 | BFD57BF01A4DF29600EB3F32 /* RSTChromeActivity.swift */, 81 | BFC3AA361A4F81730040AE42 /* RSTActivities.h */, 82 | BFC3AA321A4F81310040AE42 /* RSTActivities.m */, 83 | ); 84 | name = Sharing; 85 | sourceTree = ""; 86 | }; 87 | BF7E5EE51A4A13E700106AAB = { 88 | isa = PBXGroup; 89 | children = ( 90 | BF7E5EF11A4A13E800106AAB /* RSTWebViewController */, 91 | BF7E5EFE1A4A13E800106AAB /* RSTWebViewControllerTests */, 92 | BF7E5EF01A4A13E800106AAB /* Products */, 93 | ); 94 | sourceTree = ""; 95 | }; 96 | BF7E5EF01A4A13E800106AAB /* Products */ = { 97 | isa = PBXGroup; 98 | children = ( 99 | BF7E5EEF1A4A13E800106AAB /* RSTWebViewController.framework */, 100 | BF7E5EFA1A4A13E800106AAB /* RSTWebViewControllerTests.xctest */, 101 | ); 102 | name = Products; 103 | sourceTree = ""; 104 | }; 105 | BF7E5EF11A4A13E800106AAB /* RSTWebViewController */ = { 106 | isa = PBXGroup; 107 | children = ( 108 | BF7E5EF41A4A13E800106AAB /* RSTWebViewController.h */, 109 | BF7E5F0B1A4A148E00106AAB /* RSTWebViewController.swift */, 110 | BF34039A1A4D5C5D00FCABE3 /* Sharing */, 111 | BFCC03BA1A4F979C00DE7D64 /* 1Password */, 112 | BF92C4371A4A887E00C50472 /* Categories */, 113 | BF7E5EF21A4A13E800106AAB /* Supporting Files */, 114 | ); 115 | path = RSTWebViewController; 116 | sourceTree = ""; 117 | }; 118 | BF7E5EF21A4A13E800106AAB /* Supporting Files */ = { 119 | isa = PBXGroup; 120 | children = ( 121 | BF8D33BA1A4B4E14004CE026 /* Images.xcassets */, 122 | BF7E5EF31A4A13E800106AAB /* Info.plist */, 123 | ); 124 | name = "Supporting Files"; 125 | sourceTree = ""; 126 | }; 127 | BF7E5EFE1A4A13E800106AAB /* RSTWebViewControllerTests */ = { 128 | isa = PBXGroup; 129 | children = ( 130 | BF7E5F011A4A13E800106AAB /* RSTWebViewControllerTests.swift */, 131 | BF7E5EFF1A4A13E800106AAB /* Supporting Files */, 132 | ); 133 | path = RSTWebViewControllerTests; 134 | sourceTree = ""; 135 | }; 136 | BF7E5EFF1A4A13E800106AAB /* Supporting Files */ = { 137 | isa = PBXGroup; 138 | children = ( 139 | BF7E5F001A4A13E800106AAB /* Info.plist */, 140 | ); 141 | name = "Supporting Files"; 142 | sourceTree = ""; 143 | }; 144 | BF92C4371A4A887E00C50472 /* Categories */ = { 145 | isa = PBXGroup; 146 | children = ( 147 | BFD57BF21A4E10A900EB3F32 /* UIApplication+ExtensionSafe.h */, 148 | BFD57BF31A4E10A900EB3F32 /* UIApplication+ExtensionSafe.m */, 149 | ); 150 | name = Categories; 151 | sourceTree = ""; 152 | }; 153 | BFCC03BA1A4F979C00DE7D64 /* 1Password */ = { 154 | isa = PBXGroup; 155 | children = ( 156 | BFCC03C61A4F98B400DE7D64 /* RSTOnePasswordExtension.h */, 157 | BFCC03C71A4F98B400DE7D64 /* RSTOnePasswordExtension.m */, 158 | ); 159 | name = 1Password; 160 | sourceTree = ""; 161 | }; 162 | /* End PBXGroup section */ 163 | 164 | /* Begin PBXHeadersBuildPhase section */ 165 | BF7E5EEC1A4A13E800106AAB /* Headers */ = { 166 | isa = PBXHeadersBuildPhase; 167 | buildActionMask = 2147483647; 168 | files = ( 169 | BF7E5EF51A4A13E800106AAB /* RSTWebViewController.h in Headers */, 170 | BFD57BF41A4E10A900EB3F32 /* UIApplication+ExtensionSafe.h in Headers */, 171 | BFCC03C91A4F98B400DE7D64 /* RSTOnePasswordExtension.h in Headers */, 172 | BFC3AA371A4F81730040AE42 /* RSTActivities.h in Headers */, 173 | ); 174 | runOnlyForDeploymentPostprocessing = 0; 175 | }; 176 | /* End PBXHeadersBuildPhase section */ 177 | 178 | /* Begin PBXNativeTarget section */ 179 | BF7E5EEE1A4A13E800106AAB /* RSTWebViewController */ = { 180 | isa = PBXNativeTarget; 181 | buildConfigurationList = BF7E5F051A4A13E800106AAB /* Build configuration list for PBXNativeTarget "RSTWebViewController" */; 182 | buildPhases = ( 183 | BF7E5EEA1A4A13E800106AAB /* Sources */, 184 | BF7E5EEB1A4A13E800106AAB /* Frameworks */, 185 | BF7E5EEC1A4A13E800106AAB /* Headers */, 186 | BF7E5EED1A4A13E800106AAB /* Resources */, 187 | ); 188 | buildRules = ( 189 | ); 190 | dependencies = ( 191 | ); 192 | name = RSTWebViewController; 193 | productName = RSTWebViewController; 194 | productReference = BF7E5EEF1A4A13E800106AAB /* RSTWebViewController.framework */; 195 | productType = "com.apple.product-type.framework"; 196 | }; 197 | BF7E5EF91A4A13E800106AAB /* RSTWebViewControllerTests */ = { 198 | isa = PBXNativeTarget; 199 | buildConfigurationList = BF7E5F081A4A13E800106AAB /* Build configuration list for PBXNativeTarget "RSTWebViewControllerTests" */; 200 | buildPhases = ( 201 | BF7E5EF61A4A13E800106AAB /* Sources */, 202 | BF7E5EF71A4A13E800106AAB /* Frameworks */, 203 | BF7E5EF81A4A13E800106AAB /* Resources */, 204 | ); 205 | buildRules = ( 206 | ); 207 | dependencies = ( 208 | BF7E5EFD1A4A13E800106AAB /* PBXTargetDependency */, 209 | ); 210 | name = RSTWebViewControllerTests; 211 | productName = RSTWebViewControllerTests; 212 | productReference = BF7E5EFA1A4A13E800106AAB /* RSTWebViewControllerTests.xctest */; 213 | productType = "com.apple.product-type.bundle.unit-test"; 214 | }; 215 | /* End PBXNativeTarget section */ 216 | 217 | /* Begin PBXProject section */ 218 | BF7E5EE61A4A13E700106AAB /* Project object */ = { 219 | isa = PBXProject; 220 | attributes = { 221 | LastUpgradeCheck = 0620; 222 | ORGANIZATIONNAME = "Riley Testut"; 223 | TargetAttributes = { 224 | BF7E5EEE1A4A13E800106AAB = { 225 | CreatedOnToolsVersion = 6.2; 226 | }; 227 | BF7E5EF91A4A13E800106AAB = { 228 | CreatedOnToolsVersion = 6.2; 229 | }; 230 | }; 231 | }; 232 | buildConfigurationList = BF7E5EE91A4A13E700106AAB /* Build configuration list for PBXProject "RSTWebViewController" */; 233 | compatibilityVersion = "Xcode 3.2"; 234 | developmentRegion = English; 235 | hasScannedForEncodings = 0; 236 | knownRegions = ( 237 | en, 238 | ); 239 | mainGroup = BF7E5EE51A4A13E700106AAB; 240 | productRefGroup = BF7E5EF01A4A13E800106AAB /* Products */; 241 | projectDirPath = ""; 242 | projectRoot = ""; 243 | targets = ( 244 | BF7E5EEE1A4A13E800106AAB /* RSTWebViewController */, 245 | BF7E5EF91A4A13E800106AAB /* RSTWebViewControllerTests */, 246 | ); 247 | }; 248 | /* End PBXProject section */ 249 | 250 | /* Begin PBXResourcesBuildPhase section */ 251 | BF7E5EED1A4A13E800106AAB /* Resources */ = { 252 | isa = PBXResourcesBuildPhase; 253 | buildActionMask = 2147483647; 254 | files = ( 255 | BF8D33C31A4B50E2004CE026 /* Images.xcassets in Resources */, 256 | ); 257 | runOnlyForDeploymentPostprocessing = 0; 258 | }; 259 | BF7E5EF81A4A13E800106AAB /* Resources */ = { 260 | isa = PBXResourcesBuildPhase; 261 | buildActionMask = 2147483647; 262 | files = ( 263 | ); 264 | runOnlyForDeploymentPostprocessing = 0; 265 | }; 266 | /* End PBXResourcesBuildPhase section */ 267 | 268 | /* Begin PBXSourcesBuildPhase section */ 269 | BF7E5EEA1A4A13E800106AAB /* Sources */ = { 270 | isa = PBXSourcesBuildPhase; 271 | buildActionMask = 2147483647; 272 | files = ( 273 | BF8D33C41A4B50F9004CE026 /* RSTWebViewController.swift in Sources */, 274 | BFCC03CA1A4F98B400DE7D64 /* RSTOnePasswordExtension.m in Sources */, 275 | BFD57BF11A4DF29600EB3F32 /* RSTChromeActivity.swift in Sources */, 276 | BFC3AA381A4F81EF0040AE42 /* RSTActivities.m in Sources */, 277 | BF3403991A4D569300FCABE3 /* RSTURLActivityItem.swift in Sources */, 278 | BFD57BF71A4F369900EB3F32 /* RSTSafariActivity.swift in Sources */, 279 | BFD57BF51A4E10A900EB3F32 /* UIApplication+ExtensionSafe.m in Sources */, 280 | ); 281 | runOnlyForDeploymentPostprocessing = 0; 282 | }; 283 | BF7E5EF61A4A13E800106AAB /* Sources */ = { 284 | isa = PBXSourcesBuildPhase; 285 | buildActionMask = 2147483647; 286 | files = ( 287 | BF7E5F021A4A13E800106AAB /* RSTWebViewControllerTests.swift in Sources */, 288 | ); 289 | runOnlyForDeploymentPostprocessing = 0; 290 | }; 291 | /* End PBXSourcesBuildPhase section */ 292 | 293 | /* Begin PBXTargetDependency section */ 294 | BF7E5EFD1A4A13E800106AAB /* PBXTargetDependency */ = { 295 | isa = PBXTargetDependency; 296 | target = BF7E5EEE1A4A13E800106AAB /* RSTWebViewController */; 297 | targetProxy = BF7E5EFC1A4A13E800106AAB /* PBXContainerItemProxy */; 298 | }; 299 | /* End PBXTargetDependency section */ 300 | 301 | /* Begin XCBuildConfiguration section */ 302 | BF7E5F031A4A13E800106AAB /* Debug */ = { 303 | isa = XCBuildConfiguration; 304 | buildSettings = { 305 | ALWAYS_SEARCH_USER_PATHS = NO; 306 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 307 | CLANG_CXX_LIBRARY = "libc++"; 308 | CLANG_ENABLE_MODULES = YES; 309 | CLANG_ENABLE_OBJC_ARC = YES; 310 | CLANG_WARN_BOOL_CONVERSION = YES; 311 | CLANG_WARN_CONSTANT_CONVERSION = YES; 312 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 313 | CLANG_WARN_EMPTY_BODY = YES; 314 | CLANG_WARN_ENUM_CONVERSION = YES; 315 | CLANG_WARN_INT_CONVERSION = YES; 316 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 317 | CLANG_WARN_UNREACHABLE_CODE = YES; 318 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 319 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 320 | COPY_PHASE_STRIP = NO; 321 | CURRENT_PROJECT_VERSION = 1; 322 | ENABLE_STRICT_OBJC_MSGSEND = YES; 323 | GCC_C_LANGUAGE_STANDARD = gnu99; 324 | GCC_DYNAMIC_NO_PIC = NO; 325 | GCC_OPTIMIZATION_LEVEL = 0; 326 | GCC_PREPROCESSOR_DEFINITIONS = ( 327 | "DEBUG=1", 328 | "$(inherited)", 329 | ); 330 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 331 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 332 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 333 | GCC_WARN_UNDECLARED_SELECTOR = YES; 334 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 335 | GCC_WARN_UNUSED_FUNCTION = YES; 336 | GCC_WARN_UNUSED_VARIABLE = YES; 337 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 338 | MTL_ENABLE_DEBUG_INFO = YES; 339 | ONLY_ACTIVE_ARCH = YES; 340 | OTHER_SWIFT_FLAGS = "-D DEBUG"; 341 | SDKROOT = iphoneos; 342 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 343 | TARGETED_DEVICE_FAMILY = "1,2"; 344 | VERSIONING_SYSTEM = "apple-generic"; 345 | VERSION_INFO_PREFIX = ""; 346 | }; 347 | name = Debug; 348 | }; 349 | BF7E5F041A4A13E800106AAB /* Release */ = { 350 | isa = XCBuildConfiguration; 351 | buildSettings = { 352 | ALWAYS_SEARCH_USER_PATHS = NO; 353 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 354 | CLANG_CXX_LIBRARY = "libc++"; 355 | CLANG_ENABLE_MODULES = YES; 356 | CLANG_ENABLE_OBJC_ARC = YES; 357 | CLANG_WARN_BOOL_CONVERSION = YES; 358 | CLANG_WARN_CONSTANT_CONVERSION = YES; 359 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 360 | CLANG_WARN_EMPTY_BODY = YES; 361 | CLANG_WARN_ENUM_CONVERSION = YES; 362 | CLANG_WARN_INT_CONVERSION = YES; 363 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 364 | CLANG_WARN_UNREACHABLE_CODE = YES; 365 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 366 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 367 | COPY_PHASE_STRIP = NO; 368 | CURRENT_PROJECT_VERSION = 1; 369 | ENABLE_NS_ASSERTIONS = NO; 370 | ENABLE_STRICT_OBJC_MSGSEND = YES; 371 | GCC_C_LANGUAGE_STANDARD = gnu99; 372 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 373 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 374 | GCC_WARN_UNDECLARED_SELECTOR = YES; 375 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 376 | GCC_WARN_UNUSED_FUNCTION = YES; 377 | GCC_WARN_UNUSED_VARIABLE = YES; 378 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 379 | MTL_ENABLE_DEBUG_INFO = NO; 380 | OTHER_SWIFT_FLAGS = ""; 381 | SDKROOT = iphoneos; 382 | TARGETED_DEVICE_FAMILY = "1,2"; 383 | VALIDATE_PRODUCT = YES; 384 | VERSIONING_SYSTEM = "apple-generic"; 385 | VERSION_INFO_PREFIX = ""; 386 | }; 387 | name = Release; 388 | }; 389 | BF7E5F061A4A13E800106AAB /* Debug */ = { 390 | isa = XCBuildConfiguration; 391 | buildSettings = { 392 | APPLICATION_EXTENSION_API_ONLY = YES; 393 | CLANG_ENABLE_MODULES = YES; 394 | DEFINES_MODULE = YES; 395 | DYLIB_COMPATIBILITY_VERSION = 1; 396 | DYLIB_CURRENT_VERSION = 1; 397 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 398 | INFOPLIST_FILE = RSTWebViewController/Info.plist; 399 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 400 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 401 | PRODUCT_NAME = "$(TARGET_NAME)"; 402 | SKIP_INSTALL = YES; 403 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 404 | }; 405 | name = Debug; 406 | }; 407 | BF7E5F071A4A13E800106AAB /* Release */ = { 408 | isa = XCBuildConfiguration; 409 | buildSettings = { 410 | APPLICATION_EXTENSION_API_ONLY = YES; 411 | CLANG_ENABLE_MODULES = YES; 412 | DEFINES_MODULE = YES; 413 | DYLIB_COMPATIBILITY_VERSION = 1; 414 | DYLIB_CURRENT_VERSION = 1; 415 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 416 | INFOPLIST_FILE = RSTWebViewController/Info.plist; 417 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 418 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 419 | PRODUCT_NAME = "$(TARGET_NAME)"; 420 | SKIP_INSTALL = YES; 421 | }; 422 | name = Release; 423 | }; 424 | BF7E5F091A4A13E800106AAB /* Debug */ = { 425 | isa = XCBuildConfiguration; 426 | buildSettings = { 427 | FRAMEWORK_SEARCH_PATHS = ( 428 | "$(SDKROOT)/Developer/Library/Frameworks", 429 | "$(inherited)", 430 | ); 431 | GCC_PREPROCESSOR_DEFINITIONS = ( 432 | "DEBUG=1", 433 | "$(inherited)", 434 | ); 435 | INFOPLIST_FILE = RSTWebViewControllerTests/Info.plist; 436 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 437 | PRODUCT_NAME = "$(TARGET_NAME)"; 438 | }; 439 | name = Debug; 440 | }; 441 | BF7E5F0A1A4A13E800106AAB /* Release */ = { 442 | isa = XCBuildConfiguration; 443 | buildSettings = { 444 | FRAMEWORK_SEARCH_PATHS = ( 445 | "$(SDKROOT)/Developer/Library/Frameworks", 446 | "$(inherited)", 447 | ); 448 | INFOPLIST_FILE = RSTWebViewControllerTests/Info.plist; 449 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 450 | PRODUCT_NAME = "$(TARGET_NAME)"; 451 | }; 452 | name = Release; 453 | }; 454 | /* End XCBuildConfiguration section */ 455 | 456 | /* Begin XCConfigurationList section */ 457 | BF7E5EE91A4A13E700106AAB /* Build configuration list for PBXProject "RSTWebViewController" */ = { 458 | isa = XCConfigurationList; 459 | buildConfigurations = ( 460 | BF7E5F031A4A13E800106AAB /* Debug */, 461 | BF7E5F041A4A13E800106AAB /* Release */, 462 | ); 463 | defaultConfigurationIsVisible = 0; 464 | defaultConfigurationName = Release; 465 | }; 466 | BF7E5F051A4A13E800106AAB /* Build configuration list for PBXNativeTarget "RSTWebViewController" */ = { 467 | isa = XCConfigurationList; 468 | buildConfigurations = ( 469 | BF7E5F061A4A13E800106AAB /* Debug */, 470 | BF7E5F071A4A13E800106AAB /* Release */, 471 | ); 472 | defaultConfigurationIsVisible = 0; 473 | defaultConfigurationName = Release; 474 | }; 475 | BF7E5F081A4A13E800106AAB /* Build configuration list for PBXNativeTarget "RSTWebViewControllerTests" */ = { 476 | isa = XCConfigurationList; 477 | buildConfigurations = ( 478 | BF7E5F091A4A13E800106AAB /* Debug */, 479 | BF7E5F0A1A4A13E800106AAB /* Release */, 480 | ); 481 | defaultConfigurationIsVisible = 0; 482 | defaultConfigurationName = Release; 483 | }; 484 | /* End XCConfigurationList section */ 485 | }; 486 | rootObject = BF7E5EE61A4A13E700106AAB /* Project object */; 487 | } 488 | -------------------------------------------------------------------------------- /RSTWebViewController/RSTWebViewController/RSTWebViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RSTWebViewController.swift 3 | // RSTWebViewController 4 | // 5 | // Created by Riley Testut on 12/23/14. 6 | // Copyright (c) 2014 Riley Testut. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import WebKit 11 | 12 | public extension RSTWebViewController { 13 | 14 | //MARK: Update UI 15 | 16 | func updateToolbarItems() 17 | { 18 | if self.webView.loading 19 | { 20 | self.refreshButton = self.stopLoadingButton 21 | } 22 | else 23 | { 24 | self.refreshButton = self.reloadButton 25 | } 26 | 27 | if self.showsDoneButton && self.doneButton == nil 28 | { 29 | self.doneButton = UIBarButtonItem(barButtonSystemItem: .Done, target: self, action: "dismissWebViewController:") 30 | } 31 | else if !self.showsDoneButton && self.doneButton != nil 32 | { 33 | self.doneButton = nil 34 | } 35 | 36 | self.backButton.enabled = self.webView.canGoBack 37 | self.forwardButton.enabled = self.webView.canGoForward 38 | 39 | if self.traitCollection.horizontalSizeClass == .Regular 40 | { 41 | self.toolbarItems = nil 42 | 43 | let fixedSpaceItem = UIBarButtonItem(barButtonSystemItem: .FixedSpace, target: nil, action: nil) 44 | fixedSpaceItem.width = 20.0 45 | 46 | let reloadButtonFixedSpaceItem = UIBarButtonItem(barButtonSystemItem: .FixedSpace, target: nil, action: nil) 47 | reloadButtonFixedSpaceItem.width = fixedSpaceItem.width 48 | 49 | if self.refreshButton == self.stopLoadingButton 50 | { 51 | reloadButtonFixedSpaceItem.width = fixedSpaceItem.width + 1 52 | } 53 | 54 | var items = [self.shareButton, fixedSpaceItem, self.refreshButton, reloadButtonFixedSpaceItem, self.forwardButton, fixedSpaceItem, self.backButton, fixedSpaceItem] 55 | 56 | if self.showsDoneButton 57 | { 58 | items.insert(fixedSpaceItem, atIndex: 0) 59 | items.insert(self.doneButton!, atIndex: 0) 60 | } 61 | 62 | self.navigationItem.rightBarButtonItems = items 63 | } 64 | else 65 | { 66 | // We have to set rightBarButtonItems instead of simply rightBarButtonItem to properly clear previous buttons 67 | self.navigationItem.rightBarButtonItems = self.showsDoneButton ? [self.doneButton!] : nil 68 | 69 | let flexibleSpaceItem = UIBarButtonItem(barButtonSystemItem: .FlexibleSpace, target: nil, action: nil) 70 | self.toolbarItems = [self.backButton, flexibleSpaceItem, self.forwardButton, flexibleSpaceItem, self.refreshButton, flexibleSpaceItem, self.shareButton] 71 | } 72 | } 73 | 74 | } 75 | 76 | public class RSTWebViewController: UIViewController { 77 | 78 | //MARK: Public Properties 79 | 80 | // WKWebView used to display webpages 81 | public private(set) var webView: WKWebView 82 | 83 | // UIBarButton items. Customizable, and subclasses can override updateToolbarItems() to arrange them however they want 84 | public var backButton: UIBarButtonItem = UIBarButtonItem(image: nil, style: .Plain, target: nil, action: "goBack:") 85 | public var forwardButton: UIBarButtonItem = UIBarButtonItem(image: nil, style: .Plain, target: nil, action: "goForward:") 86 | public var shareButton: UIBarButtonItem = UIBarButtonItem(barButtonSystemItem: .Action, target: nil, action: "shareLink:") 87 | 88 | public var reloadButton: UIBarButtonItem = UIBarButtonItem(barButtonSystemItem: .Refresh, target: nil, action: "refresh:") 89 | public var stopLoadingButton: UIBarButtonItem = UIBarButtonItem(barButtonSystemItem: .Stop, target: nil, action: "refresh:") 90 | 91 | public var doneButton: UIBarButtonItem? 92 | 93 | // Set to true when presenting modally to show a Done button that'll dismiss itself. 94 | public var showsDoneButton: Bool = false { 95 | didSet { 96 | self.updateToolbarItems() 97 | } 98 | } 99 | 100 | // Array of activity types that should not be displayed in the UIActivityViewController share sheet 101 | public var excludedActivityTypes: [String]? 102 | 103 | // Array of application-specific UIActivities to handle sharing links via UIActivityViewController 104 | public var applicationActivities: [UIActivity]? 105 | 106 | 107 | //MARK: Private Properties 108 | 109 | private let initialReqest: NSURLRequest? 110 | private let progressView = UIProgressView() 111 | private var ignoreUpdateProgress: Bool = false 112 | private var refreshButton: UIBarButtonItem 113 | 114 | 115 | //MARK: Initializers 116 | 117 | public required init(request: NSURLRequest?) 118 | { 119 | self.initialReqest = request 120 | 121 | let configuration = WKWebViewConfiguration() 122 | self.webView = WKWebView(frame: CGRectZero, configuration: configuration) 123 | 124 | self.refreshButton = self.reloadButton 125 | 126 | super.init(nibName: nil, bundle: nil) 127 | 128 | self.initialize() 129 | } 130 | 131 | public convenience init (URL: NSURL?) 132 | { 133 | if let URL = URL 134 | { 135 | self.init(request: NSURLRequest(URL: URL)) 136 | } 137 | else 138 | { 139 | self.init(request: nil) 140 | } 141 | } 142 | 143 | public convenience init (address: String?) 144 | { 145 | if let address = address 146 | { 147 | self.init(URL: NSURL(string: address)) 148 | } 149 | else 150 | { 151 | self.init(URL: nil) 152 | } 153 | } 154 | 155 | public required init(coder: NSCoder) 156 | { 157 | let configuration = WKWebViewConfiguration() 158 | self.webView = WKWebView(frame: CGRectZero, configuration: configuration) 159 | 160 | self.refreshButton = self.reloadButton 161 | 162 | self.initialReqest = nil 163 | 164 | super.init(coder: coder) 165 | 166 | self.initialize() 167 | } 168 | 169 | private func initialize() 170 | { 171 | self.progressView.progressViewStyle = .Bar 172 | self.progressView.autoresizingMask = .FlexibleWidth | .FlexibleTopMargin 173 | self.progressView.progress = 0.5 174 | self.progressView.alpha = 0.0 175 | self.progressView.hidden = true 176 | 177 | self.backButton.target = self 178 | self.forwardButton.target = self 179 | self.reloadButton.target = self 180 | self.stopLoadingButton.target = self 181 | self.shareButton.target = self 182 | 183 | let bundle = NSBundle(forClass: RSTWebViewController.self) 184 | self.backButton.image = UIImage(named: "back_button", inBundle: bundle, compatibleWithTraitCollection: nil) 185 | self.forwardButton.image = UIImage(named: "forward_button", inBundle: bundle, compatibleWithTraitCollection: nil) 186 | } 187 | 188 | deinit 189 | { 190 | self.stopKeyValueObserving() 191 | } 192 | 193 | 194 | //MARK: UIViewController 195 | 196 | public override func loadView() 197 | { 198 | self.startKeyValueObserving() 199 | 200 | if let request = self.initialReqest 201 | { 202 | self.webView.loadRequest(request) 203 | } 204 | 205 | self.view = self.webView 206 | } 207 | 208 | public override func viewDidLoad() 209 | { 210 | super.viewDidLoad() 211 | 212 | self.updateToolbarItems() 213 | } 214 | 215 | public override func viewWillAppear(animated: Bool) 216 | { 217 | super.viewWillAppear(animated) 218 | 219 | if self.webView.estimatedProgress < 1.0 220 | { 221 | self.transitionCoordinator()?.animateAlongsideTransition( { (context) in 222 | 223 | self.showProgressBar(animated: true) 224 | 225 | }) { (context) in 226 | 227 | if context.isCancelled() 228 | { 229 | self.hideProgressBar(animated: false) 230 | } 231 | } 232 | } 233 | 234 | if self.traitCollection.horizontalSizeClass == .Regular 235 | { 236 | self.navigationController?.setToolbarHidden(true, animated: false) 237 | } 238 | else 239 | { 240 | self.navigationController?.setToolbarHidden(false, animated: false) 241 | } 242 | 243 | self.updateToolbarItems() 244 | } 245 | 246 | public override func viewWillDisappear(animated: Bool) 247 | { 248 | super.viewDidDisappear(animated) 249 | 250 | var shouldHideToolbarItems = true 251 | 252 | if let toolbarItems = self.navigationController?.topViewController.toolbarItems 253 | { 254 | if toolbarItems.count > 0 255 | { 256 | shouldHideToolbarItems = false 257 | } 258 | } 259 | 260 | if shouldHideToolbarItems 261 | { 262 | self.navigationController?.setToolbarHidden(true, animated: false) 263 | } 264 | 265 | self.transitionCoordinator()?.animateAlongsideTransition( { (context) in 266 | 267 | self.hideProgressBar(animated: true) 268 | 269 | }) { (context) in 270 | 271 | if context.isCancelled() && self.webView.estimatedProgress < 1.0 272 | { 273 | self.showProgressBar(animated: false) 274 | } 275 | } 276 | } 277 | 278 | public override func didMoveToParentViewController(parent: UIViewController?) 279 | { 280 | if parent == nil 281 | { 282 | self.webView.stopLoading() 283 | } 284 | } 285 | 286 | public override func didReceiveMemoryWarning() 287 | { 288 | super.didReceiveMemoryWarning() 289 | } 290 | 291 | //MARK: Layout 292 | 293 | public override func willTransitionToTraitCollection(newCollection: UITraitCollection, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) 294 | { 295 | super.willTransitionToTraitCollection(newCollection, withTransitionCoordinator: coordinator) 296 | 297 | coordinator.animateAlongsideTransition({ (context) in 298 | 299 | if self.traitCollection.horizontalSizeClass == .Regular 300 | { 301 | self.navigationController?.setToolbarHidden(true, animated: true) 302 | } 303 | else 304 | { 305 | self.navigationController?.setToolbarHidden(false, animated: true) 306 | } 307 | 308 | }, completion: nil) 309 | } 310 | 311 | public override func traitCollectionDidChange(previousTraitCollection: UITraitCollection?) 312 | { 313 | super.traitCollectionDidChange(previousTraitCollection) 314 | 315 | self.updateToolbarItems() 316 | } 317 | 318 | //MARK: KVO 319 | 320 | public override func observeValueForKeyPath(keyPath: String, ofObject object: AnyObject, change: [NSObject : AnyObject], context: UnsafeMutablePointer<()>) 321 | { 322 | if context == RSTWebViewControllerContext 323 | { 324 | let webView = (object as! WKWebView) 325 | 326 | switch keyPath 327 | { 328 | case "title": 329 | self.updateTitle(webView.title) 330 | 331 | case "estimatedProgress": 332 | self.updateProgress(Float(webView.estimatedProgress)) 333 | 334 | case "loading": 335 | self.updateLoadingStatus(status: webView.loading) 336 | 337 | case "canGoBack", "canGoForward": 338 | self.updateToolbarItems() 339 | 340 | default: 341 | println("Unknown KVO keypath") 342 | } 343 | } 344 | else 345 | { 346 | super.observeValueForKeyPath(keyPath, ofObject: object, change: change, context: context) 347 | } 348 | } 349 | 350 | } 351 | 352 | // Cannot be private or else they will crash upon being called ಠ_ಠ 353 | internal extension RSTWebViewController { 354 | 355 | //MARK: Dismissal 356 | 357 | func dismissWebViewController(sender: UIBarButtonItem) 358 | { 359 | self.parentViewController?.dismissViewControllerAnimated(true, completion: nil) 360 | } 361 | 362 | //MARK: Toolbar Items 363 | 364 | func goBack(button: UIBarButtonItem) 365 | { 366 | self.webView.goBack() 367 | } 368 | 369 | func goForward(button: UIBarButtonItem) 370 | { 371 | self.webView.goForward() 372 | } 373 | 374 | func refresh(button: UIBarButtonItem) 375 | { 376 | if self.webView.loading 377 | { 378 | self.ignoreUpdateProgress = true 379 | self.webView.stopLoading() 380 | } 381 | else 382 | { 383 | if self.webView.URL == nil && self.webView.backForwardList.backList.count == 0 && self.initialReqest != nil 384 | { 385 | self.webView.loadRequest(self.initialReqest!) 386 | } 387 | else 388 | { 389 | self.webView.reload() 390 | } 391 | } 392 | } 393 | 394 | func shareLink(button: UIBarButtonItem) 395 | { 396 | let activityItem = RSTURLActivityItem(URL: self.webView.URL ?? NSURL()) 397 | activityItem.title = self.webView.title 398 | 399 | if self.excludedActivityTypes == nil || (self.excludedActivityTypes != nil && !contains(self.excludedActivityTypes!, RSTActivityTypeOnePassword)) 400 | { 401 | 402 | #if DEBUG 403 | 404 | // If UIApplication.rst_sharedApplication() is nil, we are running in an application extension, meaning NSBundle.mainBundle() will return the extension bundle, not the container app's 405 | // Because of this, we can't check to see if the Imported UTIs have been added, but since the assert is purely for debugging, it's not that big of an issue 406 | 407 | if UIApplication.rst_sharedApplication() != nil 408 | { 409 | var importedOnePasswordUTI = false 410 | var importedURLUTI = false 411 | 412 | if let importedUTIs = NSBundle.mainBundle().objectForInfoDictionaryKey("UTImportedTypeDeclarations") as! [[String: AnyObject]]? 413 | { 414 | for importedUTI in importedUTIs 415 | { 416 | let identifier = importedUTI["UTTypeIdentifier"] as! String 417 | 418 | if identifier == "org.appextension.fill-webview-action" 419 | { 420 | importedOnePasswordUTI = true 421 | } 422 | else if identifier == "com.rileytestut.RSTWebViewController.url" 423 | { 424 | let UTIs = importedUTI["UTTypeConformsTo"] as! [String] 425 | 426 | if contains(UTIs, "org.appextension.fill-webview-action") && contains(UTIs, "public.url") 427 | { 428 | importedURLUTI = true 429 | break 430 | } 431 | } 432 | 433 | if importedOnePasswordUTI && importedURLUTI 434 | { 435 | break 436 | } 437 | } 438 | } 439 | 440 | assert(importedOnePasswordUTI && importedURLUTI, "Either the 1Password Extension UTI, the RSTWebViewController URL UTI, or both, have not been properly declared as Imported UTIs. Please see the RSTWebViewController README for details on how to add them.") 441 | } 442 | 443 | #endif 444 | 445 | 446 | let onePasswordURLScheme = NSURL(string: "org-appextension-feature-password-management://") 447 | 448 | // If we're running in an application extension, there is no way to detect if 1Password is installed. 449 | // Because of this, if UIApplication.rst_sharedApplication() == nil, we'll simply assume it is installed, since there's no harm in doing so 450 | if UIApplication.rst_sharedApplication() == nil || (onePasswordURLScheme != nil && UIApplication.rst_sharedApplication().canOpenURL(onePasswordURLScheme!)) 451 | { 452 | activityItem.typeIdentifier = "com.rileytestut.RSTWebViewController.url" 453 | 454 | RSTOnePasswordExtension.sharedExtension().createExtensionItemForWebView(self.webView, completion: { (extensionItem, error) in 455 | activityItem.setItem(extensionItem, forActivityType: "com.agilebits.onepassword-ios.extension") 456 | activityItem.setItem(extensionItem, forActivityType: "com.agilebits.beta.onepassword-ios.extension") 457 | self.presentActivityViewControllerWithItems([activityItem], fromBarButtonItem: button) 458 | }) 459 | 460 | return 461 | } 462 | } 463 | 464 | self.presentActivityViewControllerWithItems([activityItem], fromBarButtonItem: button) 465 | } 466 | 467 | func presentActivityViewControllerWithItems(activityItems: [AnyObject], fromBarButtonItem barButtonItem: UIBarButtonItem) 468 | { 469 | var applicationActivities = self.applicationActivities ?? [UIActivity]() 470 | 471 | if let excludedActivityTypes = self.excludedActivityTypes 472 | { 473 | if !contains(excludedActivityTypes, RSTActivityTypeSafari) 474 | { 475 | applicationActivities.append(RSTSafariActivity()) 476 | } 477 | 478 | if !contains(excludedActivityTypes, RSTActivityTypeChrome) 479 | { 480 | applicationActivities.append(RSTChromeActivity()) 481 | } 482 | } 483 | else 484 | { 485 | applicationActivities.append(RSTSafariActivity()) 486 | applicationActivities.append(RSTChromeActivity()) 487 | } 488 | 489 | let reloadButtonTintColor = self.reloadButton.tintColor 490 | let stopLoadingButtonTintColor = self.stopLoadingButton.tintColor 491 | 492 | let activityViewController = UIActivityViewController(activityItems: activityItems, applicationActivities: applicationActivities) 493 | activityViewController.excludedActivityTypes = self.excludedActivityTypes 494 | 495 | activityViewController.modalPresentationStyle = .Popover 496 | activityViewController.popoverPresentationController?.barButtonItem = barButtonItem 497 | 498 | activityViewController.completionWithItemsHandler = { activityType, success, items, error in 499 | 500 | if RSTOnePasswordExtension.sharedExtension().isOnePasswordExtensionActivityType(activityType) 501 | { 502 | RSTOnePasswordExtension.sharedExtension().fillReturnedItems(items, intoWebView: self.webView, completion: nil) 503 | } 504 | 505 | // Because tint colors aren't properly updated when views aren't in a view hierarchy, we manually fix any erroneous tint colors 506 | self.progressView.tintColorDidChange() 507 | 508 | let systemTintColor = UIColor(red: 0, green: 0.478431, blue: 1, alpha: 1) 509 | 510 | // If previous tint color is nil, we need to temporarily set the tint color to something else or it won't visually update the tint color 511 | if reloadButtonTintColor == nil 512 | { 513 | self.reloadButton.tintColor = systemTintColor 514 | } 515 | 516 | if stopLoadingButtonTintColor == nil 517 | { 518 | self.stopLoadingButton.tintColor = systemTintColor 519 | } 520 | 521 | self.reloadButton.tintColor = reloadButtonTintColor 522 | self.stopLoadingButton.tintColor = stopLoadingButtonTintColor 523 | 524 | } 525 | 526 | self.presentViewController(activityViewController, animated: true, completion: nil) 527 | } 528 | } 529 | 530 | private let RSTWebViewControllerContext = UnsafeMutablePointer<()>() 531 | 532 | private extension RSTWebViewController { 533 | 534 | //MARK: KVO 535 | 536 | func startKeyValueObserving() 537 | { 538 | self.webView.addObserver(self, forKeyPath: "title", options:nil, context: RSTWebViewControllerContext) 539 | self.webView.addObserver(self, forKeyPath: "estimatedProgress", options: nil, context: RSTWebViewControllerContext) 540 | self.webView.addObserver(self, forKeyPath: "loading", options: nil, context: RSTWebViewControllerContext) 541 | self.webView.addObserver(self, forKeyPath: "canGoBack", options: nil, context: RSTWebViewControllerContext) 542 | self.webView.addObserver(self, forKeyPath: "canGoForward", options: nil, context: RSTWebViewControllerContext) 543 | } 544 | 545 | func stopKeyValueObserving() 546 | { 547 | self.webView.removeObserver(self, forKeyPath: "title", context: RSTWebViewControllerContext) 548 | self.webView.removeObserver(self, forKeyPath: "estimatedProgress", context: RSTWebViewControllerContext) 549 | self.webView.removeObserver(self, forKeyPath: "loading", context: RSTWebViewControllerContext) 550 | self.webView.removeObserver(self, forKeyPath: "canGoBack", context: RSTWebViewControllerContext) 551 | self.webView.removeObserver(self, forKeyPath: "canGoForward", context: RSTWebViewControllerContext) 552 | } 553 | 554 | //MARK: Update UI 555 | 556 | func updateTitle(title: String?) 557 | { 558 | self.title = title 559 | } 560 | 561 | func updateLoadingStatus(status loading: Bool) 562 | { 563 | self.updateToolbarItems() 564 | 565 | if let application = UIApplication.rst_sharedApplication() 566 | { 567 | if loading 568 | { 569 | application.networkActivityIndicatorVisible = true 570 | } 571 | else 572 | { 573 | application.networkActivityIndicatorVisible = false 574 | } 575 | } 576 | 577 | } 578 | 579 | func updateProgress(progress: Float) 580 | { 581 | if self.progressView.hidden 582 | { 583 | self.showProgressBar(animated: true) 584 | } 585 | 586 | if self.ignoreUpdateProgress 587 | { 588 | self.ignoreUpdateProgress = false 589 | self.hideProgressBar(animated: true) 590 | } 591 | else if progress < self.progressView.progress 592 | { 593 | // If progress is less than self.progressView.progress, another webpage began to load before the first one completed 594 | // In this case, we set the progress back to 0.0, and then wait until the next updateProgress, because it results in a much better animation 595 | 596 | self.progressView.setProgress(0.0, animated: false) 597 | } 598 | else 599 | { 600 | UIView.animateWithDuration(0.4, animations: { 601 | 602 | self.progressView.setProgress(progress, animated: true) 603 | 604 | }, completion: { (finished) in 605 | 606 | if progress == 1.0 607 | { 608 | // This delay serves two purposes. One, it keeps the progress bar on screen just a bit longer so it doesn't appear to disappear too quickly. 609 | // Two, it allows us to prevent the progress bar from disappearing if the user actually started loading another webpage before the current one finished loading. 610 | 611 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64((0.2 * Float(NSEC_PER_SEC)))), dispatch_get_main_queue(), { 612 | 613 | if self.webView.estimatedProgress == 1.0 614 | { 615 | self.hideProgressBar(animated: true) 616 | } 617 | 618 | }) 619 | } 620 | 621 | }); 622 | } 623 | } 624 | 625 | func showProgressBar(#animated: Bool) 626 | { 627 | let navigationBarBounds = self.navigationController?.navigationBar.bounds ?? CGRectZero 628 | self.progressView.frame = CGRect(x: 0, y: navigationBarBounds.height - self.progressView.bounds.height, width: navigationBarBounds.width, height: self.progressView.bounds.height) 629 | 630 | self.navigationController?.navigationBar.addSubview(self.progressView) 631 | 632 | self.progressView.setProgress(Float(self.webView.estimatedProgress), animated: false) 633 | self.progressView.hidden = false 634 | 635 | if animated 636 | { 637 | UIView.animateWithDuration(0.4) { 638 | self.progressView.alpha = 1.0 639 | } 640 | } 641 | else 642 | { 643 | self.progressView.alpha = 1.0 644 | } 645 | } 646 | 647 | func hideProgressBar(#animated: Bool) 648 | { 649 | if animated 650 | { 651 | UIView.animateWithDuration(0.4, animations: { 652 | self.progressView.alpha = 0.0 653 | }, completion: { (finished) in 654 | 655 | self.progressView.setProgress(0.0, animated: false) 656 | self.progressView.hidden = true 657 | self.progressView.removeFromSuperview() 658 | }) 659 | } 660 | else 661 | { 662 | self.progressView.alpha = 0.0 663 | 664 | // Completion 665 | self.progressView.setProgress(0.0, animated: false) 666 | self.progressView.hidden = true 667 | self.progressView.removeFromSuperview() 668 | } 669 | } 670 | 671 | } -------------------------------------------------------------------------------- /RSTWebViewController/RSTWebViewController/RSTOnePasswordExtension.m: -------------------------------------------------------------------------------- 1 | // 2 | // 1Password Extension 3 | // 4 | // Lovingly handcrafted by Dave Teare, Michael Fey, Rad Azzouz, and Roustem Karimov. 5 | // Copyright (c) 2014 AgileBits. All rights reserved. 6 | // 7 | 8 | #import "RSTOnePasswordExtension.h" 9 | 10 | // Version 11 | #define VERSION_NUMBER @(110) 12 | static NSString *const AppExtensionVersionNumberKey = @"version_number"; 13 | 14 | // Available App Extension Actions 15 | static NSString *const kUTTypeAppExtensionFindLoginAction = @"org.appextension.find-login-action"; 16 | static NSString *const kUTTypeAppExtensionSaveLoginAction = @"org.appextension.save-login-action"; 17 | static NSString *const kUTTypeAppExtensionChangePasswordAction = @"org.appextension.change-password-action"; 18 | static NSString *const kUTTypeAppExtensionFillWebViewAction = @"org.appextension.fill-webview-action"; 19 | 20 | // WebView Dictionary keys 21 | static NSString *const AppExtensionWebViewPageFillScript = @"fillScript"; 22 | static NSString *const AppExtensionWebViewPageDetails = @"pageDetails"; 23 | 24 | @implementation RSTOnePasswordExtension 25 | 26 | #pragma mark - Public Methods 27 | 28 | + (RSTOnePasswordExtension *)sharedExtension { 29 | static dispatch_once_t onceToken; 30 | static RSTOnePasswordExtension *__sharedExtension; 31 | 32 | dispatch_once(&onceToken, ^{ 33 | __sharedExtension = [RSTOnePasswordExtension new]; 34 | }); 35 | 36 | return __sharedExtension; 37 | } 38 | 39 | - (BOOL)isSystemAppExtensionAPIAvailable { 40 | #ifdef __IPHONE_8_0 41 | return NSClassFromString(@"NSExtensionItem") != nil; 42 | #else 43 | return NO; 44 | #endif 45 | } 46 | 47 | - (BOOL)isAppExtensionAvailable { 48 | if ([self isSystemAppExtensionAPIAvailable]) { 49 | return [[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:@"org-appextension-feature-password-management://"]]; 50 | } 51 | 52 | return NO; 53 | } 54 | 55 | - (void)findLoginForURLString:(NSString *)URLString forViewController:(UIViewController *)viewController sender:(id)sender completion:(void (^)(NSDictionary *loginDictionary, NSError *error))completion { 56 | NSAssert(URLString != nil, @"URLString must not be nil"); 57 | NSAssert(viewController != nil, @"viewController must not be nil"); 58 | 59 | if (![self isSystemAppExtensionAPIAvailable]) { 60 | NSLog(@"Failed to findLoginForURLString, system API is not available"); 61 | if (completion) { 62 | completion(nil, [RSTOnePasswordExtension systemAppExtensionAPINotAvailableError]); 63 | } 64 | 65 | return; 66 | } 67 | 68 | #ifdef __IPHONE_8_0 69 | __weak __typeof__ (self) weakSelf = self; 70 | 71 | NSExtensionItem *extensionItem = [self createExtensionItemToFindLoginForURLString:URLString]; 72 | UIActivityViewController *activityViewController = [self activityViewControllerForExtensionItem:extensionItem viewController:viewController sender:sender]; 73 | 74 | activityViewController.completionWithItemsHandler = ^(NSString *activityType, BOOL completed, NSArray *returnedItems, NSError *activityError) 75 | { 76 | if (returnedItems.count == 0) { 77 | NSError *error = nil; 78 | if (activityError) { 79 | NSLog(@"Failed to findLoginForURLString: %@", activityError); 80 | error = [RSTOnePasswordExtension failedToContactExtensionErrorWithActivityError:activityError]; 81 | } 82 | else { 83 | error = [RSTOnePasswordExtension extensionCancelledByUserError]; 84 | } 85 | 86 | if (completion) { 87 | completion(nil, error); 88 | } 89 | 90 | return; 91 | } 92 | 93 | __strong __typeof__(self) strongSelf = weakSelf; 94 | [strongSelf processReturnedItems:returnedItems completion:completion]; 95 | }; 96 | 97 | [viewController presentViewController:activityViewController animated:YES completion:nil]; 98 | #endif 99 | } 100 | 101 | - (void)storeLoginForURLString:(NSString *)URLString loginDetails:(NSDictionary *)loginDetailsDict passwordGenerationOptions:(NSDictionary *)passwordGenerationOptions forViewController:(UIViewController *)viewController sender:(id)sender completion:(void (^)(NSDictionary *, NSError *))completion { 102 | NSAssert(URLString != nil, @"URLString must not be nil"); 103 | NSAssert(loginDetailsDict != nil, @"loginDetailsDict must not be nil"); 104 | NSAssert(viewController != nil, @"viewController must not be nil"); 105 | 106 | if (![self isSystemAppExtensionAPIAvailable]) { 107 | NSLog(@"Failed to storeLoginForURLString, system API is not available"); 108 | if (completion) { 109 | completion(nil, [RSTOnePasswordExtension systemAppExtensionAPINotAvailableError]); 110 | } 111 | 112 | return; 113 | } 114 | 115 | 116 | #ifdef __IPHONE_8_0 117 | NSExtensionItem *extensionItem = [self createExtensionItemToStoreLoginForURLString:URLString loginDetails:loginDetailsDict passwordGenerationOptions:passwordGenerationOptions]; 118 | 119 | __weak __typeof__ (self) weakSelf = self; 120 | 121 | UIActivityViewController *activityViewController = [self activityViewControllerForExtensionItem:extensionItem viewController:viewController sender:sender]; 122 | 123 | activityViewController.completionWithItemsHandler = ^(NSString *activityType, BOOL completed, NSArray *returnedItems, NSError *activityError) { 124 | if (returnedItems.count == 0) { 125 | NSError *error = nil; 126 | if (activityError) { 127 | NSLog(@"Failed to storeLoginForURLString: %@", activityError); 128 | error = [RSTOnePasswordExtension failedToContactExtensionErrorWithActivityError:activityError]; 129 | } 130 | else { 131 | error = [RSTOnePasswordExtension extensionCancelledByUserError]; 132 | } 133 | 134 | if (completion) { 135 | completion(nil, error); 136 | } 137 | 138 | return; 139 | } 140 | 141 | __strong __typeof__(self) strongSelf = weakSelf; 142 | [strongSelf processReturnedItems:returnedItems completion:completion]; 143 | }; 144 | 145 | [viewController presentViewController:activityViewController animated:YES completion:nil]; 146 | #endif 147 | } 148 | 149 | - (void)changePasswordForLoginForURLString:(NSString *)URLString loginDetails:(NSDictionary *)loginDetailsDict passwordGenerationOptions:(NSDictionary *)passwordGenerationOptions forViewController:(UIViewController *)viewController sender:(id)sender completion:(void (^)(NSDictionary *loginDict, NSError *error))completion { 150 | NSAssert(URLString != nil, @"URLString must not be nil"); 151 | NSAssert(viewController != nil, @"viewController must not be nil"); 152 | 153 | if (![self isSystemAppExtensionAPIAvailable]) { 154 | NSLog(@"Failed to changePasswordForLoginWithUsername, system API is not available"); 155 | if (completion) { 156 | completion(nil, [RSTOnePasswordExtension systemAppExtensionAPINotAvailableError]); 157 | } 158 | 159 | return; 160 | } 161 | 162 | #ifdef __IPHONE_8_0 163 | NSMutableDictionary *item = [NSMutableDictionary new]; 164 | item[AppExtensionVersionNumberKey] = VERSION_NUMBER; 165 | item[AppExtensionURLStringKey] = URLString; 166 | [item addEntriesFromDictionary:loginDetailsDict]; 167 | if (passwordGenerationOptions.count > 0) { 168 | item[AppExtensionPasswordGereratorOptionsKey] = passwordGenerationOptions; 169 | } 170 | 171 | __weak __typeof__ (self) miniMe = self; 172 | UIActivityViewController *activityViewController = [self activityViewControllerForItem:item viewController:viewController sender:sender typeIdentifier:kUTTypeAppExtensionChangePasswordAction]; 173 | 174 | activityViewController.completionWithItemsHandler = ^(NSString *activityType, BOOL completed, NSArray *returnedItems, NSError *activityError) { 175 | if (returnedItems.count == 0) { 176 | NSError *error = nil; 177 | if (activityError) { 178 | NSLog(@"Failed to changePasswordForLoginWithUsername: %@", activityError); 179 | error = [RSTOnePasswordExtension failedToContactExtensionErrorWithActivityError:activityError]; 180 | } 181 | else { 182 | error = [RSTOnePasswordExtension extensionCancelledByUserError]; 183 | } 184 | 185 | if (completion) { 186 | completion(nil, error); 187 | } 188 | 189 | return; 190 | } 191 | 192 | __strong __typeof__(self) strongMe = miniMe; 193 | [strongMe processExtensionItem:returnedItems[0] completion:^(NSDictionary *loginDictionary, NSError *error) { 194 | if (completion) { 195 | completion(loginDictionary, error); 196 | } 197 | }]; 198 | }; 199 | 200 | [viewController presentViewController:activityViewController animated:YES completion:nil]; 201 | #endif 202 | } 203 | 204 | - (void)fillLoginIntoWebView:(id)webView forViewController:(UIViewController *)viewController sender:(id)sender completion:(void (^)(BOOL success, NSError *error))completion { 205 | NSAssert(webView != nil, @"webView must not be nil"); 206 | NSAssert(viewController != nil, @"viewController must not be nil"); 207 | 208 | #ifdef __IPHONE_8_0 209 | if ([webView isKindOfClass:[UIWebView class]]) { 210 | [self fillLoginIntoUIWebView:webView webViewController:viewController sender:(id)sender completion:^(BOOL success, NSError *error) { 211 | if (completion) { 212 | completion(success, error); 213 | } 214 | }]; 215 | } 216 | #if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_8_0 217 | else if ([webView isKindOfClass:[WKWebView class]]) { 218 | [self fillLoginIntoWKWebView:webView forViewController:viewController sender:(id)sender completion:^(BOOL success, NSError *error) { 219 | if (completion) { 220 | completion(success, error); 221 | } 222 | }]; 223 | } 224 | #endif 225 | else { 226 | [NSException raise:@"Invalid argument: web view must be an instance of WKWebView or UIWebView." format:@""]; 227 | } 228 | #endif 229 | } 230 | 231 | #pragma mark - Low-level API 232 | 233 | - (BOOL)isOnePasswordExtensionActivityType:(NSString *)activityType { 234 | return [@"com.agilebits.onepassword-ios.extension" isEqualToString:activityType] || [@"com.agilebits.beta.onepassword-ios.extension" isEqualToString:activityType]; 235 | } 236 | 237 | - (NSExtensionItem *)createExtensionItemToFindLoginForURLString:(NSString *)URLString { 238 | #ifdef __IPHONE_8_0 239 | NSAssert(URLString != nil, @"URLString must not be nil"); 240 | 241 | NSDictionary *item = @{ AppExtensionVersionNumberKey: VERSION_NUMBER, AppExtensionURLStringKey: URLString }; 242 | 243 | NSItemProvider *itemProvider = [[NSItemProvider alloc] initWithItem:item typeIdentifier:kUTTypeAppExtensionFindLoginAction]; 244 | 245 | NSExtensionItem *result = [[NSExtensionItem alloc] init]; 246 | result.attachments = @[ itemProvider ]; 247 | 248 | return result; 249 | #else 250 | return nil; 251 | #endif 252 | } 253 | 254 | - (NSExtensionItem *)createExtensionItemToStoreLoginForURLString:(NSString *)URLString loginDetails:(NSDictionary *)loginDetailsDict passwordGenerationOptions:(NSDictionary *)passwordGenerationOptionsOrNil { 255 | NSAssert(URLString != nil, @"URLString must not be nil"); 256 | NSAssert(loginDetailsDict != nil, @"loginDetailsDict must not be nil"); 257 | 258 | #ifdef __IPHONE_8_0 259 | NSMutableDictionary *newLoginAttributesDict = [NSMutableDictionary new]; 260 | newLoginAttributesDict[AppExtensionVersionNumberKey] = VERSION_NUMBER; 261 | newLoginAttributesDict[AppExtensionURLStringKey] = URLString; 262 | [newLoginAttributesDict addEntriesFromDictionary:loginDetailsDict]; 263 | if (passwordGenerationOptionsOrNil.count > 0) { 264 | newLoginAttributesDict[AppExtensionPasswordGereratorOptionsKey] = passwordGenerationOptionsOrNil; 265 | } 266 | 267 | NSItemProvider *itemProvider = [[NSItemProvider alloc] initWithItem:newLoginAttributesDict typeIdentifier:kUTTypeAppExtensionSaveLoginAction]; 268 | 269 | NSExtensionItem *result = [[NSExtensionItem alloc] init]; 270 | result.attachments = @[ itemProvider ]; 271 | 272 | return result; 273 | #else 274 | return nil; 275 | #endif 276 | } 277 | 278 | - (NSExtensionItem *)createExtensionItemToChangePasswordForLoginForURLString:(NSString *)URLString loginDetails:(NSDictionary *)loginDetailsDict passwordGenerationOptions:(NSDictionary *)passwordGenerationOptionsOrNil { 279 | NSAssert(URLString != nil, @"URLString must not be nil"); 280 | NSAssert(loginDetailsDict != nil, @"loginDetailsDict must not be nil"); 281 | 282 | #ifdef __IPHONE_8_0 283 | NSMutableDictionary *loginAttributesDict = [NSMutableDictionary new]; 284 | loginAttributesDict[AppExtensionVersionNumberKey] = VERSION_NUMBER; 285 | loginAttributesDict[AppExtensionURLStringKey] = URLString; 286 | [loginAttributesDict addEntriesFromDictionary:loginDetailsDict]; 287 | if (passwordGenerationOptionsOrNil.count > 0) { 288 | loginAttributesDict[AppExtensionPasswordGereratorOptionsKey] = passwordGenerationOptionsOrNil; 289 | } 290 | 291 | NSItemProvider *itemProvider = [[NSItemProvider alloc] initWithItem:loginAttributesDict typeIdentifier:kUTTypeAppExtensionChangePasswordAction]; 292 | 293 | NSExtensionItem *result = [[NSExtensionItem alloc] init]; 294 | result.attachments = @[ itemProvider ]; 295 | 296 | return result; 297 | #else 298 | return nil; 299 | #endif 300 | } 301 | 302 | - (void)processReturnedItems:(NSArray *)returnedItems completion:(void (^)(NSDictionary *loginDict, NSError *))completion { 303 | if (returnedItems.count == 0) { 304 | if (completion) { 305 | NSError *error = [RSTOnePasswordExtension extensionCancelledByUserError]; 306 | completion(nil, error); 307 | } 308 | 309 | return; 310 | } 311 | 312 | [self processExtensionItem:returnedItems[0] completion:^(NSDictionary *loginDictionary, NSError *error) { 313 | if (completion) { 314 | completion(loginDictionary, error); 315 | } 316 | }]; 317 | } 318 | 319 | - (void)createExtensionItemForWebView:(id)webView completion:(void (^)(NSExtensionItem *extensionItem, NSError *error))completion { 320 | NSAssert(webView != nil, @"webView must not be nil"); 321 | 322 | #ifdef __IPHONE_8_0 323 | if ([webView isKindOfClass:[UIWebView class]]) { 324 | UIWebView *uiWebView = (UIWebView *)webView; 325 | NSString *collectedPageDetails = [uiWebView stringByEvaluatingJavaScriptFromString:OPWebViewCollectFieldsScript]; 326 | 327 | [self _createExtensionItemForURLString:uiWebView.request.URL.absoluteString webPageDetails:collectedPageDetails completion:completion]; 328 | } 329 | #if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_8_0 330 | else if ([webView isKindOfClass:[WKWebView class]]) { 331 | WKWebView *wkWebView = (WKWebView *)webView; 332 | __weak __typeof__ (self) weakSelf = self; 333 | [wkWebView evaluateJavaScript:OPWebViewCollectFieldsScript completionHandler:^(NSString *result, NSError *error) { 334 | if (!result) { 335 | NSLog(@"1Password Extension failed to collect web page fields: %@", error); 336 | if (completion) { 337 | completion(nil, [RSTOnePasswordExtension failedToCollectFieldsErrorWithUnderlyingError:error]); 338 | } 339 | 340 | return; 341 | } 342 | 343 | [weakSelf _createExtensionItemForURLString:wkWebView.URL.absoluteString webPageDetails:result completion:completion]; 344 | }]; 345 | } 346 | #endif 347 | else { 348 | [NSException raise:@"Invalid argument: web view must be an instance of WKWebView or UIWebView." format:@""]; 349 | } 350 | #endif 351 | } 352 | 353 | - (void)_createExtensionItemForURLString:(NSString *)URLString webPageDetails:(NSString *)webPageDetails completion:(void (^)(NSExtensionItem *extensionItem, NSError *error))completion { 354 | NSDictionary *item = @{ AppExtensionVersionNumberKey : VERSION_NUMBER, AppExtensionURLStringKey : URLString, AppExtensionWebViewPageDetails : webPageDetails }; 355 | 356 | NSItemProvider *itemProvider = [[NSItemProvider alloc] initWithItem:item typeIdentifier:kUTTypeAppExtensionFillWebViewAction]; 357 | 358 | NSExtensionItem *extensionItem = [[NSExtensionItem alloc] init]; 359 | extensionItem.attachments = @[ itemProvider ]; 360 | 361 | if (completion) { 362 | completion(extensionItem, nil); 363 | } 364 | } 365 | 366 | - (void)fillReturnedItems:(NSArray *)returnedItems intoWebView:(id)webView completion:(void (^)(BOOL success, NSError *error))completion { 367 | if (returnedItems.count == 0) { 368 | NSError *error = [RSTOnePasswordExtension extensionCancelledByUserError]; 369 | if (completion) { 370 | completion(NO, error); 371 | } 372 | 373 | return; 374 | } 375 | 376 | __weak __typeof__(self) weakSelf = self; 377 | [self processExtensionItem:returnedItems[0] completion:^(NSDictionary *loginDictionary, NSError *error) { 378 | if (!loginDictionary) { 379 | if (completion) { 380 | completion(NO, error); 381 | } 382 | 383 | return; 384 | } 385 | 386 | __strong __typeof__(weakSelf) strongSelf = weakSelf; 387 | NSString *fillScript = loginDictionary[AppExtensionWebViewPageFillScript]; 388 | [strongSelf executeFillScript:fillScript inWebView:webView completion:^(BOOL success, NSError *error) { 389 | if (completion) { 390 | completion(success, error); 391 | } 392 | }]; 393 | }]; 394 | } 395 | 396 | 397 | #pragma mark - Helpers 398 | 399 | - (UIActivityViewController *)activityViewControllerForExtensionItem:(NSExtensionItem *)extensionItem viewController:(UIViewController*)viewController sender:(id)sender { 400 | #ifdef __IPHONE_8_0 401 | UIActivityViewController *controller = [[UIActivityViewController alloc] initWithActivityItems:@[ extensionItem ] applicationActivities:nil]; 402 | 403 | if ([sender isKindOfClass:[UIBarButtonItem class]]) { 404 | controller.popoverPresentationController.barButtonItem = sender; 405 | } 406 | else if ([sender isKindOfClass:[UIView class]]) { 407 | controller.popoverPresentationController.sourceView = [sender superview]; 408 | controller.popoverPresentationController.sourceRect = [sender frame]; 409 | } 410 | else { 411 | NSLog(@"sender can be nil on iPhone"); 412 | } 413 | 414 | return controller; 415 | #else 416 | return nil; 417 | #endif 418 | } 419 | 420 | 421 | - (UIActivityViewController *)activityViewControllerForItem:(NSDictionary *)item viewController:(UIViewController*)viewController sender:(id)sender typeIdentifier:(NSString *)typeIdentifier { 422 | #ifdef __IPHONE_8_0 423 | 424 | NSItemProvider *itemProvider = [[NSItemProvider alloc] initWithItem:item typeIdentifier:typeIdentifier]; 425 | 426 | NSExtensionItem *extensionItem = [[NSExtensionItem alloc] init]; 427 | extensionItem.attachments = @[ itemProvider ]; 428 | 429 | UIActivityViewController *controller = [[UIActivityViewController alloc] initWithActivityItems:@[ extensionItem ] applicationActivities:nil]; 430 | 431 | if ([sender isKindOfClass:[UIBarButtonItem class]]) { 432 | controller.popoverPresentationController.barButtonItem = sender; 433 | } 434 | else if ([sender isKindOfClass:[UIView class]]) { 435 | controller.popoverPresentationController.sourceView = [sender superview]; 436 | controller.popoverPresentationController.sourceRect = [sender frame]; 437 | } 438 | else { 439 | NSLog(@"sender can be nil on iPhone"); 440 | } 441 | 442 | return controller; 443 | #else 444 | return nil; 445 | #endif 446 | } 447 | 448 | 449 | #pragma mark - Errors 450 | 451 | + (NSError *)systemAppExtensionAPINotAvailableError { 452 | NSDictionary *userInfo = @{ NSLocalizedDescriptionKey : NSLocalizedString(@"App Extension API is not available is this version of iOS", @"1Password Extension Error Message") }; 453 | return [NSError errorWithDomain:AppExtensionErrorDomain code:AppExtensionErrorCodeAPINotAvailable userInfo:userInfo]; 454 | } 455 | 456 | 457 | + (NSError *)extensionCancelledByUserError { 458 | NSDictionary *userInfo = @{ NSLocalizedDescriptionKey : NSLocalizedString(@"1Password Extension was cancelled by the user", @"1Password Extension Error Message") }; 459 | return [NSError errorWithDomain:AppExtensionErrorDomain code:AppExtensionErrorCodeCancelledByUser userInfo:userInfo]; 460 | } 461 | 462 | + (NSError *)failedToContactExtensionErrorWithActivityError:(NSError *)activityError { 463 | NSMutableDictionary *userInfo = [NSMutableDictionary new]; 464 | userInfo[NSLocalizedDescriptionKey] = NSLocalizedString(@"Failed to contact the 1Password Extension", @"1Password Extension Error Message"); 465 | if (activityError) { 466 | userInfo[NSUnderlyingErrorKey] = activityError; 467 | } 468 | 469 | return [NSError errorWithDomain:AppExtensionErrorDomain code:AppExtensionErrorCodeFailedToContactExtension userInfo:userInfo]; 470 | } 471 | 472 | + (NSError *)failedToCollectFieldsErrorWithUnderlyingError:(NSError *)underlyingError { 473 | NSMutableDictionary *userInfo = [NSMutableDictionary new]; 474 | userInfo[NSLocalizedDescriptionKey] = NSLocalizedString(@"Failed to execute script that collects web page information", @"1Password Extension Error Message"); 475 | if (underlyingError) { 476 | userInfo[NSUnderlyingErrorKey] = underlyingError; 477 | } 478 | 479 | return [NSError errorWithDomain:AppExtensionErrorDomain code:AppExtensionErrorCodeCollectFieldsScriptFailed userInfo:userInfo]; 480 | } 481 | 482 | + (NSError *)failedToFillFieldsErrorWithLocalizedErrorMessage:(NSString *)errorMessage underlyingError:(NSError *)underlyingError { 483 | NSMutableDictionary *userInfo = [NSMutableDictionary new]; 484 | if (errorMessage) { 485 | userInfo[NSLocalizedDescriptionKey] = errorMessage; 486 | } 487 | if (underlyingError) { 488 | userInfo[NSUnderlyingErrorKey] = underlyingError; 489 | } 490 | 491 | return [NSError errorWithDomain:AppExtensionErrorDomain code:AppExtensionErrorCodeFillFieldsScriptFailed userInfo:userInfo]; 492 | } 493 | 494 | + (NSError *)failedToLoadItemProviderDataErrorWithUnderlyingError:(NSError *)underlyingError { 495 | NSMutableDictionary *userInfo = [NSMutableDictionary new]; 496 | userInfo[NSLocalizedDescriptionKey] = NSLocalizedString(@"Failed to parse information returned by 1Password Extension", @"1Password Extension Error Message"); 497 | if (underlyingError) { 498 | userInfo[NSUnderlyingErrorKey] = underlyingError; 499 | } 500 | 501 | return [[NSError alloc] initWithDomain:AppExtensionErrorDomain code:AppExtensionErrorCodeFailedToLoadItemProviderData userInfo:userInfo]; 502 | } 503 | 504 | + (NSError *)failedToObtainURLStringFromWebViewError { 505 | NSDictionary *userInfo = @{ NSLocalizedDescriptionKey : NSLocalizedString(@"Failed to obtain URL String from web view. The web view must be loaded completely when calling the 1Password Extension", @"1Password Extension Error Message") }; 506 | return [NSError errorWithDomain:AppExtensionErrorDomain code:AppExtensionErrorCodeFailedToObtainURLStringFromWebView userInfo:userInfo]; 507 | } 508 | 509 | 510 | #pragma mark - App Extension ItemProvider Callback 511 | 512 | #ifdef __IPHONE_8_0 513 | - (void)processExtensionItem:(NSExtensionItem *)extensionItem completion:(void (^)(NSDictionary *loginDictionary, NSError *error))completion { 514 | if (extensionItem.attachments.count == 0) { 515 | NSDictionary *userInfo = @{ NSLocalizedDescriptionKey: @"Unexpected data returned by App Extension: extension item had no attachments." }; 516 | NSError *error = [[NSError alloc] initWithDomain:AppExtensionErrorDomain code:AppExtensionErrorCodeUnexpectedData userInfo:userInfo]; 517 | if (completion) { 518 | completion(nil, error); 519 | } 520 | return; 521 | } 522 | 523 | NSItemProvider *itemProvider = extensionItem.attachments[0]; 524 | if (![itemProvider hasItemConformingToTypeIdentifier:(NSString *)kUTTypePropertyList]) { 525 | NSDictionary *userInfo = @{ NSLocalizedDescriptionKey: @"Unexpected data returned by App Extension: extension item attachment does not conform to kUTTypePropertyList type identifier" }; 526 | NSError *error = [[NSError alloc] initWithDomain:AppExtensionErrorDomain code:AppExtensionErrorCodeUnexpectedData userInfo:userInfo]; 527 | if (completion) { 528 | completion(nil, error); 529 | } 530 | return; 531 | } 532 | 533 | 534 | [itemProvider loadItemForTypeIdentifier:(NSString *)kUTTypePropertyList options:nil completionHandler:^(NSDictionary *loginDictionary, NSError *itemProviderError) 535 | { 536 | NSError *error = nil; 537 | if (!loginDictionary) { 538 | NSLog(@"Failed to loadItemForTypeIdentifier: %@", itemProviderError); 539 | error = [RSTOnePasswordExtension failedToLoadItemProviderDataErrorWithUnderlyingError:itemProviderError]; 540 | } 541 | 542 | if (completion) { 543 | if ([NSThread isMainThread]) { 544 | completion(loginDictionary, error); 545 | } 546 | else { 547 | dispatch_async(dispatch_get_main_queue(), ^{ 548 | completion(loginDictionary, error); 549 | }); 550 | } 551 | } 552 | }]; 553 | } 554 | 555 | 556 | #pragma mark - Web view integration 557 | 558 | #if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_8_0 559 | - (void)fillLoginIntoWKWebView:(WKWebView *)webView forViewController:(UIViewController *)viewController sender:(id)sender completion:(void (^)(BOOL success, NSError *error))completion { 560 | __weak __typeof__ (self) miniMe = self; 561 | [webView evaluateJavaScript:OPWebViewCollectFieldsScript completionHandler:^(NSString *result, NSError *error) { 562 | if (!result) { 563 | NSLog(@"1Password Extension failed to collect web page fields: %@", error); 564 | if (completion) { 565 | completion(NO,[RSTOnePasswordExtension failedToCollectFieldsErrorWithUnderlyingError:error]); 566 | } 567 | 568 | return; 569 | } 570 | 571 | __strong __typeof__(self) strongMe = miniMe; 572 | [strongMe findLoginIn1PasswordWithURLString:webView.URL.absoluteString collectedPageDetails:result forWebViewController:viewController sender:sender withWebView:webView completion:^(BOOL success, NSError *error) { 573 | if (completion) { 574 | completion(success, error); 575 | } 576 | }]; 577 | }]; 578 | } 579 | #endif 580 | 581 | - (void)fillLoginIntoUIWebView:(UIWebView *)webView webViewController:(UIViewController *)viewController sender:(id)sender completion:(void (^)(BOOL success, NSError *error))completion { 582 | NSString *collectedPageDetails = [webView stringByEvaluatingJavaScriptFromString:OPWebViewCollectFieldsScript]; 583 | [self findLoginIn1PasswordWithURLString:webView.request.URL.absoluteString collectedPageDetails:collectedPageDetails forWebViewController:viewController sender:sender withWebView:webView completion:^(BOOL success, NSError *error) { 584 | if (completion) { 585 | completion(success, error); 586 | } 587 | }]; 588 | } 589 | 590 | - (void)findLoginIn1PasswordWithURLString:(NSString *)URLString collectedPageDetails:(NSString *)collectedPageDetails forWebViewController:(UIViewController *)forViewController sender:(id)sender withWebView:(id)webView completion:(void (^)(BOOL success, NSError *error))completion { 591 | if ([URLString length] == 0) { 592 | NSError *URLStringError = [RSTOnePasswordExtension failedToObtainURLStringFromWebViewError]; 593 | NSLog(@"Failed to findLoginIn1PasswordWithURLString: %@", URLStringError); 594 | completion(NO, URLStringError); 595 | } 596 | 597 | NSDictionary *item = @{ AppExtensionVersionNumberKey : VERSION_NUMBER, AppExtensionURLStringKey : URLString, AppExtensionWebViewPageDetails : collectedPageDetails }; 598 | 599 | __weak __typeof__ (self) miniMe = self; 600 | 601 | UIActivityViewController *activityViewController = [self activityViewControllerForItem:item viewController:forViewController sender:sender typeIdentifier:kUTTypeAppExtensionFillWebViewAction]; 602 | activityViewController.completionWithItemsHandler = ^(NSString *activityType, BOOL completed, NSArray *returnedItems, NSError *activityError) { 603 | if (returnedItems.count == 0) { 604 | NSError *error = nil; 605 | if (activityError) { 606 | NSLog(@"Failed to findLoginIn1PasswordWithURLString: %@", activityError); 607 | error = [RSTOnePasswordExtension failedToContactExtensionErrorWithActivityError:activityError]; 608 | } 609 | else { 610 | error = [RSTOnePasswordExtension extensionCancelledByUserError]; 611 | } 612 | 613 | if (completion) { 614 | completion(NO, error); 615 | } 616 | 617 | return; 618 | } 619 | 620 | __strong __typeof__(self) strongMe = miniMe; 621 | [strongMe processExtensionItem:returnedItems[0] completion:^(NSDictionary *loginDictionary, NSError *processExtensionItemError) { 622 | if (!loginDictionary) { 623 | if (completion) { 624 | completion(NO, processExtensionItemError); 625 | } 626 | 627 | return; 628 | } 629 | 630 | __strong __typeof__(self) strongMe2 = miniMe; 631 | NSString *fillScript = loginDictionary[AppExtensionWebViewPageFillScript]; 632 | [strongMe2 executeFillScript:fillScript inWebView:webView completion:^(BOOL success, NSError *executeFillScriptError) { 633 | if (completion) { 634 | completion(success, executeFillScriptError); 635 | } 636 | }]; 637 | }]; 638 | }; 639 | 640 | [forViewController presentViewController:activityViewController animated:YES completion:nil]; 641 | } 642 | 643 | - (void)executeFillScript:(NSString *)fillScript inWebView:(id)webView completion:(void (^)(BOOL success, NSError *error))completion { 644 | if (!fillScript) { 645 | NSLog(@"Failed to executeFillScript, fillScript is missing"); 646 | if (completion) { 647 | completion(NO, [RSTOnePasswordExtension failedToFillFieldsErrorWithLocalizedErrorMessage:NSLocalizedString(@"Failed to fill web page because script is missing", @"1Password Extension Error Message") underlyingError:nil]); 648 | } 649 | 650 | return; 651 | } 652 | 653 | NSMutableString *scriptSource = [OPWebViewFillScript mutableCopy]; 654 | [scriptSource appendFormat:@"('%@');", fillScript]; 655 | 656 | if ([webView isKindOfClass:[UIWebView class]]) { 657 | NSString *result = [((UIWebView *)webView) stringByEvaluatingJavaScriptFromString:scriptSource]; 658 | BOOL success = (result != nil); 659 | NSError *error = nil; 660 | 661 | if (!success) { 662 | NSLog(@"Cannot executeFillScript, stringByEvaluatingJavaScriptFromString failed"); 663 | error = [RSTOnePasswordExtension failedToFillFieldsErrorWithLocalizedErrorMessage:NSLocalizedString(@"Failed to fill web page because script could not be evaluated", @"1Password Extension Error Message") underlyingError:nil]; 664 | } 665 | 666 | if (completion) { 667 | completion(success, error); 668 | } 669 | 670 | return; 671 | } 672 | 673 | #if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_8_0 674 | if ([webView isKindOfClass:[WKWebView class]]) { 675 | [((WKWebView *)webView) evaluateJavaScript:scriptSource completionHandler:^(NSString *result, NSError *evaluationError) { 676 | BOOL success = (result != nil); 677 | NSError *error = nil; 678 | 679 | if (!success) { 680 | NSLog(@"Cannot executeFillScript, evaluateJavaScript failed: %@", evaluationError); 681 | error = [RSTOnePasswordExtension failedToFillFieldsErrorWithLocalizedErrorMessage:NSLocalizedString(@"Failed to fill web page because script could not be evaluated", @"1Password Extension Error Message") underlyingError:error]; 682 | } 683 | 684 | if (completion) { 685 | completion(success, error); 686 | } 687 | }]; 688 | 689 | return; 690 | } 691 | #endif 692 | 693 | [NSException raise:@"Invalid argument: web view must be an instance of WKWebView or UIWebView." format:@""]; 694 | } 695 | #endif 696 | 697 | 698 | #pragma mark - WebView field collection and filling scripts 699 | 700 | static NSString *const OPWebViewCollectFieldsScript = @"var f;document.collect=l;function l(a,b){var c=Array.prototype.slice.call(a.querySelectorAll('input, select'));f=b;c.forEach(p);return c.filter(function(a){q(a,['select','textarea'])?a=!0:q(a,'input')?(a=(a.getAttribute('type')||'').toLowerCase(),a=!('button'===a||'submit'===a||'reset'==a||'file'===a||'hidden'===a||'image'===a)):a=!1;return a}).map(s)}function s(a,b){var c=a.opid,d=a.id||a.getAttribute('id')||null,g=a.name||null,z=a['class']||a.getAttribute('class')||null,A=a.rel||a.getAttribute('rel')||null,B=String.prototype.toLowerCase.call(a.type||a.getAttribute('type')),C=a.value,D=-1==a.maxLength?999:a.maxLength,E=a.getAttribute('x-autocompletetype')||a.getAttribute('autocompletetype')||a.getAttribute('autocomplete')||null,k;k=[];var h,n;if(a.options){h=0;for(n=a.options.length;h=b.cells.length)return null;a=b.cells[a.cellIndex];return t(a.innerText||a.textContent)}function w(a){var b=a.id,c=a.name,d=a.ownerDocument;if(void 0===b&&void 0===c)return null;b=O(String.prototype.replace.call(b,\"'\",\"\\\\'\"));c=O(String.prototype.replace.call(c,\"'\",\"\\\\'\"));if(b=d.querySelector(\"label[for='\"+b+\"']\")||d.querySelector(\"label[for='\"+c+\"']\"))return t(b.innerText||b.textContent);do{if('label'===(''+a.tagName).toLowerCase())return t(a.innerText||a.textContent);a=a.parentNode}while(a&&a!=d);return null};function t(a){var b=null;a&&(b=a.toLowerCase().replace(/\\s/mg,'').replace(/[~`!@$%^&*()\\-_+=:;'\"\\[\\]|\\\\,<.>\\/?]/mg,''),b=0b||b>g.width||0>c||c>g.height)return u(a);if(b=a.ownerDocument.elementFromPoint(b+3,c+3)){if('label'===(b.tagName||'').toLowerCase())return g=String.prototype.replace.call(a.id,\"'\",\"\\\\'\"),c=String.prototype.replace.call(a.name,\"'\",\"\\\\'\"),a=a.ownerDocument.querySelector(\"label[for='\"+g+\"']\")||a.ownerDocument.querySelector(\"label[for='\"+c+\"']\"),b===a;if(b.tagName===a.tagName)return!0}return!1}function u(a){var b=a;a=(a=a.ownerDocument)?a.defaultView:{};for(var c;b&&b!==document;){c=a.getComputedStyle?a.getComputedStyle(b,null):b.style;if('none'===c.display||'hidden'==c.visibility)return!1;b=b.parentNode}return b===document}function O(a){return a?a.replace(/([:\\\\.'])/g,'\\\\$1'):null};var P=/^[\\/\\?]/;function N(a){if(!a)return null;if(0==a.indexOf('http'))return a;var b=window.location.protocol+'//'+window.location.hostname;window.location.port&&''!=window.location.port&&(b+=':'+window.location.port);a.match(P)||(a='/'+a);return b+a}var L=new function(){return{a:function(){function a(){return(65536*(1+Math.random())|0).toString(16).substring(1).toUpperCase()}return[a(),a(),a(),a(),a(),a(),a(),a()].join('')}}}; (function collect(uuid) { var fields = document.collect(document, uuid); return { 'url': document.baseURI, 'fields': fields }; })('uuid');"; 701 | 702 | static NSString *const OPWebViewFillScript = @"var e=!0,h=!0;document.fill=k;function k(a){var b,c=[],d=a.properties,f=1,g;d&&d.delay_between_operations&&(f=d.delay_between_operations);if(null!=a.savedURL&&0===a.savedURL.indexOf('https://')&&'http:'==document.location.protocol&&(b=confirm('This page is not protected. Any information you submit can potentially be seen by others. This login was originally saved on a secure page, so it is possible you are being tricked into revealing your login information.\\n\\nDo you still wish to fill this login?'),!1==b))return;g=function(a,b){var d=a[0];void 0===d?b():('delay'===d.operation?f=d.parameters[0]:c.push(l(d)),setTimeout(function(){g(a.slice(1),b)},f))};if(b=a.options)h=b.animate,e=b.markFilling;a.hasOwnProperty('script')&&(b=a.script,g(b,function(){c=Array.prototype.concat.apply(c,void 0);a.hasOwnProperty('autosubmit')&&setTimeout(function(){autosubmit(a.autosubmit,d.allow_clicky_autosubmit)},AUTOSUBMIT_DELAY);'object'==typeof protectedGlobalPage&&protectedGlobalPage.a('fillItemResults',{documentUUID:documentUUID,fillContextIdentifier:a.fillContextIdentifier,usedOpids:c},function(){})}))}var t={fill_by_opid:m,fill_by_query:n,click_on_opid:p,click_on_query:q,touch_all_fields:r,simple_set_value_by_query:s,delay:null};function l(a){var b;if(!a.hasOwnProperty('operation')||!a.hasOwnProperty('parameters'))return null;b=a.operation;return t.hasOwnProperty(b)?t[b].apply(this,a.parameters):null}function m(a,b){var c;return(c=u(a))?(v(c,b),c.opid):null}function n(a,b){var c;c=document.querySelectorAll(a);return Array.prototype.map.call(c,function(a){v(a,b);return a.opid},this)}function s(a,b){var c,d=[];c=document.querySelectorAll(a);Array.prototype.forEach.call(c,function(a){void 0!==a.value&&(a.value=b,d.push(a.opid))});return d}function p(a){a=u(a);w(a);'function'===typeof a.click&&a.click();return a?a.opid:null}function q(a){a=document.querySelectorAll(a);return Array.prototype.map.call(a,function(a){w(a);'function'===typeof a.click&&a.click();'function'===typeof a.focus&&a.focus();return a.opid},this)}function r(){x()};var y={'true':!0,y:!0,1:!0,yes:!0,'✓':!0},z=200;function v(a,b){var c;if(a&&null!==b&&void 0!==b)switch(e&&a.form&&!a.form.opfilled&&(a.form.opfilled=!0),a.type?a.type.toLowerCase():null){case 'checkbox':c=b&&1<=b.length&&y.hasOwnProperty(b.toLowerCase())&&!0===y[b.toLowerCase()];a.checked===c||A(a,function(a){a.checked=c});break;case 'radio':!0===y[b.toLowerCase()]&&a.click();break;default:a.value==b||A(a,function(a){a.value=b})}}function A(a,b){B(a);b(a);C(a);D(a)&&(a.className+=' com-agilebits-onepassword-extension-animated-fill',setTimeout(function(){a&&a.className&&(a.className=a.className.replace(/(\\s)?com-agilebits-onepassword-extension-animated-fill/,''))},z))};function E(a,b){var c;c=a.ownerDocument.createEvent('KeyboardEvent');c.initKeyboardEvent?c.initKeyboardEvent(b,!0,!0):c.initKeyEvent&&c.initKeyEvent(b,!0,!0,null,!1,!1,!1,!1,0,0);a.dispatchEvent(c)}function B(a){w(a);a.focus();E(a,'keydown');E(a,'keyup');E(a,'keypress')}function C(a){var b=a.ownerDocument.createEvent('HTMLEvents'),c=a.ownerDocument.createEvent('HTMLEvents');E(a,'keydown');E(a,'keyup');E(a,'keypress');c.initEvent('input',!0,!0);a.dispatchEvent(c);b.initEvent('change',!0,!0);a.dispatchEvent(b);a.blur()}function w(a){!a||a&&'function'!==typeof a.click||a.click()}function F(){var a=RegExp('(pin|password|passwort|kennwort|passe|contraseña|senha|密码|adgangskode|hasło|wachtwoord)','i');return Array.prototype.slice.call(document.querySelectorAll(\"input[type='text']\")).filter(function(b){return b.value&&a.test(b.value)},this)}function x(){F().forEach(function(a){B(a);a.click&&a.click();C(a)})}function D(a){var b;if(b=h)a:{b=a;for(var c=a.ownerDocument,c=c?c.defaultView:{},d;b&&b!==document;){d=c.getComputedStyle?c.getComputedStyle(b,null):b.style;if('none'===d.display||'hidden'==d.visibility){b=!1;break a}b=b.parentNode}b=b===document}return b?-1!=='email text password number tel url'.split(' ').indexOf(a.type||''):!1}function u(a){var b,c,d;if(a)for(d=document.querySelectorAll('input, select'),b=0,c=d.length;b