├── .gitignore ├── LICENSE ├── README.md └── WKWebViewLocal ├── WKWebViewLocal.xcodeproj ├── project.pbxproj └── project.xcworkspace │ └── contents.xcworkspacedata └── WKWebViewLocal ├── Assets.xcassets ├── AccentColor.colorset │ └── Contents.json ├── AppIcon.appiconset │ └── Contents.json └── Contents.json ├── ContentView.swift ├── Preview Content └── Preview Assets.xcassets │ └── Contents.json ├── WKWebViewLocalApp.swift ├── WebViewModel.swift ├── WebViewRepresentable.swift └── Web_Assets ├── css └── test.css ├── images └── pic.png ├── index.html └── page.html /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | *.pbxuser 3 | !default.pbxuser 4 | *.mode1v3 5 | !default.mode1v3 6 | *.mode2v3 7 | !default.mode2v3 8 | *.perspectivev3 9 | !default.perspectivev3 10 | xcuserdata 11 | *.xccheckout 12 | *.moved-aside 13 | DerivedData 14 | *.xcuserstate 15 | .DS_Store 16 | 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Gary Newby 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 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | WKWebViewLocal 2 | ============== 3 | 4 | SwiftUI example of WKWebView loading local file (including css and images) or html string, and calling between Javascript and Swift. 5 | 6 | 7 | -------------------------------------------------------------------------------- /WKWebViewLocal/WKWebViewLocal.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 77; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 4E4CE9202CF4E4BD009C10E4 /* Web_Assets in Resources */ = {isa = PBXBuildFile; fileRef = 4E4CE91F2CF4E4BD009C10E4 /* Web_Assets */; }; 11 | /* End PBXBuildFile section */ 12 | 13 | /* Begin PBXFileReference section */ 14 | 4E4CE8D32CF4D36E009C10E4 /* WKWebViewLocal.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = WKWebViewLocal.app; sourceTree = BUILT_PRODUCTS_DIR; }; 15 | 4E4CE91F2CF4E4BD009C10E4 /* Web_Assets */ = {isa = PBXFileReference; lastKnownFileType = folder; name = Web_Assets; path = WKWebViewLocal/Web_Assets; sourceTree = ""; }; 16 | /* End PBXFileReference section */ 17 | 18 | /* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ 19 | 4E4CE9212CF4E4EB009C10E4 /* Exceptions for "WKWebViewLocal" folder in "WKWebViewLocal" target */ = { 20 | isa = PBXFileSystemSynchronizedBuildFileExceptionSet; 21 | membershipExceptions = ( 22 | Web_Assets/css/test.css, 23 | Web_Assets/images/pic.png, 24 | Web_Assets/index.html, 25 | Web_Assets/page.html, 26 | ); 27 | target = 4E4CE8D22CF4D36E009C10E4 /* WKWebViewLocal */; 28 | }; 29 | /* End PBXFileSystemSynchronizedBuildFileExceptionSet section */ 30 | 31 | /* Begin PBXFileSystemSynchronizedRootGroup section */ 32 | 4E4CE8D52CF4D36E009C10E4 /* WKWebViewLocal */ = { 33 | isa = PBXFileSystemSynchronizedRootGroup; 34 | exceptions = ( 35 | 4E4CE9212CF4E4EB009C10E4 /* Exceptions for "WKWebViewLocal" folder in "WKWebViewLocal" target */, 36 | ); 37 | path = WKWebViewLocal; 38 | sourceTree = ""; 39 | }; 40 | /* End PBXFileSystemSynchronizedRootGroup section */ 41 | 42 | /* Begin PBXFrameworksBuildPhase section */ 43 | 4E4CE8D02CF4D36E009C10E4 /* Frameworks */ = { 44 | isa = PBXFrameworksBuildPhase; 45 | buildActionMask = 2147483647; 46 | files = ( 47 | ); 48 | runOnlyForDeploymentPostprocessing = 0; 49 | }; 50 | /* End PBXFrameworksBuildPhase section */ 51 | 52 | /* Begin PBXGroup section */ 53 | 4E4CE8CA2CF4D36E009C10E4 = { 54 | isa = PBXGroup; 55 | children = ( 56 | 4E4CE91F2CF4E4BD009C10E4 /* Web_Assets */, 57 | 4E4CE8D52CF4D36E009C10E4 /* WKWebViewLocal */, 58 | 4E4CE8D42CF4D36E009C10E4 /* Products */, 59 | ); 60 | sourceTree = ""; 61 | }; 62 | 4E4CE8D42CF4D36E009C10E4 /* Products */ = { 63 | isa = PBXGroup; 64 | children = ( 65 | 4E4CE8D32CF4D36E009C10E4 /* WKWebViewLocal.app */, 66 | ); 67 | name = Products; 68 | sourceTree = ""; 69 | }; 70 | /* End PBXGroup section */ 71 | 72 | /* Begin PBXNativeTarget section */ 73 | 4E4CE8D22CF4D36E009C10E4 /* WKWebViewLocal */ = { 74 | isa = PBXNativeTarget; 75 | buildConfigurationList = 4E4CE8E12CF4D36F009C10E4 /* Build configuration list for PBXNativeTarget "WKWebViewLocal" */; 76 | buildPhases = ( 77 | 4E4CE8CF2CF4D36E009C10E4 /* Sources */, 78 | 4E4CE8D02CF4D36E009C10E4 /* Frameworks */, 79 | 4E4CE8D12CF4D36E009C10E4 /* Resources */, 80 | ); 81 | buildRules = ( 82 | ); 83 | dependencies = ( 84 | ); 85 | fileSystemSynchronizedGroups = ( 86 | 4E4CE8D52CF4D36E009C10E4 /* WKWebViewLocal */, 87 | ); 88 | name = WKWebViewLocal; 89 | packageProductDependencies = ( 90 | ); 91 | productName = WKWebViewLocal; 92 | productReference = 4E4CE8D32CF4D36E009C10E4 /* WKWebViewLocal.app */; 93 | productType = "com.apple.product-type.application"; 94 | }; 95 | /* End PBXNativeTarget section */ 96 | 97 | /* Begin PBXProject section */ 98 | 4E4CE8CB2CF4D36E009C10E4 /* Project object */ = { 99 | isa = PBXProject; 100 | attributes = { 101 | BuildIndependentTargetsInParallel = 1; 102 | LastSwiftUpdateCheck = 1600; 103 | LastUpgradeCheck = 1600; 104 | TargetAttributes = { 105 | 4E4CE8D22CF4D36E009C10E4 = { 106 | CreatedOnToolsVersion = 16.0; 107 | }; 108 | }; 109 | }; 110 | buildConfigurationList = 4E4CE8CE2CF4D36E009C10E4 /* Build configuration list for PBXProject "WKWebViewLocal" */; 111 | developmentRegion = en; 112 | hasScannedForEncodings = 0; 113 | knownRegions = ( 114 | en, 115 | Base, 116 | ); 117 | mainGroup = 4E4CE8CA2CF4D36E009C10E4; 118 | minimizedProjectReferenceProxies = 1; 119 | preferredProjectObjectVersion = 77; 120 | productRefGroup = 4E4CE8D42CF4D36E009C10E4 /* Products */; 121 | projectDirPath = ""; 122 | projectRoot = ""; 123 | targets = ( 124 | 4E4CE8D22CF4D36E009C10E4 /* WKWebViewLocal */, 125 | ); 126 | }; 127 | /* End PBXProject section */ 128 | 129 | /* Begin PBXResourcesBuildPhase section */ 130 | 4E4CE8D12CF4D36E009C10E4 /* Resources */ = { 131 | isa = PBXResourcesBuildPhase; 132 | buildActionMask = 2147483647; 133 | files = ( 134 | 4E4CE9202CF4E4BD009C10E4 /* Web_Assets in Resources */, 135 | ); 136 | runOnlyForDeploymentPostprocessing = 0; 137 | }; 138 | /* End PBXResourcesBuildPhase section */ 139 | 140 | /* Begin PBXSourcesBuildPhase section */ 141 | 4E4CE8CF2CF4D36E009C10E4 /* Sources */ = { 142 | isa = PBXSourcesBuildPhase; 143 | buildActionMask = 2147483647; 144 | files = ( 145 | ); 146 | runOnlyForDeploymentPostprocessing = 0; 147 | }; 148 | /* End PBXSourcesBuildPhase section */ 149 | 150 | /* Begin XCBuildConfiguration section */ 151 | 4E4CE8DF2CF4D36F009C10E4 /* Debug */ = { 152 | isa = XCBuildConfiguration; 153 | buildSettings = { 154 | ALWAYS_SEARCH_USER_PATHS = NO; 155 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 156 | CLANG_ANALYZER_NONNULL = YES; 157 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 158 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 159 | CLANG_ENABLE_MODULES = YES; 160 | CLANG_ENABLE_OBJC_ARC = YES; 161 | CLANG_ENABLE_OBJC_WEAK = YES; 162 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 163 | CLANG_WARN_BOOL_CONVERSION = YES; 164 | CLANG_WARN_COMMA = YES; 165 | CLANG_WARN_CONSTANT_CONVERSION = YES; 166 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 167 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 168 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 169 | CLANG_WARN_EMPTY_BODY = YES; 170 | CLANG_WARN_ENUM_CONVERSION = YES; 171 | CLANG_WARN_INFINITE_RECURSION = YES; 172 | CLANG_WARN_INT_CONVERSION = YES; 173 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 174 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 175 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 176 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 177 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 178 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 179 | CLANG_WARN_STRICT_PROTOTYPES = YES; 180 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 181 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 182 | CLANG_WARN_UNREACHABLE_CODE = YES; 183 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 184 | COPY_PHASE_STRIP = NO; 185 | DEBUG_INFORMATION_FORMAT = dwarf; 186 | ENABLE_STRICT_OBJC_MSGSEND = YES; 187 | ENABLE_TESTABILITY = YES; 188 | ENABLE_USER_SCRIPT_SANDBOXING = YES; 189 | GCC_C_LANGUAGE_STANDARD = gnu17; 190 | GCC_DYNAMIC_NO_PIC = NO; 191 | GCC_NO_COMMON_BLOCKS = YES; 192 | GCC_OPTIMIZATION_LEVEL = 0; 193 | GCC_PREPROCESSOR_DEFINITIONS = ( 194 | "DEBUG=1", 195 | "$(inherited)", 196 | ); 197 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 198 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 199 | GCC_WARN_UNDECLARED_SELECTOR = YES; 200 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 201 | GCC_WARN_UNUSED_FUNCTION = YES; 202 | GCC_WARN_UNUSED_VARIABLE = YES; 203 | IPHONEOS_DEPLOYMENT_TARGET = 18.0; 204 | LOCALIZATION_PREFERS_STRING_CATALOGS = YES; 205 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 206 | MTL_FAST_MATH = YES; 207 | ONLY_ACTIVE_ARCH = YES; 208 | SDKROOT = iphoneos; 209 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; 210 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 211 | }; 212 | name = Debug; 213 | }; 214 | 4E4CE8E02CF4D36F009C10E4 /* Release */ = { 215 | isa = XCBuildConfiguration; 216 | buildSettings = { 217 | ALWAYS_SEARCH_USER_PATHS = NO; 218 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 219 | CLANG_ANALYZER_NONNULL = YES; 220 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 221 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 222 | CLANG_ENABLE_MODULES = YES; 223 | CLANG_ENABLE_OBJC_ARC = YES; 224 | CLANG_ENABLE_OBJC_WEAK = YES; 225 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 226 | CLANG_WARN_BOOL_CONVERSION = YES; 227 | CLANG_WARN_COMMA = YES; 228 | CLANG_WARN_CONSTANT_CONVERSION = YES; 229 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 230 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 231 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 232 | CLANG_WARN_EMPTY_BODY = YES; 233 | CLANG_WARN_ENUM_CONVERSION = YES; 234 | CLANG_WARN_INFINITE_RECURSION = YES; 235 | CLANG_WARN_INT_CONVERSION = YES; 236 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 237 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 238 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 239 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 240 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 241 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 242 | CLANG_WARN_STRICT_PROTOTYPES = YES; 243 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 244 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 245 | CLANG_WARN_UNREACHABLE_CODE = YES; 246 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 247 | COPY_PHASE_STRIP = NO; 248 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 249 | ENABLE_NS_ASSERTIONS = NO; 250 | ENABLE_STRICT_OBJC_MSGSEND = YES; 251 | ENABLE_USER_SCRIPT_SANDBOXING = YES; 252 | GCC_C_LANGUAGE_STANDARD = gnu17; 253 | GCC_NO_COMMON_BLOCKS = YES; 254 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 255 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 256 | GCC_WARN_UNDECLARED_SELECTOR = YES; 257 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 258 | GCC_WARN_UNUSED_FUNCTION = YES; 259 | GCC_WARN_UNUSED_VARIABLE = YES; 260 | IPHONEOS_DEPLOYMENT_TARGET = 18.0; 261 | LOCALIZATION_PREFERS_STRING_CATALOGS = YES; 262 | MTL_ENABLE_DEBUG_INFO = NO; 263 | MTL_FAST_MATH = YES; 264 | SDKROOT = iphoneos; 265 | SWIFT_COMPILATION_MODE = wholemodule; 266 | VALIDATE_PRODUCT = YES; 267 | }; 268 | name = Release; 269 | }; 270 | 4E4CE8E22CF4D36F009C10E4 /* Debug */ = { 271 | isa = XCBuildConfiguration; 272 | buildSettings = { 273 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 274 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 275 | CODE_SIGN_STYLE = Manual; 276 | CURRENT_PROJECT_VERSION = 1; 277 | DEVELOPMENT_ASSET_PATHS = "\"WKWebViewLocal/Preview Content\""; 278 | DEVELOPMENT_TEAM = ""; 279 | ENABLE_PREVIEWS = YES; 280 | GENERATE_INFOPLIST_FILE = YES; 281 | INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; 282 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; 283 | INFOPLIST_KEY_UILaunchScreen_Generation = YES; 284 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 285 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 286 | LD_RUNPATH_SEARCH_PATHS = ( 287 | "$(inherited)", 288 | "@executable_path/Frameworks", 289 | ); 290 | MARKETING_VERSION = 1.0; 291 | PRODUCT_BUNDLE_IDENTIFIER = com.mellowmuse.WKWebViewLocal; 292 | PRODUCT_NAME = "$(TARGET_NAME)"; 293 | PROVISIONING_PROFILE_SPECIFIER = ""; 294 | SWIFT_EMIT_LOC_STRINGS = YES; 295 | SWIFT_VERSION = 5.0; 296 | TARGETED_DEVICE_FAMILY = "1,2"; 297 | }; 298 | name = Debug; 299 | }; 300 | 4E4CE8E32CF4D36F009C10E4 /* Release */ = { 301 | isa = XCBuildConfiguration; 302 | buildSettings = { 303 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 304 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 305 | CODE_SIGN_STYLE = Manual; 306 | CURRENT_PROJECT_VERSION = 1; 307 | DEVELOPMENT_ASSET_PATHS = "\"WKWebViewLocal/Preview Content\""; 308 | DEVELOPMENT_TEAM = ""; 309 | ENABLE_PREVIEWS = YES; 310 | GENERATE_INFOPLIST_FILE = YES; 311 | INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; 312 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; 313 | INFOPLIST_KEY_UILaunchScreen_Generation = YES; 314 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 315 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 316 | LD_RUNPATH_SEARCH_PATHS = ( 317 | "$(inherited)", 318 | "@executable_path/Frameworks", 319 | ); 320 | MARKETING_VERSION = 1.0; 321 | PRODUCT_BUNDLE_IDENTIFIER = com.mellowmuse.WKWebViewLocal; 322 | PRODUCT_NAME = "$(TARGET_NAME)"; 323 | PROVISIONING_PROFILE_SPECIFIER = ""; 324 | SWIFT_EMIT_LOC_STRINGS = YES; 325 | SWIFT_VERSION = 5.0; 326 | TARGETED_DEVICE_FAMILY = "1,2"; 327 | }; 328 | name = Release; 329 | }; 330 | /* End XCBuildConfiguration section */ 331 | 332 | /* Begin XCConfigurationList section */ 333 | 4E4CE8CE2CF4D36E009C10E4 /* Build configuration list for PBXProject "WKWebViewLocal" */ = { 334 | isa = XCConfigurationList; 335 | buildConfigurations = ( 336 | 4E4CE8DF2CF4D36F009C10E4 /* Debug */, 337 | 4E4CE8E02CF4D36F009C10E4 /* Release */, 338 | ); 339 | defaultConfigurationIsVisible = 0; 340 | defaultConfigurationName = Release; 341 | }; 342 | 4E4CE8E12CF4D36F009C10E4 /* Build configuration list for PBXNativeTarget "WKWebViewLocal" */ = { 343 | isa = XCConfigurationList; 344 | buildConfigurations = ( 345 | 4E4CE8E22CF4D36F009C10E4 /* Debug */, 346 | 4E4CE8E32CF4D36F009C10E4 /* Release */, 347 | ); 348 | defaultConfigurationIsVisible = 0; 349 | defaultConfigurationName = Release; 350 | }; 351 | /* End XCConfigurationList section */ 352 | }; 353 | rootObject = 4E4CE8CB2CF4D36E009C10E4 /* Project object */; 354 | } 355 | -------------------------------------------------------------------------------- /WKWebViewLocal/WKWebViewLocal.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /WKWebViewLocal/WKWebViewLocal/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /WKWebViewLocal/WKWebViewLocal/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "platform" : "ios", 6 | "size" : "1024x1024" 7 | }, 8 | { 9 | "appearances" : [ 10 | { 11 | "appearance" : "luminosity", 12 | "value" : "dark" 13 | } 14 | ], 15 | "idiom" : "universal", 16 | "platform" : "ios", 17 | "size" : "1024x1024" 18 | }, 19 | { 20 | "appearances" : [ 21 | { 22 | "appearance" : "luminosity", 23 | "value" : "tinted" 24 | } 25 | ], 26 | "idiom" : "universal", 27 | "platform" : "ios", 28 | "size" : "1024x1024" 29 | } 30 | ], 31 | "info" : { 32 | "author" : "xcode", 33 | "version" : 1 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /WKWebViewLocal/WKWebViewLocal/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /WKWebViewLocal/WKWebViewLocal/ContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentView.swift 3 | // WKWebViewLocal 4 | // 5 | // Created by Gary Newby on 25/11/2024. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct ContentView: View { 11 | let webViewModel = WebViewModel() 12 | let actions = ["changeText('hello from Swift')", "goBack()"] 13 | 14 | static var date: String { 15 | let formatter = DateFormatter() 16 | formatter.dateStyle = .medium 17 | return formatter.string(from: Date()) 18 | } 19 | 20 | var body: some View { 21 | VStack { 22 | WebViewRepresentable(viewModel: webViewModel) 23 | .cornerRadius(10) 24 | 25 | ForEach(actions, id: \.self) { action in 26 | if webViewModel.isButtonVisible(action) { 27 | Button { 28 | webViewModel.callJavascriptFunction(named: action) 29 | } label: { 30 | Text("Call \(action)") 31 | .font(.headline) 32 | .frame(maxWidth: .infinity, maxHeight: 35) 33 | } 34 | .buttonStyle(.borderedProminent) 35 | } 36 | } 37 | } 38 | .padding(20) 39 | } 40 | } 41 | 42 | #Preview { 43 | ContentView() 44 | } 45 | -------------------------------------------------------------------------------- /WKWebViewLocal/WKWebViewLocal/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /WKWebViewLocal/WKWebViewLocal/WKWebViewLocalApp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WKWebViewLocalApp.swift 3 | // WKWebViewLocal 4 | // 5 | // Created by Gary Newby on 25/11/2024. 6 | // 7 | 8 | import SwiftUI 9 | 10 | @main 11 | struct WKWebViewLocalApp: App { 12 | var body: some Scene { 13 | WindowGroup { 14 | ContentView() 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /WKWebViewLocal/WKWebViewLocal/WebViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WebViewModel.swift 3 | // WKWebViewLocal 4 | // 5 | // Created by Gary Newby on 25/11/2024. 6 | // 7 | 8 | import SwiftUI 9 | import WebKit 10 | 11 | @Observable 12 | class WebViewModel { 13 | 14 | var url: URL? 15 | var error: Error? 16 | var isLoading = false 17 | var backButtonIsEnabled = false 18 | 19 | /// Load a file or a HTML string 20 | let loadFile = false 21 | 22 | weak var webView: WKWebView? 23 | 24 | func assignWebView(webView: WKWebView) { 25 | self.webView = webView 26 | 27 | if loadFile { 28 | loadFile(name: "index") 29 | } else { 30 | loadString(name: "index") 31 | } 32 | } 33 | 34 | func loadFile(name: String) { 35 | guard let filePath = Bundle.main.path(forResource: name, ofType: "html", inDirectory: "Web_Assets") else { 36 | print("file not found:", name) 37 | return 38 | } 39 | let filePathURL = URL.init(fileURLWithPath: filePath) 40 | let fileDirectoryURL = filePathURL.deletingLastPathComponent() 41 | webView?.loadFileURL(filePathURL, allowingReadAccessTo: fileDirectoryURL) 42 | } 43 | 44 | func loadString(name: String) { 45 | guard let filePath = Bundle.main.path(forResource: name, ofType: "html", inDirectory: "Web_Assets") else { 46 | print("file not found:", name) 47 | return 48 | } 49 | do { 50 | let html = try String(contentsOfFile: filePath, encoding: .utf8) 51 | /// baseURL needs to be set for local files to load correctly 52 | webView?.loadHTMLString(html, baseURL: Bundle.main.resourceURL?.appendingPathComponent("Web_Assets")) 53 | } catch { 54 | print("Error loading html") 55 | } 56 | } 57 | 58 | func callJavascriptFunction(named: String) { 59 | let script = named 60 | webView?.evaluateJavaScript(script) { (result: Any?, error: Error?) in 61 | if let error = error { 62 | print("evaluateJavaScript error: \(error)") 63 | } else { 64 | print("evaluateJavaScript result: \(result ?? "")") 65 | } 66 | } 67 | } 68 | 69 | func isButtonVisible(_ action: String) -> Bool { 70 | switch action { 71 | case "goBack()": 72 | return backButtonIsEnabled 73 | case "changeText('hello from Swift')": 74 | return !backButtonIsEnabled 75 | default: 76 | return false 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /WKWebViewLocal/WKWebViewLocal/WebViewRepresentable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WebView.swift 3 | // WKWebViewLocal 4 | // 5 | // Created by Gary Newby on 25/11/2024. 6 | // 7 | 8 | import SwiftUI 9 | @preconcurrency 10 | import WebKit 11 | 12 | struct WebViewRepresentable: UIViewRepresentable { 13 | 14 | let viewModel: WebViewModel 15 | let webView = WKWebView() 16 | 17 | func makeCoordinator() -> Coordinator { 18 | Coordinator(self) 19 | } 20 | 21 | func makeUIView(context: Context) -> WKWebView { 22 | webView.navigationDelegate = context.coordinator 23 | if let url = viewModel.url { 24 | webView.load(URLRequest(url: url)) 25 | } 26 | viewModel.assignWebView(webView: webView) 27 | return webView 28 | } 29 | 30 | func updateUIView(_ uiView: WKWebView, context: Context) { 31 | } 32 | 33 | class Coordinator: NSObject, WKNavigationDelegate, WKScriptMessageHandler { 34 | var parent: WebViewRepresentable 35 | 36 | var webView: WKWebView { 37 | parent.webView 38 | } 39 | var viewModel: WebViewModel { 40 | parent.viewModel 41 | } 42 | 43 | init(_ parent: WebViewRepresentable) { 44 | self.parent = parent 45 | super.init() 46 | 47 | // Add addScriptMessageHandler in javascript: window.webkit.messageHandlers.MyObserver.postMessage() 48 | webView.configuration.userContentController.add(self, name: "MyObserver") 49 | } 50 | 51 | func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { 52 | if navigationAction.navigationType == .linkActivated { 53 | if let url = navigationAction.request.url { 54 | webView.load(URLRequest(url: url)) 55 | decisionHandler(.cancel) 56 | return 57 | } 58 | } 59 | decisionHandler(.allow) 60 | } 61 | 62 | func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) { 63 | viewModel.isLoading = true 64 | } 65 | 66 | func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { 67 | viewModel.isLoading = false 68 | } 69 | 70 | func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) { 71 | viewModel.isLoading = false 72 | viewModel.error = error 73 | print("loading error: \(error)") 74 | } 75 | 76 | /// Callback from javascript: window.webkit.messageHandlers.MyObserver.postMessage(message) 77 | func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { 78 | let messageFromWebPage = message.body as! String 79 | 80 | if messageFromWebPage == "index" { 81 | viewModel.backButtonIsEnabled = false 82 | viewModel.loadFile(name: messageFromWebPage) 83 | return 84 | } 85 | if messageFromWebPage == "goPage" { 86 | viewModel.backButtonIsEnabled = true 87 | return 88 | } 89 | 90 | let alertController = UIAlertController(title: "Javascript said:", message: messageFromWebPage, preferredStyle: .alert) 91 | let okAction = UIAlertAction(title: "OK", style: .default) { (_) in 92 | print("OK") 93 | } 94 | alertController.addAction(okAction) 95 | guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene, 96 | let rootViewController = windowScene.windows.first?.rootViewController else { 97 | return 98 | } 99 | rootViewController.present(alertController, animated: true) 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /WKWebViewLocal/WKWebViewLocal/Web_Assets/css/test.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: Arial, sans-serif; 3 | font-size:16px; 4 | line-height: 20px; 5 | margin:25px; 6 | background:#eee; 7 | color:#111; 8 | } 9 | -------------------------------------------------------------------------------- /WKWebViewLocal/WKWebViewLocal/Web_Assets/images/pic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garynewby/WKWebView-Local/2f7ce6c43951e490b1015ed258df8b8fa7a25805/WKWebViewLocal/WKWebViewLocal/Web_Assets/images/pic.png -------------------------------------------------------------------------------- /WKWebViewLocal/WKWebViewLocal/Web_Assets/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

WKWebView

8 | 9 |

Loading local file, assets and css and calling javascript.

10 | 11 | 12 | 13 |

14 | Local files and assets load correctly if baseUrl is set. 15 |

16 | 17 |

18 | A ScriptMessageHandler "MyObserver" is added when the WKWebView is configured at start up. 19 | The "Call.." button calls the javascript function changeText() in this html file, cahnging the text below. 20 |

21 | 22 |

23 | The "Show a Swift alert" link calls showSwiftDialog() which calls the WKScriptMessageHandler delegate method userContentController 24 | and brings up a dialog with the passed message text. 25 |

26 | 27 |

[Swift]:

28 | 29 |

30 | Show a Swift alert from Javascript 31 |

32 | 33 |

34 | Another page 35 | 36 |

37 | 38 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /WKWebViewLocal/WKWebViewLocal/Web_Assets/page.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

Some Other Page

8 | 9 | 10 | 11 | 16 | 17 | 18 | 19 | --------------------------------------------------------------------------------