How to use ORKESerializer in my app? - ios

I am working on developing my first ResearchKit App. I have been watching this video. One of the techniques used that is going to be helpful for me is serializing the results of a survey to JSON. The method used in the video is ORKESerializer.JSONDataForObject(taskResult). He explains that this is not a standard part of researchKit, but it was included in a test app, called ORKTest that is on GitHub.
I set up my taskViewController delegate just like he had it set on the video, like this:
extension ViewController : ORKTaskViewControllerDelegate {
func taskViewController(taskViewController: ORKTaskViewController, didFinishWithReason reason: ORKTaskViewControllerFinishReason, error: NSError?) {
switch reason {
case .Completed:
let taskResult = taskViewController.result
let jsonData = try! ORKESerializer.JSONDataForObject(taskResult)
if let jsonString = NSString(data: jsonData, encoding: NSUTF8StringEncoding) {
print(jsonString)
}
break
case .Failed, .Discarded, .Saved:
break
}
//Handle results with taskViewController.result
// let taskResult = taskViewController.result
taskViewController.dismissViewControllerAnimated(true, completion: nil)
}
}
I am getting this error upon compiling : use of unresolved identifier: ORKESerializer
So in the ORKTest app, in the GitHub files, I found 2 files. One called ORKESerialization.h, and one called ORKESerialization.m. I tried dragging those into my project, as I saw those files in the man's project in the video. And then that also prompted me to create a bridging header file as well, which I also saw in his project.
After doing that I am still getting the same error. The truth is I don't know exactly how to include these serialization packages with my app. Does anyone know how to included the right files so that I can implement this ORKEserialization method?
Thanks!

You need to import ORKESerialization.h in your bridging header:
#import "ORKESerialization.h"

Related

Why is url.bookmarkData returning nil?

