Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add CapWebView #5715

Merged
merged 24 commits into from
Jun 30, 2022
Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
b6ce7a1
capwebview wip
elylucas Jul 30, 2021
a430774
wip
elylucas Aug 10, 2021
935e7d8
adding capwebview to project (again?)
elylucas Aug 11, 2021
61ab121
clealup capwebview
elylucas Aug 11, 2021
2894234
feat: adding getAny method to PluginCall to return back Any type
elylucas Aug 24, 2021
1c756dd
Merge branch 'main' into portals-dev
jcesarmobile Oct 13, 2021
779f010
Merge branch 'main' into portals-dev
jcesarmobile Nov 3, 2021
9f644be
Merge branch 'main' into portals-dev
jcesarmobile Nov 5, 2021
881ae3a
Merge branch 'main' into portals-dev
jcesarmobile Nov 17, 2021
8e6cbdc
Merge branch 'main' into portals-dev
jcesarmobile Dec 8, 2021
6b87ef0
Merge branch 'main' into portals-dev
jcesarmobile Jan 5, 2022
5e18997
Merge branch 'main' into portals-dev
jcesarmobile Jan 19, 2022
3ca471c
Merge branch 'main' into portals-dev
jcesarmobile Feb 10, 2022
63cb8ae
Merge branch 'main' into portals-dev
jcesarmobile Mar 3, 2022
20bda84
Merge branch 'main' into portals-dev
jcesarmobile Mar 4, 2022
0f148f3
fix(ios): Use autolayout for CAPWebView and JSValueContainer Update (…
Steven0351 Mar 8, 2022
a495adc
Merge branch 'main' into portals-dev-merge-main
jcesarmobile Apr 22, 2022
b149b09
fix(ios): Add _Router() to WebViewAssetHandler initializer in CAPWebView
Steven0351 Apr 22, 2022
83955fd
Merge branch 'main' into portals-dev
jcesarmobile Apr 22, 2022
3373dd2
Merge branch 'main' into portals-dev
jcesarmobile May 4, 2022
f8bf7f3
Merge branch 'main' into portals-dev
jcesarmobile Jun 17, 2022
252b18f
Merge branch 'main' into portals-dev
jcesarmobile Jun 23, 2022
dc68392
Merge branch 'main' into portals-dev
jcesarmobile Jun 23, 2022
cb35145
Merge branch 'main' into portals-dev
jcesarmobile Jun 30, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions ios/Capacitor/Capacitor.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
501CBAA71FC0A723009B0D4D /* WebKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 501CBAA61FC0A723009B0D4D /* WebKit.framework */; };
50503EE91FC08595003606DC /* Capacitor.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 50503EDF1FC08594003606DC /* Capacitor.framework */; };
50503EEE1FC08595003606DC /* CapacitorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50503EED1FC08595003606DC /* CapacitorTests.swift */; };
55F6736A26C371E6001E7AB9 /* CAPWebView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55F6736826C371E6001E7AB9 /* CAPWebView.swift */; };
6214934725509C3F006C36F9 /* CAPInstanceConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6214934625509C3F006C36F9 /* CAPInstanceConfiguration.swift */; };
621ECCB72542045900D3D615 /* CAPBridgedJSTypes.m in Sources */ = {isa = PBXBuildFile; fileRef = 621ECCB42542045900D3D615 /* CAPBridgedJSTypes.m */; };
621ECCB82542045900D3D615 /* CAPBridgedJSTypes.h in Headers */ = {isa = PBXBuildFile; fileRef = 621ECCB62542045900D3D615 /* CAPBridgedJSTypes.h */; settings = {ATTRIBUTES = (Private, ); }; };
Expand Down Expand Up @@ -138,6 +139,7 @@
50503EDF1FC08594003606DC /* Capacitor.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Capacitor.framework; sourceTree = BUILT_PRODUCTS_DIR; };
50503EE81FC08595003606DC /* CapacitorTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CapacitorTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
50503EED1FC08595003606DC /* CapacitorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CapacitorTests.swift; sourceTree = "<group>"; };
55F6736826C371E6001E7AB9 /* CAPWebView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CAPWebView.swift; sourceTree = "<group>"; };
6214934625509C3F006C36F9 /* CAPInstanceConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CAPInstanceConfiguration.swift; sourceTree = "<group>"; };
621ECCB42542045900D3D615 /* CAPBridgedJSTypes.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CAPBridgedJSTypes.m; sourceTree = "<group>"; };
621ECCB62542045900D3D615 /* CAPBridgedJSTypes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CAPBridgedJSTypes.h; sourceTree = "<group>"; };
Expand Down Expand Up @@ -327,6 +329,7 @@
623D6913254C7030002D01D1 /* CAPInstanceDescriptor.swift */,
623D691B254C7462002D01D1 /* CAPInstanceConfiguration.h */,
623D691C254C7462002D01D1 /* CAPInstanceConfiguration.m */,
55F6736826C371E6001E7AB9 /* CAPWebView.swift */,
6214934625509C3F006C36F9 /* CAPInstanceConfiguration.swift */,
62959B082524DA7700A3D7F1 /* CAPLog.swift */,
62959AE72524DA7700A3D7F1 /* CAPFile.swift */,
Expand Down Expand Up @@ -579,6 +582,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
55F6736A26C371E6001E7AB9 /* CAPWebView.swift in Sources */,
A71289E627F380A500DADDF3 /* Router.swift in Sources */,
62959B362524DA7800A3D7F1 /* CAPBridgeViewController.swift in Sources */,
621ECCB72542045900D3D615 /* CAPBridgedJSTypes.m in Sources */,
Expand Down
227 changes: 227 additions & 0 deletions ios/Capacitor/Capacitor/CAPWebView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
import Foundation
import WebKit
import UIKit

