├── 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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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: 
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