I'm relatively new to iOS development, and currently using Swift to develop a prototype ResearchKit app. One of the requirements is to embed a WKWebView
in an ORKTask
consisting of three steps: ORKQuestionStep
, ORKWebViewStep
, ORKCompletionStep
. I can't seem to find much information on how to subclass ORKStep
and ORKStepViewController
using Swift to create a custom step. Can someone guide me in the right direction for how to subclass ORKStep
and ORKStepViewController
to display a WKWebView
using Swift?
Thank you in advance!
Yuan Zhu's answer in ResearchKit-Users mailing list:
I guess you can:
- Create an
ORKActiveStep
and configure it properly.
- Implement the
- (void)taskViewController:(ORKTaskViewController *)taskViewController stepViewControllerWillAppear:(ORKStepViewController *)stepViewController
delegate method.
- Inject your
webView
into the ORKActiveStepViewController
through its customView
property.
I have build my own ORKWebView as follows. I am happy about comments on how to improve that.
1. Subclass an ORKActiveStep
class Javascript_Step : ORKActiveStep {
static func stepViewControllerClass ( ) -> Javascript_Step_View_Controller.Type {
return Javascript_Step_View_Controller.self
}
}
2. Subclass ORKActiveStepViewController
Here you will modify your WebView and it's components.
For quitting the WebView and passing the result, I use Javascript.
import Foundation
import ResearchKit
import WebKit
class Javascript_Step_View_Controller : ORKActiveStepViewController, WKScriptMessageHandler {
weak var webView: WKWebView!
var html : String = "<!DOCTYPE html><html><head> <meta charset=\"UTF-8\"> <title>JS Widget</title> <style type=\"text/css\"> h1 { color: red } </style></head><body> <h1>Test</h1> <input type=\"button\" value=\"Say hello\" onClick=\"Finish_This_Widget('Result is String')\" /> <script type=\"text/javascript\"> function Finish_This_Widget(string) { App.Finish_Widget(string) } </script></body></html>"
// Here the result message posted by Javascript can be handled
func userContentController(userContentController: WKUserContentController, didReceiveScriptMessage message: WKScriptMessage) {
if let result = message.body as? String {
print("userContentController: \(result)")
// With the incoming reult of your webview, the ORKActiveStep timer will be started
start()
}
}
override func viewDidLoad() {
super.viewDidLoad()
// Here you set the function name to call from javascript
let controller = WKUserContentController()
controller.addScriptMessageHandler(self, name: "Finish_Widget")
let config = WKWebViewConfiguration()
config.userContentController = controller
let frame = CGRectMake(20, 20, 200, 200)
let webView = WKWebView(frame: frame, configuration: config)
self.customView = webView
// Set the view constraints (warning message with following unproper settings)
self.customView?.superview!.translatesAutoresizingMaskIntoConstraints = false
view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|-[demoView]-|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: ["demoView": webView])) view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|-[demoView]-|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: ["demoView": webView]))
// Load the html string
webView.loadHTMLString(html, baseURL: NSBundle.mainBundle().bundleURL)
webView.translatesAutoresizingMaskIntoConstraints = false
self.webView = webView
}
}
3. Load the task with your WebViewStep
In step 2 we saw the start()-function call we need be able to finish the step and to switch to the next one. That's why we want that the timer is finished immediately after we started it.
// This functions gives you the Step you can work with finanlly
func Javascript_Widget_Step ( identifier : String, title : String, text : String ) -> ORKActiveStep {
let active_Step = Javascript_Step(identifier: identifier)
// Set title and text for the step (which is optional)
active_Step.title = title
active_Step.text = text
// set stepduration to a minimum -> after javascript function call, step will be finished
active_Step.stepDuration = 0.001
// if this is false, it will not switch to the next step after the javascript call automatically
active_Step.shouldContinueOnFinish = true
return active_Step
}
Open Question
How can I inject the html? If I don't want a static WebView with always the same html, at which point can I set the html string from outside?