open class CAPWebView: UIView {
lazy var webView: WKWebView = createWebView(
with: configuration,
assetHandler: assetHandler,
delegationHandler: delegationHandler
)

private lazy var capacitorBridge = CapacitorBridge(
with: configuration,
delegate: self,
cordovaConfiguration: configDescriptor.cordovaConfiguration,
assetHandler: assetHandler,
delegationHandler: delegationHandler
)

public final var bridge: CAPBridgeProtocol {
return capacitorBridge
}

private lazy var configDescriptor = instanceDescriptor()
private lazy var configuration = InstanceConfiguration(with: configDescriptor, isDebug: CapacitorBridge.isDevEnvironment)

private lazy var assetHandler: WebViewAssetHandler = {
let handler = WebViewAssetHandler(router: _Router())
handler.setAssetPath(configuration.appLocation.path)
return handler
}()

private lazy var delegationHandler = WebViewDelegationHandler()

public required init?(coder: NSCoder) {
super.init(coder: coder)
setup()
}

public init() {
super.init(frame: .zero)
setup()
}

private func setup() {
CAPLog.enableLogging = configuration.loggingEnabled
logWarnings(for: configDescriptor)

if configDescriptor.instanceType == .fixed { updateBinaryVersion() }

addSubview(webView)
webView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
webView.topAnchor.constraint(equalTo: topAnchor),
webView.bottomAnchor.constraint(equalTo: bottomAnchor),
webView.leadingAnchor.constraint(equalTo: leadingAnchor),
webView.trailingAnchor.constraint(equalTo: trailingAnchor)
])

guard FileManager.default.fileExists(atPath: bridge.config.appStartFileURL.path) else { fatalLoadError() }
capacitorDidLoad()

let url = bridge.config.appStartServerURL
CAPLog.print("⚡️ Loading app at \(url.absoluteString)")
capacitorBridge.webViewDelegationHandler.willLoadWebview(webView)
_ = webView.load(URLRequest(url: url))
}

public lazy final var isNewBinary: Bool = {
if let curVersionCode = Bundle.main.infoDictionary?["CFBundleVersion"] as? String,
let curVersionName = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String {
if let lastVersionCode = UserDefaults.standard.string(forKey: "lastBinaryVersionCode"),
let lastVersionName = UserDefaults.standard.string(forKey: "lastBinaryVersionName") {
return (curVersionCode.isEqual(lastVersionCode) == false || curVersionName.isEqual(lastVersionName) == false)
}
}
return false
}()

open func instanceDescriptor() -> InstanceDescriptor {
let descriptor = InstanceDescriptor.init()
if !isNewBinary && !descriptor.cordovaDeployDisabled {
if let persistedPath = UserDefaults.standard.string(forKey: "serverBasePath"), !persistedPath.isEmpty {
if let libPath = NSSearchPathForDirectoriesInDomains(.libraryDirectory, .userDomainMask, true).first {
descriptor.appLocation = URL(fileURLWithPath: libPath, isDirectory: true)
.appendingPathComponent("NoCloud")
.appendingPathComponent("ionic_built_snapshots")
.appendingPathComponent(URL(fileURLWithPath: persistedPath, isDirectory: true).lastPathComponent)
}
}
}
return descriptor
}

/**
Allows any additional configuration to be performed. The `webView` and `bridge` properties will be set by this point.

- Note: This is called before the webview has been added to the view hierarchy. Not all operations may be possible at
this time.
*/
open func capacitorDidLoad() {
}

open func loadInitialContext(_ userContentController: WKUserContentController) {
CAPLog.print("in loadInitialContext base")
}
}

