Skip to content

Commit

Permalink
feat(iOS): Refactoring configuration (#3759)
Browse files Browse the repository at this point in the history
  • Loading branch information
ikeith authored Nov 24, 2020
1 parent 32206bf commit e2e64c2
Show file tree
Hide file tree
Showing 25 changed files with 759 additions and 206 deletions.
3 changes: 0 additions & 3 deletions ios/Capacitor/.swiftlint.yml

This file was deleted.

75 changes: 63 additions & 12 deletions ios/Capacitor/Capacitor.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions ios/Capacitor/Capacitor/CAPBridgeProtocol.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import WebKit
@objc public protocol CAPBridgeProtocol: NSObjectProtocol {
// MARK: Environment Properties
var viewController: UIViewController? { get }
var config: CAPConfig { get }
var config: InstanceConfiguration { get }
var webView: WKWebView? { get }
var isSimEnvironment: Bool { get }
var isDevEnvironment: Bool { get }
Expand Down Expand Up @@ -34,7 +34,7 @@ import WebKit
@available(*, deprecated, renamed: "userInterfaceStyle")
func getUserInterfaceStyle() -> UIUserInterfaceStyle

@available(*, deprecated, message: "will be moved to config")
@available(*, deprecated, message: "Moved - equivalent is found on config.localURL")
func getLocalUrl() -> String

// MARK: Call Management
Expand Down
145 changes: 76 additions & 69 deletions ios/Capacitor/Capacitor/CAPBridgeViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ public class CAPBridgeViewController: UIViewController, CAPBridgeDelegate, WKUID
public var bridgedViewController: UIViewController? {
return self
}
public let cordovaParser = CDVConfigParser.init()
private var hostname: String?
private var allowNavigationConfig: [String]?
private var basePath: String = ""
Expand All @@ -37,7 +36,6 @@ public class CAPBridgeViewController: UIViewController, CAPBridgeDelegate, WKUID
@objc public var supportedOrientations: [Int] = []

@objc public var startDir = ""
@objc public var config: String?

// Construct the Capacitor runtime
private var capacitorBridge: CapacitorBridge?
Expand All @@ -47,79 +45,91 @@ public class CAPBridgeViewController: UIViewController, CAPBridgeDelegate, WKUID
private var handler: CAPAssetHandler?

override public func loadView() {
let configUrl = Bundle.main.url(forResource: "config", withExtension: "xml")
let configParser = XMLParser(contentsOf: configUrl!)!
configParser.delegate = cordovaParser
configParser.parse()
guard let startPath = self.getStartPath() else {
// load the configuration and set the logging flag
let configDescriptor = InstanceDescriptor.init()
let configuration = InstanceConfiguration(with: configDescriptor)
CAPLog.enableLogging = configuration.enableLogging
logWarnings(for: configDescriptor)

// get the starting path and configure our environment
guard let startPath = self.getStartPath(deployDisabled: configuration.cordovaDeployDisabled) else {
return
}

setStatusBarDefaults()
setScreenOrientationDefaults()
let capConfig = CAPConfig(self.config)

HTTPCookieStorage.shared.cookieAcceptPolicy = HTTPCookie.AcceptPolicy.always
let webViewConfiguration = WKWebViewConfiguration()
let messageHandler = CAPMessageHandlerWrapper()
self.handler = CAPAssetHandler()
self.handler!.setAssetPath(startPath)
var specifiedScheme = CapacitorBridge.defaultScheme
let configScheme = capConfig.getString("server.iosScheme") ?? CapacitorBridge.defaultScheme
// check if WebKit handles scheme and if it is valid according to Apple's documentation
if !WKWebView.handlesURLScheme(configScheme) && configScheme.range(of: "^[a-z][a-z0-9.+-]*$", options: [.regularExpression, .caseInsensitive], range: nil, locale: nil) != nil {
specifiedScheme = configScheme.lowercased()
}
webViewConfiguration.setURLSchemeHandler(self.handler, forURLScheme: specifiedScheme)
webViewConfiguration.userContentController = messageHandler.contentController

configureWebView(configuration: webViewConfiguration)

if let appendUserAgent = (capConfig.getValue("ios.appendUserAgent") as? String) ?? (capConfig.getValue("appendUserAgent") as? String) {
webViewConfiguration.applicationNameForUserAgent = appendUserAgent
}

webView = WKWebView(frame: .zero, configuration: webViewConfiguration)
webView?.scrollView.bounces = false
let availableInsets = ["automatic", "scrollableAxes", "never", "always"]
if let contentInset = (capConfig.getValue("ios.contentInset") as? String),
let index = availableInsets.firstIndex(of: contentInset) {
webView?.scrollView.contentInsetAdjustmentBehavior = UIScrollView.ContentInsetAdjustmentBehavior.init(rawValue: index)!
} else {
webView?.scrollView.contentInsetAdjustmentBehavior = .never
}

webView?.uiDelegate = self
webView?.navigationDelegate = self
if let allowsLinkPreview = (capConfig.getValue("ios.allowsLinkPreview") as? Bool) {
webView?.allowsLinkPreview = allowsLinkPreview
}
webView?.configuration.preferences.setValue(true, forKey: "allowFileAccessFromFileURLs")
// get the web view
let assetHandler = CAPAssetHandler()
assetHandler.setAssetPath(startPath)
webView = prepareWebView(with: configuration, assetHandler: assetHandler)
view = webView

self.handler = assetHandler
// configure the web view
setKeyboardRequiresUserInteraction(false)
// create the bridge
let messageHandler = CAPMessageHandlerWrapper()
capacitorBridge = CapacitorBridge(with: configuration, delegate: self, cordovaConfiguration: configDescriptor.cordovaConfiguration, messageHandler: messageHandler)
}

capacitorBridge = CapacitorBridge(self, messageHandler, capConfig, specifiedScheme)

if let scrollEnabled = bridge!.config.getValue("ios.scrollEnabled") as? Bool {
webView?.scrollView.isScrollEnabled = scrollEnabled
private func prepareWebView(with configuration: InstanceConfiguration, assetHandler: CAPAssetHandler) -> WKWebView {
// set the cookie policy
HTTPCookieStorage.shared.cookieAcceptPolicy = HTTPCookie.AcceptPolicy.always
// setup the web view configuration
let webViewConfiguration = WKWebViewConfiguration()
webViewConfiguration.allowsInlineMediaPlayback = true
webViewConfiguration.suppressesIncrementalRendering = false
webViewConfiguration.allowsAirPlayForMediaPlayback = true
webViewConfiguration.mediaTypesRequiringUserActionForPlayback = []
if let appendUserAgent = configuration.appendedUserAgentString {
webViewConfiguration.applicationNameForUserAgent = appendUserAgent
}

if let backgroundColor = (bridge!.config.getValue("ios.backgroundColor") as? String) ?? (bridge!.config.getValue("backgroundColor") as? String) {
webView?.backgroundColor = UIColor.capacitor.color(fromHex: backgroundColor)
webView?.scrollView.backgroundColor = UIColor.capacitor.color(fromHex: backgroundColor)
webViewConfiguration.setURLSchemeHandler(assetHandler, forURLScheme: configuration.localURL.scheme ?? InstanceDescriptorDefaults.scheme)
let messageHandler = CAPMessageHandlerWrapper()
webViewConfiguration.userContentController = messageHandler.contentController
// create the web view and set its properties
let webView = WKWebView(frame: .zero, configuration: webViewConfiguration)
webView.scrollView.bounces = false
webView.scrollView.contentInsetAdjustmentBehavior = configuration.contentInsetAdjustmentBehavior
webView.uiDelegate = self
webView.navigationDelegate = self
webView.allowsLinkPreview = configuration.allowLinkPreviews
webView.configuration.preferences.setValue(true, forKey: "allowFileAccessFromFileURLs")
webView.scrollView.isScrollEnabled = configuration.enableScrolling
if let overrideUserAgent = configuration.overridenUserAgentString {
webView.customUserAgent = overrideUserAgent
}
if let backgroundColor = configuration.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
webView?.backgroundColor = UIColor.systemBackground
webView?.scrollView.backgroundColor = UIColor.systemBackground
webView.backgroundColor = UIColor.systemBackground
webView.scrollView.backgroundColor = UIColor.systemBackground
}
return webView
}

if let overrideUserAgent = (bridge!.config.getValue("ios.overrideUserAgent") as? String) ?? (bridge!.config.getValue("overrideUserAgent") as? String) {
webView?.customUserAgent = overrideUserAgent
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 getStartPath() -> String? {
private func getStartPath(deployDisabled: Bool = false) -> String? {
var resourcesPath = assetsFolder
if !startDir.isEmpty {
resourcesPath = URL(fileURLWithPath: resourcesPath).appendingPathComponent(startDir).relativePath
Expand All @@ -130,7 +140,7 @@ public class CAPBridgeViewController: UIViewController, CAPBridgeDelegate, WKUID
return nil
}

if !isDeployDisabled() && !isNewBinary() {
if !deployDisabled && !isNewBinary() {
let defaults = UserDefaults.standard
let persistedPath = defaults.string(forKey: "serverBasePath")
if persistedPath != nil && !persistedPath!.isEmpty {
Expand All @@ -145,11 +155,6 @@ public class CAPBridgeViewController: UIViewController, CAPBridgeDelegate, WKUID
return startPath
}

func isDeployDisabled() -> Bool {
let val = cordovaParser.settings.object(forKey: "DisableDeploy".lowercased()) as? NSString
return val?.boolValue ?? false
}

func isNewBinary() -> Bool {
if let plist = Bundle.main.infoDictionary {
if let versionCode = plist["CFBundleVersion"] as? String, let versionName = plist["CFBundleShortVersionString"] as? String {
Expand Down Expand Up @@ -204,12 +209,14 @@ public class CAPBridgeViewController: UIViewController, CAPBridgeDelegate, WKUID
fatalLoadError()
}

hostname = capacitorBridge!.config.getString("server.url") ?? "\(capacitorBridge!.getLocalUrl())"
allowNavigationConfig = bridge!.config.getValue("server.allowNavigation") as? [String]
guard let url = bridge?.config.serverURL else {
CAPLog.print("⚡️ Unable to load app: Missing URL!")
return
}
hostname = url.absoluteString

CAPLog.print("⚡️ Loading app at \(hostname!)...")
let request = URLRequest(url: URL(string: hostname!)!)
_ = webView?.load(request)
_ = webView?.load(URLRequest(url: url))
}

func setServerPath(path: String) {
Expand Down
89 changes: 0 additions & 89 deletions ios/Capacitor/Capacitor/CAPConfig.swift

This file was deleted.

29 changes: 29 additions & 0 deletions ios/Capacitor/Capacitor/CAPInstanceConfiguration.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#ifndef CAPInstanceConfiguration_h
#define CAPInstanceConfiguration_h

@import UIKit;

@class CAPInstanceDescriptor;

NS_SWIFT_NAME(InstanceConfiguration)
@interface CAPInstanceConfiguration: NSObject
@property (nonatomic, readonly, nullable) NSString *appendedUserAgentString;
@property (nonatomic, readonly, nullable) NSString *overridenUserAgentString;
@property (nonatomic, readonly, nullable) UIColor *backgroundColor;
@property (nonatomic, readonly, nonnull) NSArray<NSString*> *allowedNavigationHostnames;
@property (nonatomic, readonly, nonnull) NSURL *localURL;
@property (nonatomic, readonly, nonnull) NSURL *serverURL;
@property (nonatomic, readonly, nonnull) NSDictionary *pluginConfigurations;
@property (nonatomic, readonly) BOOL enableLogging;
@property (nonatomic, readonly) BOOL enableScrolling;
@property (nonatomic, readonly) BOOL allowLinkPreviews;
@property (nonatomic, readonly) BOOL cordovaDeployDisabled;
@property (nonatomic, readonly) UIScrollViewContentInsetAdjustmentBehavior contentInsetAdjustmentBehavior;
@property (nonatomic, readonly, nonnull) NSURL *appLocation;

@property (nonatomic, readonly, nonnull) NSDictionary *legacyConfig DEPRECATED_MSG_ATTRIBUTE("Use direct properties instead");

- (instancetype _Nonnull)initWithDescriptor:(CAPInstanceDescriptor* _Nonnull)descriptor NS_SWIFT_NAME(init(with:));
@end

#endif /* CAPInstanceConfiguration_h */
42 changes: 42 additions & 0 deletions ios/Capacitor/Capacitor/CAPInstanceConfiguration.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#import "CAPInstanceConfiguration.h"
#import <Capacitor/Capacitor-Swift.h>

@implementation CAPInstanceConfiguration

- (instancetype)initWithDescriptor:(CAPInstanceDescriptor *)descriptor {
if (self = [super init]) {
// first, give the descriptor a chance to make itself internally consistent
[descriptor normalize];
// now copy the simple properties
_appendedUserAgentString = descriptor.appendedUserAgentString;
_overridenUserAgentString = descriptor.overridenUserAgentString;
_backgroundColor = descriptor.backgroundColor;
_allowedNavigationHostnames = descriptor.allowedNavigationHostnames;
_enableLogging = descriptor.enableLogging;
_enableScrolling = descriptor.enableScrolling;
_allowLinkPreviews = descriptor.allowLinkPreviews;
_contentInsetAdjustmentBehavior = descriptor.contentInsetAdjustmentBehavior;
_appLocation = descriptor.appLocation;
_pluginConfigurations = descriptor.pluginConfigurations;
_legacyConfig = descriptor.legacyConfig;
// construct the necessary URLs
_localURL = [[NSURL alloc] initWithString:[NSString stringWithFormat:@"%@://%@", descriptor.urlScheme, descriptor.urlHostname]];
if (descriptor.serverURL != nil) {
_serverURL = [[NSURL alloc] initWithString:(descriptor.serverURL)];
}
else {
_serverURL = _localURL;
}
// extract the one value we care about from the cordova configuration
id value = [descriptor.cordovaConfiguration.settings objectForKey:[@"DisableDeploy" lowercaseString]];
if (value != nil && [value isKindOfClass:[NSString class]]) {
_cordovaDeployDisabled = [(NSString*)value boolValue];
}
else {
_cordovaDeployDisabled = false;
}
}
return self;
}

@end
Loading

0 comments on commit e2e64c2

Please sign in to comment.