We have an implementation with the UIDocumentPickerViewController that looks something like this:
case .openInitialization:
// Setup UIDocumentPicker.
if #available(iOS 14, *) {
documentsPicker = UIDocumentPickerViewController(forOpeningContentTypes: [
UTType.text,
UTType.utf8PlainText,
UTType.flatRTFD,
UTType.pdf])
} else {
documentsPicker = UIDocumentPickerViewController(documentTypes: [
String(kUTTypeText),
String(kUTTypeUTF8PlainText),
String(kUTTypeFlatRTFD),
String(kUTTypePDF)], in: .open)
}
Everything works great and we can select a document. When we select a document we get a document url but in some cases (especially with one drive) we get issues when we want to turn the url into a bookmark. Following code returns nil:
guard let bookmark = try? url.bookmarkData(options: .minimalBookmark, includingResourceValuesForKeys: nil, relativeTo: nil) else { return }
Do anyone have an idea to why this is happening? Or what we can do to get it to work without returning nil?
Edit:
We've tryed to add try catch and we got following error which doesn't quite help much: Error Domain=NSCocoaErrorDomain Code=260 (file doesn't exist).
Edit 2:
So if I open from archive directly into our app it works no issues at all. But we still need to work from UIDocumentPickerViewController.
Also for some reasons files unlocked this way will just work from UIDocumentPickerViewController afterward.
Files can also be opened from onedrive and from there be opened in another app (ours). But this does't and gives a file does not exist error as well.
Edit 3:
So I've tested and read a ton. I can tell that following will return false for some files picked by documentpicker:
var exist = FileManager.default.fileExists(atPath: url.path)
But again if I open the file just once from iOS archive app it will work perfectly fine afterward. If there just were some way to tell it to update/download like apples does.
Edit 4:
I've made a sample project demonstrating the problem at github .
I answered a similar question here: PDFKit doesn’t work on iPads while works fine on a simulator [iOS, Swift]
Can you check if wrapping your url in a security access scope helps?:
url.startAccessingSecurityScopedResource()
print(FileManager.default.fileExists(atPath: url.path))
url.stopAccessingSecurityScopedResource()
The above should print true. This is because these API's access files outside of the applications sandbox.
See: https://developer.apple.com/documentation/uikit/view_controllers/providing_access_to_directories
Used a technical ticket for apple and they came with a solution :D
NSFileCoordinator().coordinate(readingItemAt: url, options: .withoutChanges, error:&err, byAccessor: { (newURL: URL) -> Void in
do {
let bookmark = try newURL.bookmarkData()
} catch let error {
print("\(error)")
}
})
if let err = err {
print(err.localizedDescription)
}

NSAttributed String missing return value

I'm trying to make this function inside a VC into a function within an extension (because I need to access it in multiple VC's so I'm trying to return the attributedStringWithRtf so i can use it elsewhere.
func populateTextViewWithCurrentScene() {
let fileURL = getFileURL()
do {
let attributedStringWithRtf:NSAttributedString = try NSAttributedString(
url: fileURL,
options: [.documentType: NSAttributedString.DocumentType.rtf],
documentAttributes: nil
)
self.textViewOutlet.attributedText = attributedStringWithRtf
}
catch {
print("failed to populate text view with current scene with error: \(error)")
}
}
So far, I've tried this, following the guide here How could I create a function with a completion handler in Swift? and I've also tried a version declaring a var before the function. The error I'm getting on the below is Cannot call value of non-function type 'NSAttributedString'.
I know there are quite a few questions about this sort of thing but a lot are for old versions of Swift
func populateTextViewWithCurrentScene(rtfString: NSAttributedString) -> Void {
let fileURL = getFileURL()
do {
let rtfString:NSAttributedString = try NSAttributedString(
url: fileURL,
options: [.documentType: NSAttributedString.DocumentType.rtf],
documentAttributes: nil
)
}
catch {
print("failed to populate text view with current scene with error: \(error)")
}
rtfString()
}
I went ahead and created an extension of UIViewController that should provide what you are looking for. Comments are included for each line to explain the decisions that I made.
Feel free to comment if some part is unclear, or does not work as expected.
import UIKit
// You mentioned wanting to extend your viewcontroller
// so I extend UIViewController to support that
extension UIViewController {
// Returns an optional NSAttributedString, based upon successfully loading the file contents
func loadString() -> NSAttributedString? {
do {
// Everything is cleaned up into a single return command,
// including the getFileURL, which can be used as a parameter instead
// of creating a variable for it
return try NSAttributedString(url: getFileURL(),
options: [.documentType: NSAttributedString.DocumentType.rtf],
documentAttributes: nil)
} catch {
// If this fails, use your existing print command
print("failed to populate text view with current scene with error: \(error)")
// and then return nil to indicate that nothing was loaded
return nil
}
}
}
This is building off of the comments below from you and rmaddy.
As referenced in my original comment, the solution was not consolidating the try & return, that was simply to streamline the code.
You can look at the function this way:
"I want to try and open a file that is located at getFileURL(), and
would like to use some options I specify with my options: parameter.
Since this action can fail, Xcode makes me use try.
Assuming that this file is successfully opened, then return the
contents back to the caller in the form of an NSAttributedString.
However, if this fails, print out a message telling me why it failed
and then return a nil to indicate that no data is returned."

URLSessions or AlamoFire for downloading a webpage that updates once a day?

One of my view controllers decodes and prints html from a web page. I've done searches on stackoverflow and in example project on github and it seems that people are using Alamofire with Swiftsoup to do this.
I'm a beginner but I am trying to understand why I would need AlamoFire when I can just use URLSessions? Is it better to use Alamofire?
My use case is simple, I think. If I use Alamofire,
let getURL = "https://www.someurl.com/extension"
Alamofire.request(getURL, method: .post, parameters: nil, encoding: URLEncoding.default).validate(contentType: ["application/x-www-form-urlencoded"]).response { (response) in
if let data = response.data, let utf8Text = String(data: data, encoding: .utf8) {
do {
parseHTML()
}
}
}
If I use URLSessions, I think it would like this:
let httpURL = URL(string: "https://www.someurl.com/extension")!
let httpTask = URLSession.shared.dataTask(with: httpURL) {
(data, response, error) in
guard let validData = data, error == nil else {
DispatchQueue.main.async(execute: {
print("Error getting paragraph\n") })
return
}
var results = String(data: data!, encoding: String.Encoding.utf8) ?? "Unable to read Paragraph HTML\n"
DispatchQueue.main.async(execute: {
print("Correctly read from Paragraph HTML\n")
parseHTML()
})
}
DispatchQueue.global(qos: DispatchQoS.QoSClass.background).async(execute: {
httpTask.resume()
})
Side question: Is Swiftsoup the go to for decoding HTML? Is there something built in that can be used instead?
Alamofire/AFNetworking (AFNetworking is the objective-c version) gained traction as an alternative to Apple's NSURLConnection class, that was much more low level and involved a lot of boilerplate code. It was not as easy to establish a download task or anything with NSURLConnection, AFNetworking (at the time) made it easier to perform the tasks like in your question without having to write too much code.
Around iOS7, Apple released NSURLSession to replace NSURLConnection, which made it quite similar to how AlamoFire do things. At this point personally, I feel that using NSURLSession/URLSession is fine and straightforward enough. Maybe AlamoFire is a bit easier to use but overall they are similar. The only times I end up moving towards AlamoFire these days is when I face some type of limitation.
So tl;dr, pre iOS7, AFNetworking was a much easier and straightforward way of working with download tasks. Post iOS7 URLSessions became easier to work with and set up.

Quick Look Preview Extension iOS preparePreviewOfFile(at:completionHandler:)

I'm trying to write a simple Quick Look Preview Extension for my UIDocument-based iOS app.
The problem is that in my implementation of preparePreviewOfFile(at:completionHandler:) my attempt to open the UIDocument based on the URL I'm being handed is failing. I instantiate my document with the file URL and call open(completionHandler:) but I'm not getting any data, and I'm seeing a console message that the file coordinator has crashed.
All of this works fine in my actual app; it's just the Quick Look Preview Extension implementation that's having trouble. Is there something special I have to do to open a UIDocument from inside a Quick Look Preview Extension? Apple doesn't provide any sample code; in WWDC 2017 video 229 they just gloss over the whole thing.
EDIT: Curiouser and curiouser. I created a simplified testbed app that displays a Quick Look preview with UIDocumentInteractionController, along with my custom Quick Look Preview Extension. On the Simulator, the preview works! On the device, it doesn't. It looks like, when I tell my document to open, its load(fromContents:ofType) is never even called; instead, we are getting a pair of error messages like this:
The connection to service named com.apple.FileCoordination was invalidated.
A process invoked one of the -[NSFileCoordinator coordinate...] methods but filecoordinationd crashed. Returning an error.
I was able to work around the issue by not calling open on my UIDocument. Instead, I call read directly, on a background thread, like this:
func preparePreviewOfFile(at url: URL, completionHandler handler: #escaping (Error?) -> Void) {
DispatchQueue.global(qos: .background).async {
let doc = MyDocument(fileURL: url)
do {
try doc.read(from: url)
DispatchQueue.main.async {
// update interface here!
}
handler(nil)
} catch {
handler(error)
}
}
}
I have no idea if that's even legal. You'd think that just reading the document straight in, without the use of a file coordinator, would be Bad. But it does seem to work!
I found yet another workaround, using NSFileCoordinator and calling load manually to get the UIDocument to process the data:
let fc = NSFileCoordinator()
let intent = NSFileAccessIntent.readingIntent(with: url)
fc.coordinate(with: [intent], queue: .main) { err in
do {
let data = try Data(contentsOf: intent.url)
let doc = MyDocument(fileURL: url)
try doc.load(fromContents: data, ofType: nil)
self.lab.text = doc.string
handler(nil)
} catch {
handler(error)
}
}
Again, whether that's legal, I have no idea, but I feel better about it than calling read directly, because at least I'm passing through a file coordinator.

Swift 2.0 Call Can Throw but is not marked with try

I'm trying to update the code in my app after the update to XCode 7 and it looks like I'm going to have to go through a serious learning curve again just to catch up. What am I doing wrong in the code below?
Is if let still being used?
I am so not familiar with try/catch outside of C#. I don't know how to use it in the context of swift and it'd be great to find an easy to understand guide that doesn't assume that I ever knew Objective C or have ever come across this before.
Use this instead:
do {
let json = try NSJSONSerialization.JSONObjectWithData(...)
return json
} catch let error as NSError {
print("JSON Error: \(error.localizedDescription)")
}
You are calling a method that throws a Swift error and as such, it needs to be marked with try.
do
{
let json = try NSJSONSerializer.JSONObjectWithData(...)
return json
}
catch
{
// By default the catch clause defines the variable error as whatever ws thrown
print("Error is \(error)")
return nil
}
Is pretty much what you want.
In case of my understanding is
let result: AnyObject? = try! NSJSONSerialization.JSONObjectWithData(jsonData!, options: NSJSONReadingOptions.MutableContainers)

Resources