extension CAPWebView {

open func webViewConfiguration(for instanceConfiguration: InstanceConfiguration) -> WKWebViewConfiguration {
let webViewConfiguration = WKWebViewConfiguration()
webViewConfiguration.allowsInlineMediaPlayback = true
webViewConfiguration.suppressesIncrementalRendering = false
webViewConfiguration.allowsAirPlayForMediaPlayback = true
webViewConfiguration.mediaTypesRequiringUserActionForPlayback = []
if let appendUserAgent = instanceConfiguration.appendedUserAgentString {
if let appName = webViewConfiguration.applicationNameForUserAgent {
webViewConfiguration.applicationNameForUserAgent = "\(appName) \(appendUserAgent)"
} else {
webViewConfiguration.applicationNameForUserAgent = appendUserAgent
}
}
return webViewConfiguration
}

private func createWebView(with configuration: InstanceConfiguration, assetHandler: WebViewAssetHandler, delegationHandler: WebViewDelegationHandler) -> WKWebView {
// set the cookie policy
HTTPCookieStorage.shared.cookieAcceptPolicy = HTTPCookie.AcceptPolicy.always
// setup the web view configuration
let webViewConfig = webViewConfiguration(for: configuration)
webViewConfig.setURLSchemeHandler(assetHandler, forURLScheme: configuration.localURL.scheme ?? InstanceDescriptorDefaults.scheme)
webViewConfig.userContentController = delegationHandler.contentController
// create the web view and set its properties
loadInitialContext(webViewConfig.userContentController)
let webView = WKWebView(frame: .zero, configuration: webViewConfig)
webView.scrollView.bounces = false
webView.scrollView.contentInsetAdjustmentBehavior = configuration.contentInsetAdjustmentBehavior
webView.allowsLinkPreview = configuration.allowLinkPreviews
webView.configuration.preferences.setValue(true, forKey: "allowFileAccessFromFileURLs")
webView.scrollView.isScrollEnabled = configuration.scrollingEnabled

if let overrideUserAgent = configuration.overridenUserAgentString {
webView.customUserAgent = overrideUserAgent
}

if let backgroundColor = configuration.backgroundColor {
self.backgroundColor = backgroundColor
webView.backgroundColor = backgroundColor
webView.scrollView.backgroundColor = backgroundColor
} else if #available(iOS 13, *) {
// Use the system background colors if background is not set by user
self.backgroundColor = UIColor.systemBackground
webView.backgroundColor = UIColor.systemBackground
webView.scrollView.backgroundColor = UIColor.systemBackground
}

// set our delegates
webView.uiDelegate = delegationHandler
webView.navigationDelegate = delegationHandler
return webView
}

private func logWarnings(for descriptor: InstanceDescriptor) {
if descriptor.warnings.contains(.missingAppDir) {
CAPLog.print("⚡️ ERROR: Unable to find application directory at: \"\(descriptor.appLocation.absoluteString)\"!")
}
if descriptor.instanceType == .fixed {
if descriptor.warnings.contains(.missingFile) {
CAPLog.print("Unable to find capacitor.config.json, make sure it exists and run npx cap copy.")
}
if descriptor.warnings.contains(.invalidFile) {
CAPLog.print("Unable to parse capacitor.config.json. Make sure it's valid JSON.")
}
if descriptor.warnings.contains(.missingCordovaFile) {
CAPLog.print("Unable to find config.xml, make sure it exists and run npx cap copy.")
}
if descriptor.warnings.contains(.invalidCordovaFile) {
CAPLog.print("Unable to parse config.xml. Make sure it's valid XML.")
}
}
}

private func updateBinaryVersion() {
guard isNewBinary else {
return
}
guard let versionCode = Bundle.main.infoDictionary?["CFBundleVersion"] as? String,
let versionName = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String else {
return
}
let prefs = UserDefaults.standard
prefs.set(versionCode, forKey: "lastBinaryVersionCode")
prefs.set(versionName, forKey: "lastBinaryVersionName")
prefs.set("", forKey: "serverBasePath")
prefs.synchronize()
}

private func fatalLoadError() -> Never {
printLoadError()
exit(1)
}

private func printLoadError() {
let fullStartPath = capacitorBridge.config.appStartFileURL.path

CAPLog.print("⚡️ ERROR: Unable to load \(fullStartPath)")
CAPLog.print("⚡️ This file is the root of your web app and must exist before")
CAPLog.print("⚡️ Capacitor can run. Ensure you've run capacitor copy at least")
CAPLog.print("⚡️ or, if embedding, that this directory exists as a resource directory.")
}
}

extension CAPWebView: CAPBridgeDelegate {
internal var bridgedWebView: WKWebView? {
return webView
}

internal var bridgedViewController: UIViewController? {
// search for the parent view controller
var object = self.next
while !(object is UIViewController) && object != nil {
object = object?.next
}
return object as? UIViewController
}
}
9 changes: 9 additions & 0 deletions ios/Capacitor/Capacitor/JSTypes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,15 @@ public protocol JSValueContainer: JSStringContainer, JSBoolContainer, JSIntConta
}

extension JSValueContainer {
public func getValue(_ key: String) -> JSValue? {
return jsObjectRepresentation[key]
}

@available(*, message: "All values returned conform to JSValue, use getValue(_:) instead.", renamed: "getValue(_:)")
public func getAny(_ key: String) -> Any? {
return getValue(key)
}

public func getString(_ key: String) -> String? {
return jsObjectRepresentation[key] as? String
}
Expand Down