Skip to content

Commit

Permalink
Update to FHIR R4 and Swift 4.2 (#9)
Browse files Browse the repository at this point in the history
* Update to FHIR R4 and Swift 4.2

* Fix compiler warnings

* Fix code formatting
  • Loading branch information
drdavec authored and xmlmodeling committed Mar 7, 2019
1 parent 1d88f51 commit 19cb9a7
Show file tree
Hide file tree
Showing 15 changed files with 157 additions and 86 deletions.
4 changes: 2 additions & 2 deletions App/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {

var endpointProvider = EndpointProvider()

func application(_ app: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]? = nil) -> Bool {
func application(_ app: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool {
let splitViewController = self.window!.rootViewController as! UISplitViewController
let navigationController = splitViewController.viewControllers[splitViewController.viewControllers.endIndex-1] as! UINavigationController
splitViewController.delegate = navigationController.topViewController as! DetailViewController
Expand All @@ -36,7 +36,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
}

// You need this for Safari and Safari Web View Controller to work
func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
guard let smart = endpointProvider.activeEndpoint?.client else {
window?.rootViewController?.show(error: AppError.noActiveEndpoint, title: "Not Set Up")
return false
Expand Down
2 changes: 1 addition & 1 deletion App/Bundle+Utilities.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,6 @@ extension SMART.Bundle {
*/
func entries<T: Resource>(ofType type: T.Type, typeName name: String) -> [Resource]? {
//print("===> Filtering for type \(T.self) [\(name)]")
return entry?.filter() { return nil != $0.resource && type(of: $0.resource!).resourceType == name }.map() { return $0.resource! }
return entry?.filter() { return nil != $0.resource && Swift.type(of: $0.resource!).resourceType == name }.map() { return $0.resource! }
}
}
18 changes: 3 additions & 15 deletions App/DetailViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,11 @@ import SMART
class DetailViewController: UIViewController, UISplitViewControllerDelegate {

@IBOutlet var detailDescriptionLabel: UILabel?
var masterPopoverController: UIPopoverController? = nil

/// The prescription to show details about
var resource: Resource? {
didSet {
configureView()

if masterPopoverController != nil {
masterPopoverController!.dismiss(animated: true)
}
}
}

Expand All @@ -41,16 +36,16 @@ class DetailViewController: UIViewController, UISplitViewControllerDelegate {
}
}
else {
var style = UIFontTextStyle.headline
var style = UIFont.TextStyle.headline
if #available(iOS 9, *) {
style = .title1
}
let p = NSMutableParagraphStyle()
p.alignment = .center
p.paragraphSpacingBefore = 200.0
let attr = NSAttributedString(string: "Select a FHIR Resource first", attributes: [
NSFontAttributeName: UIFont.preferredFont(forTextStyle: style),
NSParagraphStyleAttributeName: p,
NSAttributedString.Key.font: UIFont.preferredFont(forTextStyle: style),
NSAttributedString.Key.paragraphStyle: p,
])
label.attributedText = attr
}
Expand All @@ -64,16 +59,9 @@ class DetailViewController: UIViewController, UISplitViewControllerDelegate {

// MARK: - Split view

internal func splitViewController(_ splitController: UISplitViewController, willHide viewController: UIViewController, with barButtonItem: UIBarButtonItem, for popoverController: UIPopoverController) {
barButtonItem.title = "Resources" // NSLocalizedString(@"Resources", @"Resources")
self.navigationItem.setLeftBarButton(barButtonItem, animated: true)
self.masterPopoverController = popoverController
}

func splitViewController(_ splitController: UISplitViewController, willShow viewController: UIViewController, invalidating barButtonItem: UIBarButtonItem) {
// Called when the view is shown again in the split view, invalidating the button and popover controller.
self.navigationItem.setLeftBarButton(nil, animated: true)
self.masterPopoverController = nil
}

func splitViewController(_ splitController: UISplitViewController, collapseSecondary secondaryViewController: UIViewController, onto primaryViewController: UIViewController) -> Bool {
Expand Down
2 changes: 1 addition & 1 deletion App/EndpointListViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ class EndpointListViewController: UITableViewController {

class EndpointCell: UITableViewCell {

override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: .subtitle, reuseIdentifier: reuseIdentifier)
}

Expand Down
4 changes: 2 additions & 2 deletions App/EndpointProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -67,13 +67,13 @@ public class EndpointProvider {
CareTeam.self,
Condition.self,
DiagnosticReport.self,
// DocumentReference.self,
DocumentReference.self,
Goal.self,
Immunization.self,
MedicationRequest.self,
Observation.self,
Procedure.self,
ReferralRequest.self
ServiceRequest.self
]
}
}
Expand Down
5 changes: 5 additions & 0 deletions App/Images.xcassets/AppIcon.appiconset/Contents.json
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,11 @@
"idiom" : "ipad",
"filename" : "[email protected]",
"scale" : "2x"
},
{
"idiom" : "ios-marketing",
"size" : "1024x1024",
"scale" : "1x"
}
],
"info" : {
Expand Down
74 changes: 74 additions & 0 deletions App/LenientClient.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
//
// LenientClient.swift
// CareGuide
//
// Created by Dave Carlson on 1/14/19.
// Copyright © 2019 Clinical Cloud Solutions, LLC. All rights reserved.
//

import Foundation
import SMART

/**
A client that defaults to lenient validation, where JSON validation errors are tolerated when receiving a response,
i.e. don't throw upon instantiation, use what's provided.
*/
public class LenientClient: Client {

/// Which options to apply.
open var options: FHIRRequestOption = .lenient

public convenience init(baseURL: URL, settings: OAuth2JSON) {
var sett = settings
if let redirect = settings["redirect"] as? String {
sett["redirect_uris"] = [redirect]
}
if nil == settings["title"] {
sett["title"] = "SMART"
}
let srv = LenientServer(baseURL: baseURL, auth: sett)
self.init(server: srv)
}

/**
Request a JSON resource at the given path from the client's server.

- parameter path: The path relative to the server's base URL to request
- parameter callback: The callback to execute once the request finishes
*/
override public func getJSON(at path: String, callback: @escaping ((_ response: FHIRServerJSONResponse) -> Void)) {
let handler = FHIRJSONRequestHandler(.GET)
handler.options = options
server.performRequest(against: path, handler: handler, callback: { response in
callback(response as! FHIRServerJSONResponse)
})
}

}

/**
A server that defaults to lenient validation, where JSON validation errors are tolerated when receiving a response,
i.e. don't throw upon instantiation, use what's provided.
*/
public class LenientServer: Server {

/// Which options to apply.
open var options: FHIRRequestOption = .lenient

/**
The server can return the appropriate request handler for the type and resource combination.

Request handlers are responsible for constructing an URLRequest that correctly performs the desired REST interaction.

- parameter method: The request method (GET, PUT, POST or DELETE)
- parameter resource: The resource to be involved in the request, if any

- returns: An appropriate `FHIRRequestHandler`, for example a _FHIRJSONRequestHandler_ if sending and receiving JSON
*/
override public func handlerForRequest(withMethod method: FHIRRequestMethod, resource: Resource?) -> FHIRRequestHandler? {
let handler = FHIRJSONRequestHandler(method, resource: resource)
handler.options = options
return handler
}

}
6 changes: 3 additions & 3 deletions App/MasterViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ class MasterViewController: UITableViewController {
}

func spinningSpinner() -> UIActivityIndicatorView {
let activity = UIActivityIndicatorView(activityIndicatorStyle: .gray)
let activity = UIActivityIndicatorView(style: .gray)
activity.isUserInteractionEnabled = false
activity.startAnimating()
return activity
Expand Down Expand Up @@ -178,7 +178,7 @@ class MasterViewController: UITableViewController {
}
}

func cancelPatientSelection() {
@objc func cancelPatientSelection() {
endpointProvider?.cancelPatientSelect()
connectButtonTitle = previousConnectButtonTitle
}
Expand Down Expand Up @@ -272,7 +272,7 @@ class MasterViewController: UITableViewController {

// MARK: - Generic

func dismissModal() {
@objc func dismissModal() {
dismiss(animated: true)
}
}
Expand Down
37 changes: 13 additions & 24 deletions App/ResourceListViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -98,20 +98,6 @@ class ResourceListViewController: UITableViewController {
cell.detailTextLabel?.text = participants.count.description + " participants"
}
}
/*
else if let diagnosticRequestInstance = resource as? DiagnosticRequest {
if let displayName: String = diagnosticRequestInstance.code?.displayString() {
cell.textLabel?.text = displayName
} else {
cell.textLabel?.text = "Diagnostic Request"
}

if let period: Period = diagnosticRequestInstance.occurrencePeriod {
cell.detailTextLabel?.text = period.displayString()
} else {
cell.detailTextLabel?.text = "Unknown request period"
}
} // */

else if let diagnosticReportInstance = resource as? DiagnosticReport {
if let displayName: String = diagnosticReportInstance.code?.displayString() {
Expand Down Expand Up @@ -139,7 +125,7 @@ class ResourceListViewController: UITableViewController {
cell.textLabel?.text = "Document"
}

if let documentDate: DateTime = documentInstance.created {
if let documentDate: FHIRDate = documentInstance.date?.date {
let dateFormatter = DateFormatter()
dateFormatter.dateStyle = .medium
dateFormatter.timeStyle = .none
Expand All @@ -156,7 +142,7 @@ class ResourceListViewController: UITableViewController {
cell.textLabel?.text = "Goal"
}

if let goalDate: FHIRDate = goalInstance.target?.dueDate {
if let goalDate: FHIRDate = goalInstance.startDate {
let dateFormatter = DateFormatter()
dateFormatter.dateStyle = .medium
dateFormatter.timeStyle = .none
Expand All @@ -176,7 +162,7 @@ class ResourceListViewController: UITableViewController {
cell.textLabel?.text = "Immunization"
}

if let immunizationDate: DateTime = immunizationInstance.date {
if let immunizationDate: DateTime = immunizationInstance.occurrenceDateTime {
let dateFormatter = DateFormatter()
dateFormatter.dateStyle = .medium
dateFormatter.timeStyle = .none
Expand Down Expand Up @@ -204,7 +190,8 @@ class ResourceListViewController: UITableViewController {

else if let observationInstance = resource as? Observation {
if let observationValue: String = observationInstance.valueQuantity?.value?.description {
cell.textLabel?.text = observationValue + " " + (observationInstance.valueQuantity?.unit?.string)!
let units = observationInstance.valueQuantity?.unit?.string ?? "(no units)"
cell.textLabel?.text = "\(observationValue) \(units)"
} else if let observationValue: String = observationInstance.valueString?.string {
cell.textLabel?.text = observationValue
} else {
Expand Down Expand Up @@ -235,15 +222,17 @@ class ResourceListViewController: UITableViewController {
}
}

else if let referralRequestInstance = resource as? ReferralRequest {
if let displayName: String = referralRequestInstance.reasonCode?.first?.displayString() {
else if let serviceRequestInstance = resource as? ServiceRequest {
if let displayName: String = serviceRequestInstance.code?.displayString() {
cell.textLabel?.text = displayName
} else {
cell.textLabel?.text = "Referral Request"
cell.textLabel?.text = "Service Request"
}

if let specialty: String = referralRequestInstance.specialty?.displayString() {
cell.detailTextLabel?.text = "For specialty: \(specialty)"
if let requestStatus: String = serviceRequestInstance.status?.rawValue {
cell.detailTextLabel?.text = "Status: " + requestStatus
} else {
cell.detailTextLabel?.text = "Status Not Available"
}
}

Expand All @@ -269,7 +258,7 @@ class ResourceListViewController: UITableViewController {

class ResourceCell: UITableViewCell {

override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: .subtitle, reuseIdentifier: reuseIdentifier)
}

Expand Down
43 changes: 14 additions & 29 deletions Endpoints.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,54 +13,39 @@ import SMART
func configuredEndpoints() -> [Endpoint] {
var endpoints = [Endpoint]()

let hspc = Client(
baseURL: URL(string: "https://api3.hspconsortium.org/fhirconnect14/open")!,
settings: [
"client_name": "SMART on FHIR iOS Sample App",
"redirect": "smartapp://callback",
"logo_uri": "https://avatars1.githubusercontent.com/u/7401080",
])
hspc.authProperties.granularity = .patientSelectNative
hspc.authProperties.embedded = true
endpoints.append(Endpoint(client: hspc, name: "HSPC"))

let hapi = Client(
baseURL: URL(string: "https://fhirtest.uhn.ca/baseDstu3")!,
let hapi = LenientClient(
baseURL: URL(string: "http://hapi.fhir.org/baseR4")!,
settings: [
"client_name": "SMART on FHIR iOS Sample App",
"redirect": "smartapp://callback",
"logo_uri": "https://avatars1.githubusercontent.com/u/7401080",
])
hapi.authProperties.granularity = .patientSelectNative
hapi.authProperties.embedded = true
endpoints.append(Endpoint(client: hapi, name: "HAPI Public"))
endpoints.append(Endpoint(client: hapi, name: "HAPI at fhir.org"))

// Grahame's test server
let grahame = Client(
baseURL: URL(string: "http://fhir3.healthintersections.com.au/open")!,
let hspc = LenientClient(
baseURL: URL(string: "https://api-v5-r4.hspconsortium.org/testr4/open")!,
settings: [
"client_name": "SMART on FHIR iOS Sample App",
"redirect": "smartapp://callback",
"logo_uri": "https://avatars1.githubusercontent.com/u/7401080",
])
grahame.authProperties.granularity = .patientSelectNative
grahame.authProperties.embedded = true
endpoints.append(Endpoint(client: grahame, name: "Health Intersections"))
hspc.authProperties.granularity = .patientSelectNative
hspc.authProperties.embedded = true
endpoints.append(Endpoint(client: hspc, name: "HSPC Sandbox"))

// SMART DSTU-2!! sandbox
// Credentials obtained by registering on the SMART website
let smart = Client(
baseURL: URL(string: "https://fhir-api-dstu2.smarthealthit.org")!,
let fhirorg = LenientClient(
baseURL: URL(string: "http://test.fhir.org/r4")!,
settings: [
"client_id": "my_mobile_app",
"client_name": "SMART on FHIR iOS Sample App",
"redirect": "smartapp://callback",
"logo_uri": "https://avatars1.githubusercontent.com/u/7401080",
])
smart.authProperties.granularity = .patientSelectNative
smart.authProperties.embedded = true
endpoints.append(Endpoint(client: smart, name: "SMART"))

fhirorg.authProperties.granularity = .patientSelectNative
fhirorg.authProperties.embedded = true
endpoints.append(Endpoint(client: fhirorg, name: "Test at FHIR.org"))
return endpoints
}

2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ An **iOS sample app** using SMART on FHIR via our [iOS SMART on FHIR framework](
2. Open the project file `SoF-Demo.xcodeproj` in Xcode 8+.
3. Select an iPhone simulator and press **Run**.

The `master` branch is currently on _Swift 3.0_ and the _STU-3_ (`3.0.0.11832`) version of FHIR ([version `3.0.0` of the SMART framework](https://github.com/smart-on-fhir/Swift-SMART/releases/tag/3.0.0)).
The `master` branch is currently on _Swift 4.2_ and the _R4_ (`4.0.0-a53ec6ee1b`) version of FHIR ([version `4.0.0` of the SMART framework](https://github.com/smart-on-fhir/Swift-SMART/releases/tag/4.0.0)).
Check the `develop` branch for bleeding edge updates, if any, and the [tags](https://github.com/smart-on-fhir/SoF-Demo/releases) for older releases.


Expand Down
Loading

0 comments on commit 19cb9a7

Please sign in to comment.