├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── Package.swift ├── ProgressWebViewController.podspec ├── ProgressWebViewController.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── xcshareddata │ └── xcschemes │ │ └── ProgressWebViewControllerDemo.xcscheme └── xcuserdata │ └── Kf.xcuserdatad │ └── xcschemes │ └── xcschememanagement.plist ├── ProgressWebViewController ├── Enums.swift ├── Extensions │ ├── UIViewController+Helpers.swift │ └── UIWindow+Helpers.swift ├── Info.plist ├── Media.xcassets │ ├── Back.imageset │ │ ├── Back.png │ │ ├── Back@2x.png │ │ ├── Back@3x.png │ │ └── Contents.json │ ├── Contents.json │ └── Forward.imageset │ │ ├── Contents.json │ │ ├── Forward.png │ │ ├── Forward@2x.png │ │ └── Forward@3x.png ├── ProgressWebViewController.h └── ProgressWebViewController.swift ├── ProgressWebViewControllerDemo ├── AppDelegate.swift ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── Info.plist └── ViewController.swift ├── README.md ├── apps └── Hikingbook.png └── screenshots ├── progressWebViewController.png └── progressWebViewController2.png /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | # Xcode 3 | # 4 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 5 | 6 | ## User settings 7 | xcuserdata/ 8 | 9 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 10 | *.xcscmblueprint 11 | *.xccheckout 12 | 13 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 14 | build/ 15 | DerivedData/ 16 | *.moved-aside 17 | *.pbxuser 18 | !default.pbxuser 19 | *.mode1v3 20 | !default.mode1v3 21 | *.mode2v3 22 | !default.mode2v3 23 | *.perspectivev3 24 | !default.perspectivev3 25 | 26 | ## Obj-C/Swift specific 27 | *.hmap 28 | 29 | ## App packaging 30 | *.ipa 31 | *.dSYM.zip 32 | *.dSYM 33 | 34 | ## Playgrounds 35 | timeline.xctimeline 36 | playground.xcworkspace 37 | 38 | # Swift Package Manager 39 | # 40 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 41 | # Packages/ 42 | # Package.pins 43 | # Package.resolved 44 | # *.xcodeproj 45 | # 46 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata 47 | # hence it is not needed unless you have added a package configuration file to your project 48 | # .swiftpm 49 | 50 | .build/ 51 | 52 | # CocoaPods 53 | # 54 | # We recommend against adding the Pods directory to your .gitignore. However 55 | # you should judge for yourself, the pros and cons are mentioned at: 56 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 57 | # 58 | # Pods/ 59 | # 60 | # Add this line if you want to avoid checking in source code from the Xcode workspace 61 | # *.xcworkspace 62 | 63 | # Carthage 64 | # 65 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 66 | # Carthage/Checkouts 67 | 68 | Carthage/Build/ 69 | 70 | # Accio dependency management 71 | Dependencies/ 72 | .accio/ 73 | 74 | # fastlane 75 | # 76 | # It is recommended to not store the screenshots in the git repo. 77 | # Instead, use fastlane to re-generate the screenshots whenever they are needed. 78 | # For more information about the recommended setup visit: 79 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 80 | 81 | fastlane/report.xml 82 | fastlane/Preview.html 83 | fastlane/screenshots/**/*.png 84 | fastlane/test_output 85 | 86 | # Code Injection 87 | # 88 | # After new code Injection tools there's a generated folder /iOSInjectionProject 89 | # https://github.com/johnno1962/injectionforxcode 90 | 91 | iOSInjectionProject/ 92 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## Version 4.0.0 4 | 5 | - The min support iOS version is 13. 6 | - Refactor cookie management with `WKWebsiteDataStore`. 7 | - Update performance for multiple web views use case by sharing the same process pool. 8 | 9 | ## Version 3.2.0 10 | 11 | - Support JavaScript alert panel, confirm panel, and text input panel 12 | - Refresh webview event through scroll is disabled 13 | - Make web view inspectable for debug 14 | - Fix strong reference cycle 15 | 16 | ## Version 3.1.0 17 | 18 | - Support Xcode 15 19 | - Fix url fragment 20 | - Reset isReloadWhenAppear to correct load new url 21 | 22 | Breaking Changes 23 | 24 | - Add target view controller in the push navigation way 25 | 26 | ## Version 3.0.0 27 | 28 | - Improve performance 29 | - Rename `url` as `defaultURL` and expose `currentURL` (Breaking Change) 30 | - Support window.open() 31 | - Add delegate to handle createWebViewWith() 32 | - Reload webview once the webview process is terminated 33 | 34 | ## Version 2.0.0 35 | 36 | - Support container view 37 | - Show activity indicator if no navigation bar 38 | - Support dark mode 39 | - Correct navigation way 40 | 41 | ## Version 1.11.0 42 | 43 | - Support video "playinline" 44 | - Support popoverPresentationController 45 | - Add loadBlankPage() 46 | 47 | ## Version 1.10.0 48 | 49 | - Remove UIWebView APIs completely 50 | - Add functionality: 51 | - clear caches 52 | - load html string 53 | - evaluate JavaScript 54 | 55 | ## Version 1.9.0 56 | 57 | - Support Swift Package Manager 58 | - Ignore fragment for push navigation way 59 | 60 | ## Version 1.8.2 61 | 62 | - Customize the web view for push navigation. 63 | - Fix refresh control issue. 64 | 65 | ## Version 1.8.1 66 | 67 | - Expose the delegate for scroll view. 68 | - Add functionality to scroll to top. 69 | - You can add another bar button items into the navigation bar. 70 | - Fix issues. 71 | 72 | ## Version 1.8 73 | 74 | - Migrate to Xcode 10.2 and Swift 5. 75 | - Support the push navigation way. 76 | - Support pull-to-refresh to reload the web view. 77 | 78 | ## Version 1.7 79 | 80 | - Add delegate: progressWebViewController(controller:decidePolicy:response:). 81 | - Add disable zoom configuration. 82 | - Support iFrame. 83 | - Fix the issue: reload a url when a user tries to go back because the cookies in the header are not taken. 84 | 85 | ## Version 1.6.1 86 | 87 | - Ensure all available cookies are set in the navigation request. 88 | 89 | ## Version 1.6.0 90 | 91 | - Reload url if cookies or headers are changed. 92 | - Ensure the web view loads a url in the main thread. 93 | 94 | ## Version 1.5.1 95 | 96 | - Fix the issue: load the url infinitely if there is no any required cookies and request's cookies. 97 | 98 | ## Version 1.5.0 99 | 100 | - Open the special urls including the app store, tel, mailto, sms, and \_blank with other apps. 101 | 102 | ## Version 1.4.0 103 | 104 | - Support custom headers. 105 | - Support custom user agent. 106 | 107 | ## Version 1.3.1 108 | 109 | - Support large titles for navigation bars in iOS 11. 110 | 111 | ## Version 1.3.0 112 | 113 | - Let webView and progressView be optional. 114 | 115 | ## Version 1.2.0 116 | 117 | - Browse the local html files. 118 | - Change the default done bar button position. 119 | 120 | ## Version 1.1.0 121 | 122 | - Assign cookies to the web view. 123 | - Fix warnings about layout constraints in the demo project. 124 | 125 | ## Version 1.0.1 126 | 127 | - Fix a crash if the toolbarItemTypes is empty. 128 | - Update ProgressWebViewControllerDelegate. 129 | - Correct url passed to the delegate. 130 | - Ensure web view's url exist. 131 | 132 | ## Version 1.0.0 133 | 134 | Initial version 135 | 136 | - Progress bar in navigation bar. 137 | - Bypass SSL according to the assigned hosts. 138 | - Customize bar button items. 139 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Zheng-Xiang Ke 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.1 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "ProgressWebViewController", 8 | platforms: [.iOS(.v13)], 9 | products: [ 10 | // Products define the executables and libraries produced by a package, and make them visible to other packages. 11 | .library( 12 | name: "ProgressWebViewController", 13 | targets: ["ProgressWebViewController"]), 14 | ], 15 | dependencies: [ 16 | // Dependencies declare other packages that this package depends on. 17 | // .package(url: /* package url */, from: "1.0.0"), 18 | ], 19 | targets: [ 20 | // Targets are the basic building blocks of a package. A target can define a module or a test suite. 21 | // Targets can depend on other targets in this package, and on products in packages which this package depends on. 22 | .target( 23 | name: "ProgressWebViewController", 24 | dependencies: [], 25 | path: "ProgressWebViewController"), 26 | ], 27 | swiftLanguageVersions: [.v5] 28 | ) 29 | -------------------------------------------------------------------------------- /ProgressWebViewController.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # Be sure to run `pod lib lint ProgressWebViewController.podspec' to ensure this is a 3 | # valid spec before submitting. 4 | # 5 | # Any lines starting with a # are optional, but their use is encouraged 6 | # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html 7 | # 8 | 9 | Pod::Spec.new do |s| 10 | s.name = 'ProgressWebViewController' 11 | s.version = '4.0.0' 12 | s.summary = 'A WebViewController implemented by WKWebView with a progress bar in the navigation bar' 13 | 14 | # This description is used to generate tags and improve search results. 15 | # * Think: What does it do? Why did you write it? What is the focus? 16 | # * Try to keep it short, snappy and to the point. 17 | # * Write the description between the DESC delimiters below. 18 | # * Finally, don't worry about the indent, CocoaPods strips it! 19 | 20 | s.description = 'A WebViewController implemented by WKWebView with a progress bar in the navigation bar. The WebViewController is safari-like web browser.' 21 | 22 | s.homepage = 'https://github.com/kf99916/ProgressWebViewController' 23 | s.screenshots = 'https://raw.githubusercontent.com/kf99916/ProgressWebViewController/master/screenshots/progressWebViewController.png' 24 | s.license = { :type => 'MIT', :file => 'LICENSE' } 25 | s.author = { 'Zheng-Xiang Ke' => 'kf99916@gmail.com' } 26 | s.source = { :git => 'https://github.com/kf99916/ProgressWebViewController.git', :tag => s.version.to_s } 27 | s.swift_versions = '5.0' 28 | # s.social_media_url = 'https://twitter.com/' 29 | 30 | s.ios.deployment_target = '13.0' 31 | 32 | s.source_files = 'ProgressWebViewController/**/*.swift' 33 | s.resources = ['ProgressWebViewController/**/*.xcassets'] 34 | 35 | # s.public_header_files = 'Pod/Classes/**/*.h' 36 | # s.frameworks = 'UIKit', 'MapKit' 37 | # s.dependency 'AFNetworking', '~> 2.3' 38 | end 39 | -------------------------------------------------------------------------------- /ProgressWebViewController.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 48; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 35B9D9292D3530D000BA0C85 /* UIViewController+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35B9D9282D3530D000BA0C85 /* UIViewController+Helpers.swift */; }; 11 | 35B9D92B2D3530F400BA0C85 /* UIWindow+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35B9D92A2D3530F400BA0C85 /* UIWindow+Helpers.swift */; }; 12 | AA28A1B31F91C84600B67E53 /* ProgressWebViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = AA28A1B11F91C84600B67E53 /* ProgressWebViewController.h */; settings = {ATTRIBUTES = (Public, ); }; }; 13 | AA28A1C01F91C86700B67E53 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA28A1BF1F91C86700B67E53 /* AppDelegate.swift */; }; 14 | AA28A1C21F91C86700B67E53 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA28A1C11F91C86700B67E53 /* ViewController.swift */; }; 15 | AA28A1C51F91C86700B67E53 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AA28A1C31F91C86700B67E53 /* Main.storyboard */; }; 16 | AA28A1C71F91C86700B67E53 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = AA28A1C61F91C86700B67E53 /* Assets.xcassets */; }; 17 | AA28A1CA1F91C86700B67E53 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AA28A1C81F91C86700B67E53 /* LaunchScreen.storyboard */; }; 18 | AA28A1D01F91C87D00B67E53 /* ProgressWebViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA28A1CF1F91C87D00B67E53 /* ProgressWebViewController.swift */; }; 19 | AA28A1D31F91C89E00B67E53 /* WebKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AA28A1D21F91C89D00B67E53 /* WebKit.framework */; }; 20 | AA28A1D41F91C8A500B67E53 /* ProgressWebViewController.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AA28A1AE1F91C84600B67E53 /* ProgressWebViewController.framework */; }; 21 | AA28A1D61F91E1A200B67E53 /* ProgressWebViewController.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = AA28A1AE1F91C84600B67E53 /* ProgressWebViewController.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 22 | AA28A1D81F91ED7C00B67E53 /* Enums.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA28A1D71F91ED7C00B67E53 /* Enums.swift */; }; 23 | AA28A1DA1F91FF7A00B67E53 /* Media.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = AA28A1D91F91FF7A00B67E53 /* Media.xcassets */; }; 24 | /* End PBXBuildFile section */ 25 | 26 | /* Begin PBXCopyFilesBuildPhase section */ 27 | AA28A1D51F91E19800B67E53 /* Copy Frameworks */ = { 28 | isa = PBXCopyFilesBuildPhase; 29 | buildActionMask = 12; 30 | dstPath = ""; 31 | dstSubfolderSpec = 10; 32 | files = ( 33 | AA28A1D61F91E1A200B67E53 /* ProgressWebViewController.framework in Copy Frameworks */, 34 | ); 35 | name = "Copy Frameworks"; 36 | runOnlyForDeploymentPostprocessing = 0; 37 | }; 38 | /* End PBXCopyFilesBuildPhase section */ 39 | 40 | /* Begin PBXFileReference section */ 41 | 35B9D9282D3530D000BA0C85 /* UIViewController+Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewController+Helpers.swift"; sourceTree = ""; }; 42 | 35B9D92A2D3530F400BA0C85 /* UIWindow+Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIWindow+Helpers.swift"; sourceTree = ""; }; 43 | AA28A1AE1F91C84600B67E53 /* ProgressWebViewController.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = ProgressWebViewController.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 44 | AA28A1B11F91C84600B67E53 /* ProgressWebViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ProgressWebViewController.h; sourceTree = ""; }; 45 | AA28A1B21F91C84600B67E53 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 46 | AA28A1BD1F91C86700B67E53 /* ProgressWebViewControllerDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ProgressWebViewControllerDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 47 | AA28A1BF1F91C86700B67E53 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 48 | AA28A1C11F91C86700B67E53 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 49 | AA28A1C41F91C86700B67E53 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 50 | AA28A1C61F91C86700B67E53 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 51 | AA28A1C91F91C86700B67E53 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 52 | AA28A1CB1F91C86700B67E53 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 53 | AA28A1CF1F91C87D00B67E53 /* ProgressWebViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProgressWebViewController.swift; sourceTree = ""; }; 54 | AA28A1D21F91C89D00B67E53 /* WebKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WebKit.framework; path = System/Library/Frameworks/WebKit.framework; sourceTree = SDKROOT; }; 55 | AA28A1D71F91ED7C00B67E53 /* Enums.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Enums.swift; sourceTree = ""; }; 56 | AA28A1D91F91FF7A00B67E53 /* Media.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Media.xcassets; sourceTree = ""; }; 57 | /* End PBXFileReference section */ 58 | 59 | /* Begin PBXFrameworksBuildPhase section */ 60 | AA28A1AA1F91C84600B67E53 /* Frameworks */ = { 61 | isa = PBXFrameworksBuildPhase; 62 | buildActionMask = 2147483647; 63 | files = ( 64 | AA28A1D31F91C89E00B67E53 /* WebKit.framework in Frameworks */, 65 | ); 66 | runOnlyForDeploymentPostprocessing = 0; 67 | }; 68 | AA28A1BA1F91C86700B67E53 /* Frameworks */ = { 69 | isa = PBXFrameworksBuildPhase; 70 | buildActionMask = 2147483647; 71 | files = ( 72 | AA28A1D41F91C8A500B67E53 /* ProgressWebViewController.framework in Frameworks */, 73 | ); 74 | runOnlyForDeploymentPostprocessing = 0; 75 | }; 76 | /* End PBXFrameworksBuildPhase section */ 77 | 78 | /* Begin PBXGroup section */ 79 | 35B9D9272D3530AE00BA0C85 /* Extensions */ = { 80 | isa = PBXGroup; 81 | children = ( 82 | 35B9D9282D3530D000BA0C85 /* UIViewController+Helpers.swift */, 83 | 35B9D92A2D3530F400BA0C85 /* UIWindow+Helpers.swift */, 84 | ); 85 | path = Extensions; 86 | sourceTree = ""; 87 | }; 88 | AA28A1A41F91C84500B67E53 = { 89 | isa = PBXGroup; 90 | children = ( 91 | AA28A1B01F91C84600B67E53 /* ProgressWebViewController */, 92 | AA28A1BE1F91C86700B67E53 /* ProgressWebViewControllerDemo */, 93 | AA28A1AF1F91C84600B67E53 /* Products */, 94 | AA28A1D11F91C89D00B67E53 /* Frameworks */, 95 | ); 96 | sourceTree = ""; 97 | }; 98 | AA28A1AF1F91C84600B67E53 /* Products */ = { 99 | isa = PBXGroup; 100 | children = ( 101 | AA28A1AE1F91C84600B67E53 /* ProgressWebViewController.framework */, 102 | AA28A1BD1F91C86700B67E53 /* ProgressWebViewControllerDemo.app */, 103 | ); 104 | name = Products; 105 | sourceTree = ""; 106 | }; 107 | AA28A1B01F91C84600B67E53 /* ProgressWebViewController */ = { 108 | isa = PBXGroup; 109 | children = ( 110 | 35B9D9272D3530AE00BA0C85 /* Extensions */, 111 | AA28A1B11F91C84600B67E53 /* ProgressWebViewController.h */, 112 | AA28A1B21F91C84600B67E53 /* Info.plist */, 113 | AA28A1CF1F91C87D00B67E53 /* ProgressWebViewController.swift */, 114 | AA28A1D71F91ED7C00B67E53 /* Enums.swift */, 115 | AA28A1D91F91FF7A00B67E53 /* Media.xcassets */, 116 | ); 117 | path = ProgressWebViewController; 118 | sourceTree = ""; 119 | }; 120 | AA28A1BE1F91C86700B67E53 /* ProgressWebViewControllerDemo */ = { 121 | isa = PBXGroup; 122 | children = ( 123 | AA28A1BF1F91C86700B67E53 /* AppDelegate.swift */, 124 | AA28A1C11F91C86700B67E53 /* ViewController.swift */, 125 | AA28A1C31F91C86700B67E53 /* Main.storyboard */, 126 | AA28A1C61F91C86700B67E53 /* Assets.xcassets */, 127 | AA28A1C81F91C86700B67E53 /* LaunchScreen.storyboard */, 128 | AA28A1CB1F91C86700B67E53 /* Info.plist */, 129 | ); 130 | path = ProgressWebViewControllerDemo; 131 | sourceTree = ""; 132 | }; 133 | AA28A1D11F91C89D00B67E53 /* Frameworks */ = { 134 | isa = PBXGroup; 135 | children = ( 136 | AA28A1D21F91C89D00B67E53 /* WebKit.framework */, 137 | ); 138 | name = Frameworks; 139 | sourceTree = ""; 140 | }; 141 | /* End PBXGroup section */ 142 | 143 | /* Begin PBXHeadersBuildPhase section */ 144 | AA28A1AB1F91C84600B67E53 /* Headers */ = { 145 | isa = PBXHeadersBuildPhase; 146 | buildActionMask = 2147483647; 147 | files = ( 148 | AA28A1B31F91C84600B67E53 /* ProgressWebViewController.h in Headers */, 149 | ); 150 | runOnlyForDeploymentPostprocessing = 0; 151 | }; 152 | /* End PBXHeadersBuildPhase section */ 153 | 154 | /* Begin PBXNativeTarget section */ 155 | AA28A1AD1F91C84600B67E53 /* ProgressWebViewController */ = { 156 | isa = PBXNativeTarget; 157 | buildConfigurationList = AA28A1B61F91C84600B67E53 /* Build configuration list for PBXNativeTarget "ProgressWebViewController" */; 158 | buildPhases = ( 159 | AA28A1A91F91C84600B67E53 /* Sources */, 160 | AA28A1AA1F91C84600B67E53 /* Frameworks */, 161 | AA28A1AB1F91C84600B67E53 /* Headers */, 162 | AA28A1AC1F91C84600B67E53 /* Resources */, 163 | ); 164 | buildRules = ( 165 | ); 166 | dependencies = ( 167 | ); 168 | name = ProgressWebViewController; 169 | productName = ProgressWebViewController; 170 | productReference = AA28A1AE1F91C84600B67E53 /* ProgressWebViewController.framework */; 171 | productType = "com.apple.product-type.framework"; 172 | }; 173 | AA28A1BC1F91C86700B67E53 /* ProgressWebViewControllerDemo */ = { 174 | isa = PBXNativeTarget; 175 | buildConfigurationList = AA28A1CE1F91C86700B67E53 /* Build configuration list for PBXNativeTarget "ProgressWebViewControllerDemo" */; 176 | buildPhases = ( 177 | AA28A1B91F91C86700B67E53 /* Sources */, 178 | AA28A1BA1F91C86700B67E53 /* Frameworks */, 179 | AA28A1BB1F91C86700B67E53 /* Resources */, 180 | AA28A1D51F91E19800B67E53 /* Copy Frameworks */, 181 | ); 182 | buildRules = ( 183 | ); 184 | dependencies = ( 185 | ); 186 | name = ProgressWebViewControllerDemo; 187 | productName = ProgressWebViewControllerDemo; 188 | productReference = AA28A1BD1F91C86700B67E53 /* ProgressWebViewControllerDemo.app */; 189 | productType = "com.apple.product-type.application"; 190 | }; 191 | /* End PBXNativeTarget section */ 192 | 193 | /* Begin PBXProject section */ 194 | AA28A1A51F91C84500B67E53 /* Project object */ = { 195 | isa = PBXProject; 196 | attributes = { 197 | LastSwiftUpdateCheck = 0900; 198 | LastUpgradeCheck = 1240; 199 | ORGANIZATIONNAME = "Zheng-Xiang Ke"; 200 | TargetAttributes = { 201 | AA28A1AD1F91C84600B67E53 = { 202 | CreatedOnToolsVersion = 9.0; 203 | LastSwiftMigration = 0900; 204 | ProvisioningStyle = Automatic; 205 | }; 206 | AA28A1BC1F91C86700B67E53 = { 207 | CreatedOnToolsVersion = 9.0; 208 | ProvisioningStyle = Automatic; 209 | }; 210 | }; 211 | }; 212 | buildConfigurationList = AA28A1A81F91C84500B67E53 /* Build configuration list for PBXProject "ProgressWebViewController" */; 213 | compatibilityVersion = "Xcode 8.0"; 214 | developmentRegion = en; 215 | hasScannedForEncodings = 0; 216 | knownRegions = ( 217 | en, 218 | Base, 219 | ); 220 | mainGroup = AA28A1A41F91C84500B67E53; 221 | productRefGroup = AA28A1AF1F91C84600B67E53 /* Products */; 222 | projectDirPath = ""; 223 | projectRoot = ""; 224 | targets = ( 225 | AA28A1AD1F91C84600B67E53 /* ProgressWebViewController */, 226 | AA28A1BC1F91C86700B67E53 /* ProgressWebViewControllerDemo */, 227 | ); 228 | }; 229 | /* End PBXProject section */ 230 | 231 | /* Begin PBXResourcesBuildPhase section */ 232 | AA28A1AC1F91C84600B67E53 /* Resources */ = { 233 | isa = PBXResourcesBuildPhase; 234 | buildActionMask = 2147483647; 235 | files = ( 236 | AA28A1DA1F91FF7A00B67E53 /* Media.xcassets in Resources */, 237 | ); 238 | runOnlyForDeploymentPostprocessing = 0; 239 | }; 240 | AA28A1BB1F91C86700B67E53 /* Resources */ = { 241 | isa = PBXResourcesBuildPhase; 242 | buildActionMask = 2147483647; 243 | files = ( 244 | AA28A1CA1F91C86700B67E53 /* LaunchScreen.storyboard in Resources */, 245 | AA28A1C71F91C86700B67E53 /* Assets.xcassets in Resources */, 246 | AA28A1C51F91C86700B67E53 /* Main.storyboard in Resources */, 247 | ); 248 | runOnlyForDeploymentPostprocessing = 0; 249 | }; 250 | /* End PBXResourcesBuildPhase section */ 251 | 252 | /* Begin PBXSourcesBuildPhase section */ 253 | AA28A1A91F91C84600B67E53 /* Sources */ = { 254 | isa = PBXSourcesBuildPhase; 255 | buildActionMask = 2147483647; 256 | files = ( 257 | AA28A1D81F91ED7C00B67E53 /* Enums.swift in Sources */, 258 | 35B9D92B2D3530F400BA0C85 /* UIWindow+Helpers.swift in Sources */, 259 | 35B9D9292D3530D000BA0C85 /* UIViewController+Helpers.swift in Sources */, 260 | AA28A1D01F91C87D00B67E53 /* ProgressWebViewController.swift in Sources */, 261 | ); 262 | runOnlyForDeploymentPostprocessing = 0; 263 | }; 264 | AA28A1B91F91C86700B67E53 /* Sources */ = { 265 | isa = PBXSourcesBuildPhase; 266 | buildActionMask = 2147483647; 267 | files = ( 268 | AA28A1C21F91C86700B67E53 /* ViewController.swift in Sources */, 269 | AA28A1C01F91C86700B67E53 /* AppDelegate.swift in Sources */, 270 | ); 271 | runOnlyForDeploymentPostprocessing = 0; 272 | }; 273 | /* End PBXSourcesBuildPhase section */ 274 | 275 | /* Begin PBXVariantGroup section */ 276 | AA28A1C31F91C86700B67E53 /* Main.storyboard */ = { 277 | isa = PBXVariantGroup; 278 | children = ( 279 | AA28A1C41F91C86700B67E53 /* Base */, 280 | ); 281 | name = Main.storyboard; 282 | sourceTree = ""; 283 | }; 284 | AA28A1C81F91C86700B67E53 /* LaunchScreen.storyboard */ = { 285 | isa = PBXVariantGroup; 286 | children = ( 287 | AA28A1C91F91C86700B67E53 /* Base */, 288 | ); 289 | name = LaunchScreen.storyboard; 290 | sourceTree = ""; 291 | }; 292 | /* End PBXVariantGroup section */ 293 | 294 | /* Begin XCBuildConfiguration section */ 295 | AA28A1B41F91C84600B67E53 /* Debug */ = { 296 | isa = XCBuildConfiguration; 297 | buildSettings = { 298 | ALWAYS_SEARCH_USER_PATHS = NO; 299 | CLANG_ANALYZER_NONNULL = YES; 300 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 301 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 302 | CLANG_CXX_LIBRARY = "libc++"; 303 | CLANG_ENABLE_MODULES = YES; 304 | CLANG_ENABLE_OBJC_ARC = YES; 305 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 306 | CLANG_WARN_BOOL_CONVERSION = YES; 307 | CLANG_WARN_COMMA = YES; 308 | CLANG_WARN_CONSTANT_CONVERSION = YES; 309 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 310 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 311 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 312 | CLANG_WARN_EMPTY_BODY = YES; 313 | CLANG_WARN_ENUM_CONVERSION = YES; 314 | CLANG_WARN_INFINITE_RECURSION = YES; 315 | CLANG_WARN_INT_CONVERSION = YES; 316 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 317 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 318 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 319 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 320 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 321 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 322 | CLANG_WARN_STRICT_PROTOTYPES = YES; 323 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 324 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 325 | CLANG_WARN_UNREACHABLE_CODE = YES; 326 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 327 | CODE_SIGN_IDENTITY = "iPhone Developer"; 328 | COPY_PHASE_STRIP = NO; 329 | CURRENT_PROJECT_VERSION = 1; 330 | DEBUG_INFORMATION_FORMAT = dwarf; 331 | ENABLE_STRICT_OBJC_MSGSEND = YES; 332 | ENABLE_TESTABILITY = YES; 333 | GCC_C_LANGUAGE_STANDARD = gnu11; 334 | GCC_DYNAMIC_NO_PIC = NO; 335 | GCC_NO_COMMON_BLOCKS = YES; 336 | GCC_OPTIMIZATION_LEVEL = 0; 337 | GCC_PREPROCESSOR_DEFINITIONS = ( 338 | "DEBUG=1", 339 | "$(inherited)", 340 | ); 341 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 342 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 343 | GCC_WARN_UNDECLARED_SELECTOR = YES; 344 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 345 | GCC_WARN_UNUSED_FUNCTION = YES; 346 | GCC_WARN_UNUSED_VARIABLE = YES; 347 | IPHONEOS_DEPLOYMENT_TARGET = 13.0; 348 | MTL_ENABLE_DEBUG_INFO = YES; 349 | ONLY_ACTIVE_ARCH = YES; 350 | SDKROOT = iphoneos; 351 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 352 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 353 | VERSIONING_SYSTEM = "apple-generic"; 354 | VERSION_INFO_PREFIX = ""; 355 | }; 356 | name = Debug; 357 | }; 358 | AA28A1B51F91C84600B67E53 /* Release */ = { 359 | isa = XCBuildConfiguration; 360 | buildSettings = { 361 | ALWAYS_SEARCH_USER_PATHS = NO; 362 | CLANG_ANALYZER_NONNULL = YES; 363 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 364 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 365 | CLANG_CXX_LIBRARY = "libc++"; 366 | CLANG_ENABLE_MODULES = YES; 367 | CLANG_ENABLE_OBJC_ARC = YES; 368 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 369 | CLANG_WARN_BOOL_CONVERSION = YES; 370 | CLANG_WARN_COMMA = YES; 371 | CLANG_WARN_CONSTANT_CONVERSION = YES; 372 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 373 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 374 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 375 | CLANG_WARN_EMPTY_BODY = YES; 376 | CLANG_WARN_ENUM_CONVERSION = YES; 377 | CLANG_WARN_INFINITE_RECURSION = YES; 378 | CLANG_WARN_INT_CONVERSION = YES; 379 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 380 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 381 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 382 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 383 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 384 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 385 | CLANG_WARN_STRICT_PROTOTYPES = YES; 386 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 387 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 388 | CLANG_WARN_UNREACHABLE_CODE = YES; 389 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 390 | CODE_SIGN_IDENTITY = "iPhone Developer"; 391 | COPY_PHASE_STRIP = NO; 392 | CURRENT_PROJECT_VERSION = 1; 393 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 394 | ENABLE_NS_ASSERTIONS = NO; 395 | ENABLE_STRICT_OBJC_MSGSEND = YES; 396 | GCC_C_LANGUAGE_STANDARD = gnu11; 397 | GCC_NO_COMMON_BLOCKS = YES; 398 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 399 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 400 | GCC_WARN_UNDECLARED_SELECTOR = YES; 401 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 402 | GCC_WARN_UNUSED_FUNCTION = YES; 403 | GCC_WARN_UNUSED_VARIABLE = YES; 404 | IPHONEOS_DEPLOYMENT_TARGET = 13.0; 405 | MTL_ENABLE_DEBUG_INFO = NO; 406 | SDKROOT = iphoneos; 407 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 408 | VALIDATE_PRODUCT = YES; 409 | VERSIONING_SYSTEM = "apple-generic"; 410 | VERSION_INFO_PREFIX = ""; 411 | }; 412 | name = Release; 413 | }; 414 | AA28A1B71F91C84600B67E53 /* Debug */ = { 415 | isa = XCBuildConfiguration; 416 | buildSettings = { 417 | CLANG_ENABLE_MODULES = YES; 418 | CODE_SIGN_IDENTITY = ""; 419 | CODE_SIGN_STYLE = Automatic; 420 | DEFINES_MODULE = YES; 421 | DEVELOPMENT_TEAM = 5G9VJDYQ6D; 422 | DYLIB_COMPATIBILITY_VERSION = 1; 423 | DYLIB_CURRENT_VERSION = 1; 424 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 425 | INFOPLIST_FILE = ProgressWebViewController/Info.plist; 426 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 427 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 428 | PRODUCT_BUNDLE_IDENTIFIER = "Zheng-Xiang-Ke.ProgressWebViewController"; 429 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 430 | SKIP_INSTALL = YES; 431 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 432 | SWIFT_VERSION = 5.0; 433 | TARGETED_DEVICE_FAMILY = "1,2"; 434 | }; 435 | name = Debug; 436 | }; 437 | AA28A1B81F91C84600B67E53 /* Release */ = { 438 | isa = XCBuildConfiguration; 439 | buildSettings = { 440 | CLANG_ENABLE_MODULES = YES; 441 | CODE_SIGN_IDENTITY = ""; 442 | CODE_SIGN_STYLE = Automatic; 443 | DEFINES_MODULE = YES; 444 | DEVELOPMENT_TEAM = 5G9VJDYQ6D; 445 | DYLIB_COMPATIBILITY_VERSION = 1; 446 | DYLIB_CURRENT_VERSION = 1; 447 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 448 | INFOPLIST_FILE = ProgressWebViewController/Info.plist; 449 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 450 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 451 | PRODUCT_BUNDLE_IDENTIFIER = "Zheng-Xiang-Ke.ProgressWebViewController"; 452 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 453 | SKIP_INSTALL = YES; 454 | SWIFT_VERSION = 5.0; 455 | TARGETED_DEVICE_FAMILY = "1,2"; 456 | }; 457 | name = Release; 458 | }; 459 | AA28A1CC1F91C86700B67E53 /* Debug */ = { 460 | isa = XCBuildConfiguration; 461 | buildSettings = { 462 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 463 | CODE_SIGN_STYLE = Automatic; 464 | DEVELOPMENT_TEAM = 5G9VJDYQ6D; 465 | INFOPLIST_FILE = ProgressWebViewControllerDemo/Info.plist; 466 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 467 | PRODUCT_BUNDLE_IDENTIFIER = "Zheng-Xiang-Ke.ProgressWebViewControllerDemo"; 468 | PRODUCT_NAME = "$(TARGET_NAME)"; 469 | SWIFT_VERSION = 5.0; 470 | TARGETED_DEVICE_FAMILY = "1,2"; 471 | }; 472 | name = Debug; 473 | }; 474 | AA28A1CD1F91C86700B67E53 /* Release */ = { 475 | isa = XCBuildConfiguration; 476 | buildSettings = { 477 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 478 | CODE_SIGN_STYLE = Automatic; 479 | DEVELOPMENT_TEAM = 5G9VJDYQ6D; 480 | INFOPLIST_FILE = ProgressWebViewControllerDemo/Info.plist; 481 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 482 | PRODUCT_BUNDLE_IDENTIFIER = "Zheng-Xiang-Ke.ProgressWebViewControllerDemo"; 483 | PRODUCT_NAME = "$(TARGET_NAME)"; 484 | SWIFT_VERSION = 5.0; 485 | TARGETED_DEVICE_FAMILY = "1,2"; 486 | }; 487 | name = Release; 488 | }; 489 | /* End XCBuildConfiguration section */ 490 | 491 | /* Begin XCConfigurationList section */ 492 | AA28A1A81F91C84500B67E53 /* Build configuration list for PBXProject "ProgressWebViewController" */ = { 493 | isa = XCConfigurationList; 494 | buildConfigurations = ( 495 | AA28A1B41F91C84600B67E53 /* Debug */, 496 | AA28A1B51F91C84600B67E53 /* Release */, 497 | ); 498 | defaultConfigurationIsVisible = 0; 499 | defaultConfigurationName = Release; 500 | }; 501 | AA28A1B61F91C84600B67E53 /* Build configuration list for PBXNativeTarget "ProgressWebViewController" */ = { 502 | isa = XCConfigurationList; 503 | buildConfigurations = ( 504 | AA28A1B71F91C84600B67E53 /* Debug */, 505 | AA28A1B81F91C84600B67E53 /* Release */, 506 | ); 507 | defaultConfigurationIsVisible = 0; 508 | defaultConfigurationName = Release; 509 | }; 510 | AA28A1CE1F91C86700B67E53 /* Build configuration list for PBXNativeTarget "ProgressWebViewControllerDemo" */ = { 511 | isa = XCConfigurationList; 512 | buildConfigurations = ( 513 | AA28A1CC1F91C86700B67E53 /* Debug */, 514 | AA28A1CD1F91C86700B67E53 /* Release */, 515 | ); 516 | defaultConfigurationIsVisible = 0; 517 | defaultConfigurationName = Release; 518 | }; 519 | /* End XCConfigurationList section */ 520 | }; 521 | rootObject = AA28A1A51F91C84500B67E53 /* Project object */; 522 | } 523 | -------------------------------------------------------------------------------- /ProgressWebViewController.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ProgressWebViewController.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ProgressWebViewController.xcodeproj/xcshareddata/xcschemes/ProgressWebViewControllerDemo.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 42 | 44 | 50 | 51 | 52 | 53 | 59 | 61 | 67 | 68 | 69 | 70 | 72 | 73 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /ProgressWebViewController.xcodeproj/xcuserdata/Kf.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | ProgressWebViewController.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | ProgressWebViewController.xcscheme_^#shared#^_ 13 | 14 | orderHint 15 | 1 16 | 17 | ProgressWebViewControllerDemo.xcscheme 18 | 19 | orderHint 20 | 1 21 | 22 | ProgressWebViewControllerDemo.xcscheme_^#shared#^_ 23 | 24 | orderHint 25 | 0 26 | 27 | 28 | SuppressBuildableAutocreation 29 | 30 | AA28A1BC1F91C86700B67E53 31 | 32 | primary 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /ProgressWebViewController/Enums.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Enums.swift 3 | // ProgressWebViewController 4 | // 5 | // Created by Zheng-Xiang Ke on 2017/10/14. 6 | // Copyright © 2017年 Zheng-Xiang Ke. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public enum BarButtonItemType { 12 | case back 13 | case forward 14 | case reload 15 | case stop 16 | case activity 17 | case done 18 | case flexibleSpace 19 | } 20 | 21 | public enum NavigationBarPosition { 22 | case none 23 | case left 24 | case right 25 | } 26 | 27 | public enum NavigationWay { 28 | case browser 29 | case push(targetViewController: UIViewController?) 30 | } 31 | 32 | @objc public enum NavigationType: Int { 33 | case linkActivated = 0 34 | case formSubmitted = 1 35 | case backForward = 2 36 | case reload = 3 37 | case formResubmitted = 4 38 | case other = -1 39 | } 40 | -------------------------------------------------------------------------------- /ProgressWebViewController/Extensions/UIViewController+Helpers.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIViewController+Helpers.swift 3 | // ProgressWebViewController 4 | // 5 | // Created by Lei on 2025/1/13. 6 | // Copyright © 2025 Zheng-Xiang Ke. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension UIViewController { 12 | static var currentNavigationController: UINavigationController? { 13 | guard var currentViewController = UIWindow.key?.rootViewController else{ 14 | return nil 15 | } 16 | 17 | while let presentedViewController = currentViewController.presentedViewController { 18 | currentViewController = presentedViewController 19 | } 20 | 21 | if let tabBarController = currentViewController as? UITabBarController, let selectedViewController = tabBarController.selectedViewController { 22 | currentViewController = selectedViewController 23 | } 24 | return currentViewController as? UINavigationController 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /ProgressWebViewController/Extensions/UIWindow+Helpers.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIWindow+Helpers.swift 3 | // ProgressWebViewController 4 | // 5 | // Created by Lei on 2025/1/13. 6 | // Copyright © 2025 Zheng-Xiang Ke. All rights reserved. 7 | // 8 | 9 | extension UIWindow { 10 | static var key: UIWindow? { 11 | let allScenes = UIApplication.shared.connectedScenes 12 | for scene in allScenes { 13 | guard let windowScene = scene as? UIWindowScene else { continue } 14 | for window in windowScene.windows where window.isKeyWindow { 15 | return window 16 | } 17 | } 18 | return nil 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /ProgressWebViewController/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | NSPrincipalClass 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /ProgressWebViewController/Media.xcassets/Back.imageset/Back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kf99916/ProgressWebViewController/3656ef84132cf80dc2abeea830d0baf77f62d482/ProgressWebViewController/Media.xcassets/Back.imageset/Back.png -------------------------------------------------------------------------------- /ProgressWebViewController/Media.xcassets/Back.imageset/Back@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kf99916/ProgressWebViewController/3656ef84132cf80dc2abeea830d0baf77f62d482/ProgressWebViewController/Media.xcassets/Back.imageset/Back@2x.png -------------------------------------------------------------------------------- /ProgressWebViewController/Media.xcassets/Back.imageset/Back@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kf99916/ProgressWebViewController/3656ef84132cf80dc2abeea830d0baf77f62d482/ProgressWebViewController/Media.xcassets/Back.imageset/Back@3x.png -------------------------------------------------------------------------------- /ProgressWebViewController/Media.xcassets/Back.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Back.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "Back@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "Back@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | }, 23 | "properties" : { 24 | "template-rendering-intent" : "template" 25 | } 26 | } -------------------------------------------------------------------------------- /ProgressWebViewController/Media.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /ProgressWebViewController/Media.xcassets/Forward.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Forward.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "Forward@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "Forward@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | }, 23 | "properties" : { 24 | "template-rendering-intent" : "template" 25 | } 26 | } -------------------------------------------------------------------------------- /ProgressWebViewController/Media.xcassets/Forward.imageset/Forward.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kf99916/ProgressWebViewController/3656ef84132cf80dc2abeea830d0baf77f62d482/ProgressWebViewController/Media.xcassets/Forward.imageset/Forward.png -------------------------------------------------------------------------------- /ProgressWebViewController/Media.xcassets/Forward.imageset/Forward@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kf99916/ProgressWebViewController/3656ef84132cf80dc2abeea830d0baf77f62d482/ProgressWebViewController/Media.xcassets/Forward.imageset/Forward@2x.png -------------------------------------------------------------------------------- /ProgressWebViewController/Media.xcassets/Forward.imageset/Forward@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kf99916/ProgressWebViewController/3656ef84132cf80dc2abeea830d0baf77f62d482/ProgressWebViewController/Media.xcassets/Forward.imageset/Forward@3x.png -------------------------------------------------------------------------------- /ProgressWebViewController/ProgressWebViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ProgressWebViewController.h 3 | // ProgressWebViewController 4 | // 5 | // Created by Zheng-Xiang Ke on 2017/10/14. 6 | // Copyright © 2017年 Zheng-Xiang Ke. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for ProgressWebViewController. 12 | FOUNDATION_EXPORT double ProgressWebViewControllerVersionNumber; 13 | 14 | //! Project version string for ProgressWebViewController. 15 | FOUNDATION_EXPORT const unsigned char ProgressWebViewControllerVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /ProgressWebViewController/ProgressWebViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ProgressWebViewController.swift 3 | // ProgressWebViewController 4 | // 5 | // Created by Zheng-Xiang Ke on 2017/10/14. 6 | // Copyright © 2017年 Zheng-Xiang Ke. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import WebKit 11 | 12 | let estimatedProgressKeyPath = "estimatedProgress" 13 | let titleKeyPath = "title" 14 | let cookieKey = "Cookie" 15 | 16 | @objc public protocol ProgressWebViewControllerDelegate { 17 | @objc optional func progressWebViewController(_ controller: ProgressWebViewController, canDismiss url: URL) -> Bool 18 | @objc optional func progressWebViewController(_ controller: ProgressWebViewController, didStart url: URL) 19 | @objc optional func progressWebViewController(_ controller: ProgressWebViewController, didFinish url: URL) 20 | @objc optional func progressWebViewController(_ controller: ProgressWebViewController, didFail url: URL, withError error: Error) 21 | @objc optional func progressWebViewController(_ controller: ProgressWebViewController, decidePolicy url: URL, navigationType: NavigationType) -> Bool 22 | @objc optional func progressWebViewController(_ controller: ProgressWebViewController, decidePolicy url: URL, response: URLResponse) -> Bool 23 | @objc optional func progressWebViewController(_ controller: ProgressWebViewController, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? 24 | @objc optional func initPushedProgressWebViewController(defaultURL: URL) -> ProgressWebViewController 25 | } 26 | 27 | @objc public protocol ProgressWebViewControllerScrollViewDelegate { 28 | @objc optional func scrollViewDidScroll(_ scrollView: UIScrollView) 29 | } 30 | 31 | open class ProgressWebViewController: UIViewController { 32 | 33 | static let processPool = WKProcessPool() 34 | 35 | @available(*, unavailable, renamed: "defaultURL") 36 | open var url: URL? { return defaultURL } 37 | 38 | open var defaultURL: URL? 39 | open var bypassedSSLHosts: [String]? 40 | open var userAgent: String? 41 | open var disableZoom = false 42 | open var navigationWay = NavigationWay.browser 43 | open var pullToRefresh = false 44 | open var urlsHandledByApp = [ 45 | "hosts": ["itunes.apple.com"], 46 | "schemes": ["tel", "mailto", "sms"], 47 | "_blank": true 48 | ] as [String : Any] 49 | 50 | open var websiteDataStore: WKWebsiteDataStore = WKWebsiteDataStore.default() 51 | 52 | @available(iOS, obsoleted: 1.12.0, renamed: "defaultHeaders") 53 | open var headers: [String: String]? { return defaultHeaders } 54 | 55 | open var defaultHeaders: [String: String]? { 56 | didSet { 57 | var shouldReload = (defaultHeaders != nil && oldValue == nil) || (defaultHeaders == nil && oldValue != nil) 58 | if let defaultHeaders = defaultHeaders, let oldValue = oldValue, defaultHeaders != oldValue { 59 | shouldReload = true 60 | } 61 | if shouldReload { 62 | reload() 63 | } 64 | } 65 | } 66 | 67 | open var isScrollEnabled = true { 68 | didSet { 69 | webView?.scrollView.isScrollEnabled = isScrollEnabled 70 | } 71 | } 72 | 73 | open var currentURL: URL? { 74 | return webView?.url 75 | } 76 | 77 | weak open var delegate: ProgressWebViewControllerDelegate? 78 | weak open var scrollViewDelegate: ProgressWebViewControllerScrollViewDelegate? 79 | 80 | open var tintColor: UIColor? 81 | open var websiteTitleInNavigationBar = true 82 | open var doneBarButtonItemPosition: NavigationBarPosition = .right 83 | open var leftNavigaionBarItemTypes: [BarButtonItemType] = [] 84 | open var rightNavigaionBarItemTypes: [BarButtonItemType] = [] 85 | open var toolbarItemTypes: [BarButtonItemType] = [.back, .forward, .reload, .activity] 86 | 87 | fileprivate var webView: WKWebView? 88 | 89 | fileprivate var previousNavigationBarState: (tintColor: UIColor, hidden: Bool)? = nil 90 | fileprivate var previousToolbarState: (tintColor: UIColor, hidden: Bool)? = nil 91 | 92 | fileprivate var scrollToRefresh = false 93 | fileprivate var lastTapPosition = CGPoint(x: 0, y: 0) 94 | fileprivate var isReloadWhenAppear = false 95 | fileprivate var actionPolicy: WKNavigationActionPolicy = .allow 96 | fileprivate var estimatedProgress = 0.0 { 97 | didSet { 98 | if currentNavigationController?.isNavigationBarHidden ?? true, activityIndicatorView.isDescendant(of: view) { 99 | if estimatedProgress >= 1.0 { 100 | activityIndicatorView.stopAnimating() 101 | } else { 102 | activityIndicatorView.startAnimating() 103 | } 104 | } 105 | else if let navigationItem = currentNavigationController?.navigationBar, progressView.isDescendant(of: navigationItem) { 106 | progressView.alpha = 1 107 | progressView.setProgress(Float(estimatedProgress), animated: true) 108 | 109 | if estimatedProgress >= 1.0 { 110 | UIView.animate(withDuration: 0.3, delay: 0.3, options: .curveEaseOut, animations: { 111 | self.progressView.alpha = 0 112 | }, completion: { 113 | finished in 114 | self.progressView.setProgress(0, animated: false) 115 | }) 116 | } 117 | } 118 | } 119 | } 120 | 121 | lazy fileprivate var progressView: UIProgressView = { 122 | let progressView = UIProgressView(progressViewStyle: .default) 123 | progressView.alpha = 1 124 | progressView.trackTintColor = UIColor(white: 1, alpha: 0) 125 | return progressView 126 | }() 127 | 128 | lazy fileprivate var activityIndicatorView: UIActivityIndicatorView = { 129 | if #available(iOS 13.0, *) { 130 | let activityIndicatorView = UIActivityIndicatorView(style: .medium) 131 | activityIndicatorView.color = tintColor ?? .label 132 | return activityIndicatorView 133 | } else { 134 | let activityIndicatorView = UIActivityIndicatorView(style: .gray) 135 | activityIndicatorView.color = tintColor ?? .darkText 136 | return activityIndicatorView 137 | } 138 | }() 139 | 140 | lazy fileprivate var refreshControl: UIRefreshControl = { 141 | let refreshControl = UIRefreshControl() 142 | refreshControl.addTarget(self, action: #selector(refreshWebView(sender:)), for: UIControl.Event.valueChanged) 143 | webView?.scrollView.addSubview(refreshControl) 144 | webView?.scrollView.bounces = true 145 | return refreshControl 146 | }() 147 | 148 | lazy fileprivate var backBarButtonItem: UIBarButtonItem = { 149 | let bundle = Bundle(for: ProgressWebViewController.self) 150 | return UIBarButtonItem(image: UIImage(named: "Back", in: bundle, compatibleWith: nil), style: .plain, target: self, action: #selector(backDidClick(sender:))) 151 | }() 152 | 153 | lazy fileprivate var forwardBarButtonItem: UIBarButtonItem = { 154 | let bundle = Bundle(for: ProgressWebViewController.self) 155 | return UIBarButtonItem(image: UIImage(named: "Forward", in: bundle, compatibleWith: nil), style: .plain, target: self, action: #selector(forwardDidClick(sender:))) 156 | }() 157 | 158 | lazy fileprivate var reloadBarButtonItem: UIBarButtonItem = { 159 | return UIBarButtonItem(barButtonSystemItem: .refresh, target: self, action: #selector(reloadDidClick(sender:))) 160 | }() 161 | 162 | lazy fileprivate var stopBarButtonItem: UIBarButtonItem = { 163 | return UIBarButtonItem(barButtonSystemItem: .stop, target: self, action: #selector(stopDidClick(sender:))) 164 | }() 165 | 166 | lazy fileprivate var activityBarButtonItem: UIBarButtonItem = { 167 | return UIBarButtonItem(barButtonSystemItem: .action, target: self, action: #selector(activityDidClick(sender:))) 168 | }() 169 | 170 | lazy fileprivate var doneBarButtonItem: UIBarButtonItem = { 171 | return UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(doneDidClick(sender:))) 172 | }() 173 | 174 | lazy fileprivate var flexibleSpaceBarButtonItem: UIBarButtonItem = { 175 | return UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil) 176 | }() 177 | 178 | public convenience init(_ progressWebViewController: ProgressWebViewController) { 179 | self.init() 180 | self.websiteDataStore = progressWebViewController.websiteDataStore 181 | self.bypassedSSLHosts = progressWebViewController.bypassedSSLHosts 182 | self.userAgent = progressWebViewController.userAgent 183 | self.disableZoom = progressWebViewController.disableZoom 184 | self.navigationWay = progressWebViewController.navigationWay 185 | self.pullToRefresh = progressWebViewController.pullToRefresh 186 | self.urlsHandledByApp = progressWebViewController.urlsHandledByApp 187 | self.defaultHeaders = progressWebViewController.defaultHeaders 188 | self.tintColor = progressWebViewController.tintColor 189 | self.websiteTitleInNavigationBar = progressWebViewController.websiteTitleInNavigationBar 190 | self.doneBarButtonItemPosition = progressWebViewController.doneBarButtonItemPosition 191 | self.leftNavigaionBarItemTypes = progressWebViewController.leftNavigaionBarItemTypes 192 | self.rightNavigaionBarItemTypes = progressWebViewController.rightNavigaionBarItemTypes 193 | self.toolbarItemTypes = progressWebViewController.toolbarItemTypes 194 | self.delegate = progressWebViewController.delegate 195 | } 196 | 197 | deinit { 198 | webView?.removeObserver(self, forKeyPath: estimatedProgressKeyPath) 199 | webView?.removeObserver(self, forKeyPath: titleKeyPath) 200 | 201 | webView?.uiDelegate = nil 202 | webView?.navigationDelegate = nil 203 | webView?.scrollView.delegate = nil 204 | } 205 | 206 | override open func loadView() { 207 | let webConfiguration = WKWebViewConfiguration() 208 | webConfiguration.allowsInlineMediaPlayback = true 209 | webConfiguration.websiteDataStore = websiteDataStore 210 | webConfiguration.processPool = ProgressWebViewController.processPool 211 | let webView = createWebView(webConfiguration: webConfiguration) 212 | 213 | view = webView 214 | self.webView = webView 215 | #if DEBUG 216 | if #available(iOS 16.4, *) { 217 | self.webView?.isInspectable = true 218 | } 219 | #endif 220 | } 221 | 222 | override open func viewDidLoad() { 223 | super.viewDidLoad() 224 | 225 | // Do any additional setup after loading the view. 226 | navigationItem.title = navigationItem.title ?? defaultURL?.absoluteString 227 | 228 | let tapGesture = UITapGestureRecognizer(target: self, action: #selector(webViewDidTap(sender:))) 229 | tapGesture.delegate = self 230 | webView?.addGestureRecognizer(tapGesture) 231 | 232 | updateProgressViewFrame() 233 | addBarButtonItems() 234 | 235 | if let userAgent = userAgent { 236 | webView?.evaluateJavaScript("navigator.userAgent") { [weak self] result, error in 237 | guard let weakSelf = self else { 238 | return 239 | } 240 | 241 | defer { 242 | if let url = weakSelf.defaultURL { 243 | weakSelf.load(url) 244 | } 245 | } 246 | 247 | guard error == nil, let originalUserAgent = result as? String else { 248 | weakSelf.webView?.customUserAgent = userAgent 249 | return 250 | } 251 | 252 | weakSelf.webView?.customUserAgent = String(format: "%@ %@", originalUserAgent, userAgent) 253 | } 254 | } 255 | else if let url = defaultURL { 256 | load(url) 257 | } 258 | } 259 | 260 | override open func viewWillAppear(_ animated: Bool) { 261 | super.viewWillAppear(animated) 262 | 263 | setUpState() 264 | if isReloadWhenAppear { 265 | reload() 266 | } 267 | } 268 | 269 | override open func viewWillDisappear(_ animated: Bool) { 270 | super.viewWillDisappear(animated) 271 | 272 | if estimatedProgress < 1 { 273 | isReloadWhenAppear = actionPolicy == .allow 274 | if isReloadWhenAppear { 275 | webView?.stopLoading() 276 | } 277 | } 278 | rollbackState(animated) 279 | } 280 | 281 | override open func didReceiveMemoryWarning() { 282 | super.didReceiveMemoryWarning() 283 | // Dispose of any resources that can be recreated. 284 | } 285 | 286 | override open func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { 287 | switch keyPath { 288 | case estimatedProgressKeyPath?: 289 | guard let estimatedProgress = webView?.estimatedProgress else { 290 | return 291 | } 292 | self.estimatedProgress = estimatedProgress 293 | case titleKeyPath?: 294 | if websiteTitleInNavigationBar || URL(string: navigationItem.title ?? "")?.appendingPathComponent("") == currentURL?.appendingPathComponent("") { 295 | navigationItem.title = webView?.title 296 | } 297 | default: 298 | super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context) 299 | } 300 | } 301 | 302 | // Reference: https://medium.com/swlh/popover-menu-over-cards-containing-webkit-views-on-ios-13-a16705aff8af 303 | // https://stackoverflow.com/questions/58164583/wkwebview-with-the-new-ios13-modal-crash-when-a-file-picker-is-invoked 304 | override open func present(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)? = nil) { 305 | setUIDocumentMenuViewControllerSoureViewsIfNeeded(viewControllerToPresent) 306 | super.present(viewControllerToPresent, animated: flag, completion: completion) 307 | } 308 | 309 | override open func targetViewController(forAction action: Selector, sender: Any?) -> UIViewController? { 310 | switch navigationWay { 311 | case .browser: 312 | return currentNavigationController 313 | case .push(let targetViewController): 314 | return targetViewController ?? currentNavigationController 315 | } 316 | } 317 | } 318 | 319 | // MARK: - Public Methods 320 | public extension ProgressWebViewController { 321 | func load(_ url: URL) { 322 | isReloadWhenAppear = false 323 | if isViewLoaded { 324 | let request = createRequest(url: url) 325 | DispatchQueue.main.async { 326 | self.webView?.stopLoading() 327 | self.webView?.load(request) 328 | } 329 | } 330 | else { 331 | defaultURL = url 332 | } 333 | } 334 | 335 | func load(htmlString: String, baseURL: URL?) { 336 | isReloadWhenAppear = false 337 | DispatchQueue.main.async { 338 | self.webView?.stopLoading() 339 | self.webView?.loadHTMLString(htmlString, baseURL: baseURL) 340 | } 341 | } 342 | 343 | func goBackToFirstPage() { 344 | if let firstPageItem = webView?.backForwardList.backList.first { 345 | webView?.go(to: firstPageItem) 346 | } 347 | } 348 | 349 | func scrollToTop(animated: Bool, refresh: Bool = false) { 350 | guard isScrollEnabled else { 351 | if refresh { 352 | refreshWebView(sender: refreshControl) 353 | } 354 | return 355 | } 356 | 357 | var offsetY: CGFloat = 0 358 | if let currentNavigationController = currentNavigationController { 359 | offsetY -= currentNavigationController.navigationBar.frame.size.height + (view.window?.windowScene?.statusBarManager?.statusBarFrame.height ?? 0) 360 | } 361 | if refresh, pullToRefresh { 362 | offsetY -= refreshControl.frame.size.height 363 | } 364 | 365 | scrollToRefresh = refresh 366 | webView?.scrollView.setContentOffset(CGPoint(x: 0, y: offsetY), animated: animated) 367 | } 368 | 369 | func isScrollToTop() -> Bool { 370 | guard isScrollEnabled else { 371 | return true 372 | } 373 | guard let scrollView = webView?.scrollView else { 374 | return false 375 | } 376 | return scrollView.contentOffset.y <= CGFloat(0) 377 | } 378 | 379 | func clearCache(completionHandler: @escaping () -> Void) { 380 | var websiteDataTypes = Set([WKWebsiteDataTypeDiskCache, WKWebsiteDataTypeMemoryCache, WKWebsiteDataTypeOfflineWebApplicationCache]) 381 | if #available(iOS 11.3, *) { 382 | websiteDataTypes.insert(WKWebsiteDataTypeFetchCache) 383 | } 384 | WKWebsiteDataStore.default().removeData(ofTypes: websiteDataTypes, modifiedSince: Date(timeIntervalSince1970: 0), completionHandler: completionHandler) 385 | } 386 | 387 | func evaluateJavaScript(_ javaScriptString: String, completionHandler: ((Any?, Error?) -> Void)? = nil) { 388 | webView?.evaluateJavaScript(javaScriptString, completionHandler: completionHandler) 389 | } 390 | 391 | func loadBlankPage() { 392 | guard let url = URL(string:"about:blank") else { 393 | return 394 | } 395 | load(url) 396 | } 397 | 398 | func setUIDocumentMenuViewControllerSoureViewsIfNeeded(_ viewControllerToPresent: UIViewController) { 399 | viewControllerToPresent.popoverPresentationController?.sourceView = view 400 | if lastTapPosition == .zero { 401 | lastTapPosition = view.center 402 | } 403 | viewControllerToPresent.popoverPresentationController?.sourceRect = CGRect(origin: lastTapPosition, size: CGSize(width: 0, height: 0)) 404 | } 405 | 406 | func reload() { 407 | webView?.stopLoading() 408 | isReloadWhenAppear = false 409 | if let url = currentURL, !isBlank(url:url) { 410 | webView?.reload() 411 | } 412 | else if let url = defaultURL { 413 | load(url) 414 | } 415 | } 416 | 417 | func updateHttpCookies(cookies: [HTTPCookie]) async { 418 | let currentCookies = await webView?.configuration.websiteDataStore.httpCookieStore.allCookies() 419 | var shouldReload = false 420 | for cookie in cookies { 421 | if !(currentCookies?.contains(cookie) ?? false) { 422 | shouldReload = true 423 | await webView?.configuration.websiteDataStore.httpCookieStore.setCookie(cookie) 424 | } 425 | } 426 | if shouldReload { 427 | reload() 428 | } 429 | } 430 | 431 | func pushWebViewController(defaultURL: URL) { 432 | let progressWebViewController = delegate?.initPushedProgressWebViewController?(defaultURL: defaultURL) ?? ProgressWebViewController(self) 433 | progressWebViewController.defaultURL = defaultURL 434 | currentNavigationController?.show(progressWebViewController, sender: self) 435 | setUpState() 436 | } 437 | 438 | @available(*, unavailable, renamed: "pushWebViewController(defaultURL:)") 439 | func pushWebViewController(url: URL) { } 440 | } 441 | 442 | // MARK: - Fileprivate Methods 443 | fileprivate extension ProgressWebViewController { 444 | var currentNavigationController: UINavigationController? { 445 | return navigationController ?? parent?.navigationController ?? parent?.presentingViewController?.navigationController ?? UIViewController.currentNavigationController 446 | } 447 | 448 | func createWebView(webConfiguration: WKWebViewConfiguration) -> WKWebView { 449 | let webView = WKWebView(frame: .zero, configuration: webConfiguration) 450 | if #available(iOS 13.0, *) { 451 | webView.backgroundColor = .systemBackground 452 | } 453 | 454 | webView.uiDelegate = self 455 | webView.navigationDelegate = self 456 | webView.scrollView.delegate = self 457 | 458 | webView.allowsBackForwardNavigationGestures = true 459 | webView.isMultipleTouchEnabled = true 460 | 461 | webView.addObserver(self, forKeyPath: estimatedProgressKeyPath, options: .new, context: nil) 462 | webView.addObserver(self, forKeyPath: titleKeyPath, options: .new, context: nil) 463 | 464 | webView.scrollView.isScrollEnabled = isScrollEnabled 465 | return webView 466 | } 467 | 468 | func createRequest(url: URL) -> URLRequest { 469 | var request = URLRequest(url: url) 470 | 471 | // Set up Headers 472 | if let defaultHeaders = defaultHeaders { 473 | for (field, value) in defaultHeaders { 474 | request.addValue(value, forHTTPHeaderField: field) 475 | } 476 | } 477 | return request 478 | } 479 | 480 | func updateProgressViewFrame() { 481 | guard let navigationBar = currentNavigationController?.navigationBar, progressView.isDescendant(of: navigationBar) else { 482 | return 483 | } 484 | progressView.frame = CGRect(x: 0, y: navigationBar.frame.size.height - progressView.frame.size.height, width: navigationBar.frame.size.width, height: progressView.frame.size.height) 485 | } 486 | 487 | func addBarButtonItems() { 488 | let barButtonItems: [BarButtonItemType: UIBarButtonItem] = [ 489 | .back: backBarButtonItem, 490 | .forward: forwardBarButtonItem, 491 | .reload: reloadBarButtonItem, 492 | .stop: stopBarButtonItem, 493 | .activity: activityBarButtonItem, 494 | .done: doneBarButtonItem, 495 | .flexibleSpace: flexibleSpaceBarButtonItem 496 | ] 497 | 498 | if presentingViewController != nil { 499 | switch doneBarButtonItemPosition { 500 | case .left: 501 | if !leftNavigaionBarItemTypes.contains(.done) { 502 | leftNavigaionBarItemTypes.insert(.done, at: 0) 503 | } 504 | case .right: 505 | if !rightNavigaionBarItemTypes.contains(.done) { 506 | rightNavigaionBarItemTypes.insert(.done, at: 0) 507 | } 508 | case .none: 509 | break 510 | } 511 | } 512 | 513 | navigationItem.leftBarButtonItems = navigationItem.leftBarButtonItems ?? [] + leftNavigaionBarItemTypes.map { 514 | barButtonItemType in 515 | if let barButtonItem = barButtonItems[barButtonItemType] { 516 | return barButtonItem 517 | } 518 | return UIBarButtonItem() 519 | } 520 | 521 | navigationItem.rightBarButtonItems = navigationItem.rightBarButtonItems ?? [] + rightNavigaionBarItemTypes.map { 522 | barButtonItemType in 523 | if let barButtonItem = barButtonItems[barButtonItemType] { 524 | return barButtonItem 525 | } 526 | return UIBarButtonItem() 527 | } 528 | 529 | if toolbarItemTypes.count > 0 { 530 | for index in 0.. UIBarButtonItem in 537 | if let barButtonItem = barButtonItems[barButtonItemType] { 538 | return barButtonItem 539 | } 540 | return UIBarButtonItem() 541 | }, animated: true) 542 | } 543 | 544 | func updateBarButtonItems() { 545 | backBarButtonItem.isEnabled = webView?.canGoBack ?? false 546 | forwardBarButtonItem.isEnabled = webView?.canGoForward ?? false 547 | 548 | let updateReloadBarButtonItem: (UIBarButtonItem, Bool) -> UIBarButtonItem = { 549 | [weak self] barButtonItem, isLoading in 550 | guard let weakSelf = self else { 551 | return barButtonItem 552 | } 553 | switch barButtonItem { 554 | case weakSelf.reloadBarButtonItem: 555 | fallthrough 556 | case weakSelf.stopBarButtonItem: 557 | return isLoading ? weakSelf.stopBarButtonItem : weakSelf.reloadBarButtonItem 558 | default: 559 | break 560 | } 561 | return barButtonItem 562 | } 563 | 564 | let isLoading = webView?.isLoading ?? false 565 | toolbarItems = toolbarItems?.map { 566 | barButtonItem -> UIBarButtonItem in 567 | return updateReloadBarButtonItem(barButtonItem, isLoading) 568 | } 569 | 570 | navigationItem.leftBarButtonItems = navigationItem.leftBarButtonItems?.map { 571 | barButtonItem -> UIBarButtonItem in 572 | return updateReloadBarButtonItem(barButtonItem, isLoading) 573 | } 574 | 575 | navigationItem.rightBarButtonItems = navigationItem.rightBarButtonItems?.map { 576 | barButtonItem -> UIBarButtonItem in 577 | return updateReloadBarButtonItem(barButtonItem, isLoading) 578 | } 579 | } 580 | 581 | func setUpState() { 582 | if let currentNavigationController = currentNavigationController { 583 | previousNavigationBarState = (currentNavigationController.navigationBar.tintColor, currentNavigationController.isNavigationBarHidden) 584 | previousToolbarState = (currentNavigationController.toolbar.tintColor, currentNavigationController.isToolbarHidden) 585 | } 586 | 587 | if let tintColor = tintColor { 588 | progressView.progressTintColor = tintColor 589 | currentNavigationController?.navigationBar.tintColor = tintColor 590 | currentNavigationController?.toolbar.tintColor = tintColor 591 | } 592 | 593 | if currentNavigationController?.isNavigationBarHidden ?? true, !activityIndicatorView.isDescendant(of: view) { 594 | activityIndicatorView.center = view.center 595 | view.addSubview(activityIndicatorView) 596 | activityIndicatorView.startAnimating() 597 | } 598 | else if let navigationBar = currentNavigationController?.navigationBar, !progressView.isDescendant(of: navigationBar) { 599 | navigationBar.addSubview(progressView) 600 | } 601 | } 602 | 603 | func rollbackState(_ animated: Bool) { 604 | progressView.removeFromSuperview() 605 | 606 | if let previousNavigationBarState = previousNavigationBarState { 607 | currentNavigationController?.navigationBar.tintColor = previousNavigationBarState.tintColor 608 | currentNavigationController?.setNavigationBarHidden(previousNavigationBarState.hidden, animated: animated) 609 | } 610 | if let previousToolbarState = previousToolbarState { 611 | currentNavigationController?.toolbar.tintColor = previousToolbarState.tintColor 612 | currentNavigationController?.setToolbarHidden(previousToolbarState.hidden, animated: animated) 613 | } 614 | } 615 | 616 | func openURLWithApp(_ url: URL) -> Bool { 617 | let application = UIApplication.shared 618 | if application.canOpenURL(url) { 619 | application.open(url) 620 | return true 621 | } 622 | 623 | return false 624 | } 625 | 626 | func handleURLWithApp(_ url: URL, targetFrame: WKFrameInfo?) -> Bool { 627 | let hosts = urlsHandledByApp["hosts"] as? [String] 628 | let schemes = urlsHandledByApp["schemes"] as? [String] 629 | let blank = urlsHandledByApp["_blank"] as? Bool 630 | 631 | var tryToOpenURLWithApp = false 632 | if let host = url.host, hosts?.contains(host) ?? false { 633 | tryToOpenURLWithApp = true 634 | } 635 | if let scheme = url.scheme, schemes?.contains(scheme) ?? false { 636 | tryToOpenURLWithApp = true 637 | } 638 | if blank ?? false && targetFrame == nil { 639 | tryToOpenURLWithApp = true 640 | } 641 | 642 | return tryToOpenURLWithApp ? openURLWithApp(url) : false 643 | } 644 | 645 | func isBlank(url: URL) -> Bool { 646 | return url.absoluteString == "about:blank" 647 | } 648 | } 649 | 650 | // MARK: - WKUIDelegate 651 | extension ProgressWebViewController: WKUIDelegate { 652 | public func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? { 653 | if let createWebViewWithConfiguartion = delegate?.progressWebViewController(_:createWebViewWith:for:windowFeatures:) { 654 | return createWebViewWithConfiguartion(self, configuration, navigationAction, windowFeatures) 655 | } 656 | else if !(navigationAction.targetFrame?.isMainFrame ?? false) { 657 | webView.load(navigationAction.request) 658 | } 659 | return nil 660 | } 661 | 662 | public func webView(_ webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping () -> Void) { 663 | let alertController = UIAlertController(title: nil, message: message, preferredStyle: .alert) 664 | 665 | alertController.addAction(UIAlertAction(title: "Ok", style: .default, handler: { (action) in 666 | completionHandler() 667 | })) 668 | 669 | present(alertController, animated: true, completion: nil) 670 | } 671 | 672 | public func webView(_ webView: WKWebView, runJavaScriptConfirmPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (Bool) -> Void) { 673 | let alertController = UIAlertController(title: nil, message: message, preferredStyle: .alert) 674 | 675 | alertController.addAction(UIAlertAction(title: "Ok", style: .default, handler: { (action) in 676 | completionHandler(true) 677 | })) 678 | let cancelAction = UIAlertAction(title: "Cancel", style: .default, handler: { (action) in 679 | completionHandler(false) 680 | }) 681 | alertController.addAction(cancelAction) 682 | alertController.preferredAction = cancelAction 683 | 684 | present(alertController, animated: true, completion: nil) 685 | } 686 | 687 | public func webView(_ webView: WKWebView, runJavaScriptTextInputPanelWithPrompt prompt: String, defaultText: String?, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (String?) -> Void) { 688 | let alertController = UIAlertController(title: nil, message: prompt, preferredStyle: .alert) 689 | 690 | alertController.addTextField { (textField) in 691 | textField.text = defaultText 692 | } 693 | let okAction = UIAlertAction(title: "Ok", style: .default, handler: { (action) in 694 | if let text = alertController.textFields?.first?.text { 695 | completionHandler(text) 696 | } else { 697 | completionHandler(defaultText) 698 | } 699 | 700 | }) 701 | alertController.addAction(okAction) 702 | alertController.addAction(UIAlertAction(title: "Cancel", style: .default, handler: { (action) in 703 | completionHandler(nil) 704 | })) 705 | alertController.preferredAction = okAction 706 | 707 | present(alertController, animated: true, completion: nil) 708 | } 709 | } 710 | 711 | // MARK: - WKNavigationDelegate 712 | extension ProgressWebViewController: WKNavigationDelegate { 713 | public func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) { 714 | updateBarButtonItems() 715 | updateProgressViewFrame() 716 | if let url = webView.url ?? defaultURL { 717 | delegate?.progressWebViewController?(self, didStart: url) 718 | } 719 | } 720 | public func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { 721 | updateBarButtonItems() 722 | updateProgressViewFrame() 723 | if pullToRefresh { 724 | refreshControl.endRefreshing() 725 | } 726 | if let url = webView.url ?? defaultURL { 727 | delegate?.progressWebViewController?(self, didFinish: url) 728 | } 729 | } 730 | 731 | public func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) { 732 | updateBarButtonItems() 733 | updateProgressViewFrame() 734 | if pullToRefresh { 735 | refreshControl.endRefreshing() 736 | } 737 | if let url = webView.url ?? defaultURL { 738 | delegate?.progressWebViewController?(self, didFail: url, withError: error) 739 | } 740 | } 741 | 742 | public func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) { 743 | updateBarButtonItems() 744 | updateProgressViewFrame() 745 | if pullToRefresh { 746 | refreshControl.endRefreshing() 747 | } 748 | if let url = webView.url ?? defaultURL { 749 | delegate?.progressWebViewController?(self, didFail: url, withError: error) 750 | } 751 | } 752 | 753 | public func webView(_ webView: WKWebView, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) { 754 | if let bypassedSSLHosts = bypassedSSLHosts, bypassedSSLHosts.contains(challenge.protectionSpace.host) { 755 | let credential = URLCredential(trust: challenge.protectionSpace.serverTrust!) 756 | completionHandler(.useCredential, credential) 757 | } else { 758 | completionHandler(.performDefaultHandling, nil) 759 | } 760 | } 761 | 762 | public func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { 763 | actionPolicy = .allow 764 | defer { 765 | decisionHandler(actionPolicy) 766 | } 767 | guard let url = navigationAction.request.url, !url.isFileURL else { 768 | return 769 | } 770 | 771 | if let targetFrame = navigationAction.targetFrame, !targetFrame.isMainFrame { 772 | return 773 | } 774 | 775 | if handleURLWithApp(url, targetFrame: navigationAction.targetFrame) { 776 | actionPolicy = .cancel 777 | return 778 | } 779 | 780 | if let navigationType = NavigationType(rawValue: navigationAction.navigationType.rawValue), let result = delegate?.progressWebViewController?(self, decidePolicy: url, navigationType: navigationType) { 781 | actionPolicy = result ? .allow : .cancel 782 | if actionPolicy == .cancel { 783 | return 784 | } 785 | } 786 | 787 | switch navigationAction.navigationType { 788 | case .linkActivated: 789 | if let fragment = url.fragment { 790 | let removedFramgnetURL = URL(string: url.absoluteString.replacingOccurrences(of: "#\(fragment)", with: "")) 791 | var currentURL = currentURL 792 | if let currentFragment = currentURL?.fragment { 793 | currentURL = URL(string: url.absoluteString.replacingOccurrences(of: "#\(currentFragment)", with: "")) 794 | } 795 | if removedFramgnetURL == currentURL { 796 | return 797 | } 798 | } 799 | if case .push = navigationWay { 800 | pushWebViewController(defaultURL: url) 801 | actionPolicy = .cancel 802 | return 803 | } 804 | if navigationAction.targetFrame == nil { 805 | pushWebViewController(defaultURL: url) 806 | actionPolicy = .cancel 807 | } 808 | default: 809 | break 810 | } 811 | } 812 | 813 | public func webView(_ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse, decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void) { 814 | var responsePolicy: WKNavigationResponsePolicy = .allow 815 | defer { 816 | decisionHandler(responsePolicy) 817 | } 818 | guard let url = navigationResponse.response.url, !url.isFileURL else { 819 | return 820 | } 821 | 822 | if let result = delegate?.progressWebViewController?(self, decidePolicy: url, response: navigationResponse.response) { 823 | responsePolicy = result ? .allow : .cancel 824 | } 825 | 826 | if case .push = navigationWay, responsePolicy == .cancel, let webViewController = currentNavigationController?.topViewController as? ProgressWebViewController, webViewController.currentURL?.appendingPathComponent("") == url.appendingPathComponent("") { 827 | currentNavigationController?.popViewController(animated: true) 828 | } 829 | } 830 | 831 | public func webViewWebContentProcessDidTerminate(_ webView: WKWebView) { 832 | if viewIfLoaded?.window != nil { 833 | reload() 834 | } 835 | else { 836 | isReloadWhenAppear = true 837 | } 838 | } 839 | } 840 | 841 | extension ProgressWebViewController: UIScrollViewDelegate { 842 | public func viewForZooming(in scrollView: UIScrollView) -> UIView? { 843 | return disableZoom ? nil : scrollView.subviews[0] 844 | } 845 | 846 | public func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) { 847 | if scrollToRefresh, pullToRefresh { 848 | refreshWebView(sender: refreshControl) 849 | } 850 | scrollToRefresh = false 851 | } 852 | 853 | public func scrollViewDidScroll(_ scrollView: UIScrollView) { 854 | scrollViewDelegate?.scrollViewDidScroll?(scrollView) 855 | } 856 | } 857 | 858 | extension ProgressWebViewController: UIGestureRecognizerDelegate { 859 | public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { 860 | return true 861 | } 862 | } 863 | 864 | // MARK: - @objc 865 | @objc extension ProgressWebViewController { 866 | func backDidClick(sender: AnyObject) { 867 | webView?.goBack() 868 | } 869 | 870 | func forwardDidClick(sender: AnyObject) { 871 | webView?.goForward() 872 | } 873 | 874 | func reloadDidClick(sender: AnyObject) { 875 | reload() 876 | } 877 | 878 | func stopDidClick(sender: AnyObject) { 879 | webView?.stopLoading() 880 | } 881 | 882 | func activityDidClick(sender: AnyObject) { 883 | guard let url = currentURL else { 884 | return 885 | } 886 | 887 | let activityViewController = UIActivityViewController(activityItems: [url], applicationActivities: nil) 888 | present(activityViewController, animated: true, completion: nil) 889 | } 890 | 891 | func doneDidClick(sender: AnyObject) { 892 | var canDismiss = true 893 | if let url = currentURL { 894 | canDismiss = delegate?.progressWebViewController?(self, canDismiss: url) ?? true 895 | } 896 | if canDismiss { 897 | dismiss(animated: true, completion: nil) 898 | } 899 | } 900 | 901 | func refreshWebView(sender: UIRefreshControl) { 902 | let isLoading = webView?.isLoading ?? false 903 | if !isLoading { 904 | sender.beginRefreshing() 905 | reloadDidClick(sender: sender) 906 | } 907 | } 908 | 909 | func webViewDidTap(sender: UITapGestureRecognizer) { 910 | lastTapPosition = sender.location(in: view) 911 | } 912 | } 913 | -------------------------------------------------------------------------------- /ProgressWebViewControllerDemo/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // ProgressWebViewControllerDemo 4 | // 5 | // Created by Zheng-Xiang Ke on 2017/10/14. 6 | // Copyright © 2017年 Zheng-Xiang Ke. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 18 | // Override point for customization after application launch. 19 | return true 20 | } 21 | 22 | func applicationWillResignActive(_ application: UIApplication) { 23 | // 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. 24 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 25 | } 26 | 27 | func applicationDidEnterBackground(_ application: UIApplication) { 28 | // 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. 29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 30 | } 31 | 32 | func applicationWillEnterForeground(_ application: UIApplication) { 33 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 34 | } 35 | 36 | func applicationDidBecomeActive(_ application: UIApplication) { 37 | // 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. 38 | } 39 | 40 | func applicationWillTerminate(_ application: UIApplication) { 41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 42 | } 43 | 44 | 45 | } 46 | 47 | -------------------------------------------------------------------------------- /ProgressWebViewControllerDemo/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | } 88 | ], 89 | "info" : { 90 | "version" : 1, 91 | "author" : "xcode" 92 | } 93 | } -------------------------------------------------------------------------------- /ProgressWebViewControllerDemo/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /ProgressWebViewControllerDemo/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 48 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | -------------------------------------------------------------------------------- /ProgressWebViewControllerDemo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /ProgressWebViewControllerDemo/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // ProgressWebViewControllerDemo 4 | // 5 | // Created by Zheng-Xiang Ke on 2017/10/14. 6 | // Copyright © 2017年 Zheng-Xiang Ke. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import ProgressWebViewController 11 | 12 | class ViewController: UIViewController { 13 | 14 | override func viewDidLoad() { 15 | super.viewDidLoad() 16 | // Do any additional setup after loading the view, typically from a nib. 17 | } 18 | 19 | override func didReceiveMemoryWarning() { 20 | super.didReceiveMemoryWarning() 21 | // Dispose of any resources that can be recreated. 22 | } 23 | 24 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 25 | // Get the new view controller using segue.destinationViewController. 26 | // Pass the selected object to the new view controller. 27 | guard let identifier = segue.identifier, let url = URL(string: "https://www.google.com") else { 28 | return 29 | } 30 | 31 | switch identifier { 32 | case "Present": 33 | guard let navigationController = segue.destination as? UINavigationController, let progressWebViewController = navigationController.topViewController as? ProgressWebViewController else { 34 | return 35 | } 36 | 37 | progressWebViewController.pullToRefresh = true 38 | progressWebViewController.defaultURL = url 39 | progressWebViewController.bypassedSSLHosts = [url.host!] 40 | progressWebViewController.userAgent = "ProgressWebViewController/1.0.0" 41 | progressWebViewController.websiteTitleInNavigationBar = false 42 | progressWebViewController.navigationItem.title = "Google Website" 43 | progressWebViewController.navigationWay = .push(targetViewController: nil) 44 | progressWebViewController.toolbarItemTypes = [.reload, .activity] 45 | case "Show": 46 | guard let progressWebViewController = segue.destination as? ProgressWebViewController else { 47 | return 48 | } 49 | 50 | progressWebViewController.isScrollEnabled = false 51 | progressWebViewController.disableZoom = true 52 | progressWebViewController.toolbarItemTypes = [.back, .forward, .reload, .activity] 53 | progressWebViewController.defaultURL = url 54 | progressWebViewController.defaultHeaders = ["browser": "in-app browser"] 55 | progressWebViewController.tintColor = .red 56 | progressWebViewController.defaultCookies = [ 57 | HTTPCookie(properties: 58 | [HTTPCookiePropertyKey.originURL: url.absoluteString, 59 | HTTPCookiePropertyKey.path: "/", 60 | HTTPCookiePropertyKey.name: "author", 61 | HTTPCookiePropertyKey.value: "Zheng-Xiang Ke"])!, 62 | HTTPCookie(properties: 63 | [HTTPCookiePropertyKey.originURL: url.absoluteString, 64 | HTTPCookiePropertyKey.path: "/", 65 | HTTPCookiePropertyKey.name: "GitHub", 66 | HTTPCookiePropertyKey.value: "kf99916"])!] 67 | default: 68 | print("Unknown segue \(identifier)") 69 | } 70 | } 71 | } 72 | 73 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ProgressWebViewController 2 | 3 | A WebViewController implemented by WKWebView with a progress bar in the navigation bar. The WebViewController is safari-like web browser. 4 | 5 | [![CocoaPods](https://img.shields.io/cocoapods/dt/ProgressWebViewController.svg)](https://cocoapods.org/pods/ProgressWebViewController) 6 | [![GitHub stars](https://img.shields.io/github/stars/kf99916/ProgressWebViewController.svg)](https://github.com/kf99916/ProgressWebViewController/stargazers) 7 | [![GitHub forks](https://img.shields.io/github/forks/kf99916/ProgressWebViewController.svg)](https://github.com/kf99916/ProgressWebViewController/network) 8 | [![CocoaPods Compatible](https://img.shields.io/cocoapods/v/ProgressWebViewController.svg)](https://cocoapods.org/pods/ProgressWebViewController) 9 | [![Platform](https://img.shields.io/cocoapods/p/ProgressWebViewController.svg)](https://github.com/kf99916/ProgressWebViewController) 10 | [![GitHub license](https://img.shields.io/github/license/kf99916/ProgressWebViewController.svg)](https://github.com/kf99916/ProgressWebViewController/blob/master/LICENSE) 11 | 12 | ![ProgressWebViewController](/screenshots/progressWebViewController.png 'ProgressWebViewController') ![ProgressWebViewController](/screenshots/progressWebViewController2.png 'ProgressWebViewController') 13 | 14 | ## Features 15 | 16 | - :white_check_mark: Progress bar in navigation bar 17 | - :white_check_mark: Bypass SSL according to the assigned hosts.( i.e., you can access the self-signed certificate websites with ProgressWebViewController) 18 | - :white_check_mark: Customize bar button items 19 | - :white_check_mark: Assign cookies to the web view 20 | - :white_check_mark: Browse the local html files 21 | - :white_check_mark: Support large titles for navigation bars in iOS 11 22 | - :white_check_mark: Support custom headers 23 | - :white_check_mark: Support custom user agent 24 | - :white_check_mark: Open the special urls including the app store, tel, mailto, sms, and \_blank with other apps 25 | - :white_check_mark: Support the pull-to-refresh 26 | - :white_check_mark: Support the push navigation way 27 | 28 | ## Requirements 29 | 30 | - iOS 13.0+ 31 | 32 | ## Installation 33 | 34 | ### Swift Package Manager 35 | 36 | The [Swift Package Manager](https://swift.org/package-manager/) is a tool for automating the distribution of Swift code and is integrated into the `swift` compiler. 37 | 38 | Once you have your Swift package set up, adding ProgressWebViewController as a dependency is as easy as adding it to the `dependencies` value of your `Package.swift`. 39 | 40 | ```swift 41 | dependencies: [ 42 | .package(url: "https://github.com/kf99916/ProgressWebViewController.git") 43 | ] 44 | ``` 45 | 46 | ### CocoaPods 47 | 48 | [CocoaPods](https://cocoapods.org) is a dependency manager for Cocoa projects. For usage and installation instructions, visit their website. To integrate ProgressWebViewController into your Xcode project using CocoaPods, specify it in your `Podfile`: 49 | 50 | ```ruby 51 | pod 'ProgressWebViewController' 52 | ``` 53 | 54 | ## Usage 55 | 56 | ### Import 57 | 58 | ```swift 59 | import ProgressWebViewController 60 | ``` 61 | 62 | ### Integration 63 | 64 | ### ProgressWebViewController 65 | 66 | A view controller with WKWebView and a progress bar in the navigation bar 67 | 68 | `var url: URL?` the url to request 69 | `var tintColor: UIColor?` the tint color for the progress bar, navigation bar, and tool bar 70 | `var delegate: ProgressWebViewControllerDelegate?` the delegate for ProgressWebViewController 71 | `var scrollViewDelegate: ProgressWebViewControllerScrollViewDelegate?` the delegate for scroll view 72 | `var bypassedSSLHosts: [String]?` the bypassed SSL hosts. The hosts must also be disabled in the App Transport Security. 73 | `var cookies: [HTTPCookie]?` the assigned cookies 74 | `var headers: [String: String]?` the custom headers 75 | `var userAgent: String?` the custom user agent 76 | `var urlsHandledByApp: [String: Any]` configure the urls handled by other apps (default `[ "hosts": ["itunes.apple.com"], "schemes": ["tel", "mailto", "sms"], "_blank": true ]`) 77 | `var websiteTitleInNavigationBar = true` show the website title in the navigation bar 78 | `var doneBarButtonItemPosition: NavigationBarPosition` the position for the done bar button item. the done barbutton item is added automatically if the view controller is presented.(default `.left`) 79 | `var leftNavigaionBarItemTypes: [BarButtonItemType]` configure the bar button items in the left navigation bar (default `[]`) 80 | `var rightNavigaionBarItemTypes: [BarButtonItemType]` configure the bar button items in the right navigation bar (default `[]`) 81 | `var toolbarItemTypes: [BarButtonItemType]` configure the bar button items in the toolbar of navigation controller (default `[.back, .forward, .reload, .activity]`) 82 | `var navigationWay: [NavigationWay]` configure the navigation way for clicking links (default `.browser`) 83 | `var pullToRefresh: Bool` enable/disable the pull-to-refresh (default `false`) 84 | 85 | #### Subclassing 86 | 87 | You should set up the webview in `loadView()` and set up others in `viewDidLoad()` 88 | 89 | ```swift 90 | class MyWebViewController: ProgressWebViewController { 91 | override open func loadView() { 92 | super.loadView() 93 | 94 | // set up webview, including cookies, headers, user agent, and so on. 95 | } 96 | 97 | override func viewDidLoad() { 98 | super.viewDidLoad() 99 | 100 | // Do any additional setup after loading the view. 101 | } 102 | 103 | // Other methods 104 | } 105 | ``` 106 | 107 | ### ProgressWebViewControllerDelegate 108 | 109 | The delegate for ProgressWebViwController 110 | 111 | `optional func progressWebViewController(_ controller: ProgressWebViewController, canDismiss url: URL) -> Bool` 112 | `optional func progressWebViewController(_ controller: ProgressWebViewController, didStart url: URL)` 113 | `optional func progressWebViewController(_ controller: ProgressWebViewController, didFinish url: URL)` 114 | `optional func progressWebViewController(_ controller: ProgressWebViewController, didFail url: URL, withError error: Error)` 115 | `optional func progressWebViewController(_ controller: ProgressWebViewController, decidePolicy url: URL) -> Bool` 116 | `optional func initPushedProgressWebViewController(url: URL) -> ProgressWebViewController` 117 | 118 | ### ProgressWebViewControllerScrollViewDelegate 119 | 120 | The delegate for scroll view 121 | 122 | `optional func scrollViewDidScroll(_ scrollView: UIScrollView)` 123 | 124 | ### BarButtonItemType 125 | 126 | The enum for bar button item 127 | 128 | ```swift 129 | enum BarButtonItemType { 130 | case back 131 | case forward 132 | case reload 133 | case stop 134 | case activity 135 | case done 136 | case flexibleSpace 137 | } 138 | ``` 139 | 140 | ### NavigationBarPosition 141 | 142 | The enum for position of bar button item in the navigation bar 143 | 144 | ```swift 145 | enum NavigationBarPosition { 146 | case none 147 | case left 148 | case right 149 | } 150 | ``` 151 | 152 | ### NavigationWay 153 | 154 | The enum for navigation way 155 | 156 | ```swift 157 | enum NavigationWay { 158 | case browser 159 | case push 160 | } 161 | ``` 162 | 163 | ## Apps using ProgressWebViewController 164 | 165 | If you are using ProgressWebViewController in your app and want to be listed here, simply create a pull request. 166 | 167 | I am always curious who is using my projects :) 168 | 169 | [Hikingbook](https://itunes.apple.com/app/id1067838748) - by Zheng-Xiang Ke 170 | 171 | ![Hikingbook](apps/Hikingbook.png) 172 | 173 | ## Demo 174 | 175 | ProgressWebViewControllerDemo is a simple demo app which browse the Apple website with ProgressWebViewController. 176 | 177 | ## Author 178 | 179 | Zheng-Xiang Ke, kf99916@gmail.com 180 | 181 | ## License 182 | 183 | ProgressWebViewController is available under the MIT license. See the LICENSE file for more info. 184 | -------------------------------------------------------------------------------- /apps/Hikingbook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kf99916/ProgressWebViewController/3656ef84132cf80dc2abeea830d0baf77f62d482/apps/Hikingbook.png -------------------------------------------------------------------------------- /screenshots/progressWebViewController.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kf99916/ProgressWebViewController/3656ef84132cf80dc2abeea830d0baf77f62d482/screenshots/progressWebViewController.png -------------------------------------------------------------------------------- /screenshots/progressWebViewController2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kf99916/ProgressWebViewController/3656ef84132cf80dc2abeea830d0baf77f62d482/screenshots/progressWebViewController2.png --------------------------------------------------------------------------------