";
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 |
--------------------------------------------------------------------------------