I'm currently creating a program that wants to utilize ResearchKit. Before getting into survey questions, I will need to require consent. I am using the Ray Wenderlich (https://www.raywenderlich.com/1820-researchkit-tutorial-with-swift-getting-started) tutorial and have this code set up. I've already tried to go on with the survey part of my program, and I can access it without even going through consent.
import ResearchKit
public var ConsentTask: ORKOrderedTask {
var ctr = 0
let Document = ORKConsentDocument()
Document.title = "Consent"
//section types
let sectionTypes: [ORKConsentSectionType] = [
.overview,
.dataGathering,
.privacy
]
let consentSections: [ORKConsentSection] = sectionTypes.map { contentSectionType in
let consentSection = ORKConsentSection(type: contentSectionType)
if ctr < sectionTypes.count {
if ctr == 0 {
consentSection.summary = "Summary"
consentSection.content = "Content"
}
else if ctr == 1 { //Data Gathering
consentSection.summary = "Summary"
consentSection.content = "Content"
}
else if ctr == 2 { //Privacy
consentSection.summary = "Summary"
consentSection.content = "Content"
}
ctr = ctr + 1
}
return consentSection
}
Document.sections = consentSections
Document.addSignature(ORKConsentSignature(forPersonWithTitle: nil, dateFormatString: nil, identifier: "UserSignature"))
var steps = [ORKStep]()
let visualConsentStep = ORKVisualConsentStep(identifier: "VisualConsent", document: Document)
steps += [visualConsentStep]
let signature = Document.signatures!.first! as ORKConsentSignature
let reviewConsentStep = ORKConsentReviewStep(identifier: "Review", signature: signature, in: Document)
reviewConsentStep.text = "Review the consent"
reviewConsentStep.reasonForConsent = "I agree"
steps += [reviewConsentStep]
//Completion
let completionStep = ORKCompletionStep(identifier: "CompletionStep")
completionStep.title = "Welcome"
completionStep.text = "Thank you for helping!"
steps += [completionStep]
return ORKOrderedTask(identifier: "ConsentTask", steps: steps)
}
And in my view controller, I have
#IBAction func consentTask(_ sender: Any) {
if consentDone == false {
let taskViewController = ORKTaskViewController(task: ConsentTask, taskRun: nil)
taskViewController.delegate = self
present(taskViewController, animated: true, completion: nil)
}
else if consentDone == true {
//would they like to leave the study
}
}
With my efforts of putting a consentDone flag here.
func taskViewController(_ taskViewController: ORKTaskViewController, didFinishWith reason: ORKTaskViewControllerFinishReason, error: Error?) {
taskViewController.dismiss(animated: true, completion: {() in self.consentDone = true})
print(consentDone)
}
However, what happens is that if the user presses cancel on the top right or done at the very end of the consent, it will trigger this always. Is there a way to be able to make sure that a block of code is executed only after everything is finished? Ideally, after this, I would like to make this a flag that the user has already finished the consent. I will redirect the user to a different page every time afterwards until the user leaves the study.
After some trial and error, I found the answer here: https://github.com/ResearchKit/ResearchKit/issues/919
By knowing that the signature of the user means that the user has finished the form, we can do
if result.identifier == "UserSignature"{
print(result)
let consentDoneAnswerResult = result as! ORKConsentSignatureResult
let consentDone = consentDoneAnswerResult.consented
print(consentDone)
}
And this gives consentDone as true when the form is done, not canceled.
Related
I'm trying to create an Envelop and sign it using online mode.
At first, I logged in to my account
#IBAction private func signDocument(_ sender: UIButton) {
guard let hostURL = URL(string: Environment.current.docuSignHost) else { return }
isLoading = true
DSMManager.login(withEmail: Environment.current.docuSignUserID,
password: Environment.current.docuSignPass,
integratorKey: Environment.current.docuSignIntegratorKey,
host: hostURL) { [weak self] info, error in
self?.isLoading = false
if let error = error {
self?.error = error
} else {
self?.showDocuSign(info: info)
}
}
}
// MARK: - Helpers
private func showDocuSign(info: DSMAccountInfo?) {
guard let info = info else { return }
envelopManager.perform(with: info, presentingController: self)
}
After that, I created my test Envelop:
final class EnvelopeManager {
private let envelopesManager = DSMEnvelopesManager()
private let templateManager = DSMTemplatesManager()
// MARK: - Lifecycle
func perform(with config: DSMAccountInfo, presentingController: UIViewController) {
guard let documentURL = R.file.tgkCapitalPortfolioBAgreementPdf(),
let documentData = try? Data(contentsOf: documentURL) else { return }
let envelopDefinition = DSMEnvelopeDefinition()
envelopDefinition.envelopeName = "Some name"
envelopDefinition.emailSubject = "Please Sign Envelope on Document"
envelopDefinition.emailBlurb = "Hello, Please sign my Envelope"
let document = DSMDocument()
document.name = R.file.tgkCapitalPortfolioBAgreementPdf.name
document.documentId = "1"
document.documentBase64 = documentData.base64EncodedString()
envelopDefinition.documents = [document]
let signHere = DSMSignHere()
signHere.documentId = document.documentId
signHere.pageNumber = 1
signHere.recipientId = "1"
signHere.anchorXOffset = 100
signHere.anchorYOffset = 100
signHere.tabId = "1"
let tabs = DSMTabs()
tabs.signHereTabs = [signHere]
let signer = DSMSigner()
signer.canSignOffline = false
signer.email = config.email
signer.name = config.userName
signer.recipientId = "1"
signer.tabs = tabs
let signers: [DSMSigner] = [signer]
let recipients = DSMRecipients()
recipients.signers = signers
envelopDefinition.recipients = recipients
envelopDefinition.status = "sent"
envelopesManager.composeEnvelope(with: envelopDefinition, signingMode: .online) { [weak self] envelopID, error in
if let envelopID = envelopID {
print(envelopID)
self?.presentSigning(presenter: presentingController,
envelopeID: envelopID)
} else {
print(error.localizedDescription)
}
}
}
private func presentSigning(presenter: UIViewController, envelopeID: String) {
envelopesManager.presentSigning(withPresenting: presenter,
envelopeId: envelopeID,
animated: true) { (viewController, error) in
if let viewController = viewController {
print(viewController)
}
if let error = error {
print(error.localizedDescription)
}
}
}
}
But here
envelopesManager.composeEnvelope(with: envelopDefinition, signingMode: .online) { [weak self] envelopID, error in
if let envelopID = envelopID {
print(envelopID)
self?.presentSigning(presenter: presentingController,
envelopeID: envelopID)
} else {
print(error.localizedDescription)
}
}
I have an error:
Envelope creation online is not supported at this moment. Please try
offline mode
When I switched to offline mode I can't use the method
envelopesManager.presentSigning(withPresenting:, enveloped: , animated:, completeion)
Because it works only for envelops which were created in the online mode. And in my case completion block is not executed.
How to resolve this issue? How I can create an envelope in online mode and sign it? What I'm doing wrong? I just want to select my pdf which contains in the project bundle and in some position add the sign.
The sample project which was provided here doesn't match my requirements. Because there is using a template from server and creating envelop via presentComposeEnvelopeController method for choosing document and etc.
XCode 12.4, iOS 13/14, DocuSign 2.4.1 included in the project via CocoaPods.
Edit1
I have updated my perform method:
func perform(with config: DSMAccountInfo, presentingController: UIViewController) {
guard let documentURL = R.file.tgkCapitalPortfolioBAgreementPdf(),
let documentData = try? Data(contentsOf: documentURL) else { return }
let envelopDefinition = DSMEnvelopeDefinition()
envelopDefinition.envelopeName = "Some name"
envelopDefinition.emailSubject = "Please Sign Envelope on Document"
envelopDefinition.emailBlurb = "Hello, Please sign my Envelope"
let document = DSMDocument()
document.name = R.file.tgkCapitalPortfolioBAgreementPdf.name
document.documentId = "1"
document.documentBase64 = documentData.base64EncodedString()
envelopDefinition.documents = [document]
let signHere = DSMSignHere()
signHere.documentId = document.documentId
signHere.pageNumber = 1
signHere.recipientId = "1"
signHere.frame = .init(originX: 100,
originY: 100,
width: 100,
height: 100,
originYOffsetApplied: 50)
signHere.tabId = "1"
let tabs = DSMTabs()
tabs.signHereTabs = [signHere]
let signer = DSMSigner()
signer.email = config.email
signer.name = config.userName
signer.recipientId = "1"
signer.tabs = tabs
let signers: [DSMSigner] = [signer]
let recipients = DSMRecipients()
recipients.signers = signers
envelopDefinition.recipients = recipients
envelopDefinition.status = "created"
envelopesManager.composeEnvelope(with: envelopDefinition, signingMode: .offline) { [weak self] envelopID, error in
if let envelopID = envelopID {
print(envelopID)
self?.presentSigning(presenter: presentingController,
envelopeID: envelopID)
} else {
print(error.localizedDescription)
}
}
}
and presentSigning method too:
private func presentSigning(presenter: UIViewController, envelopeID: String) {
envelopesManager.resumeSigningEnvelope(withPresenting: presenter,
envelopeId: envelopeID) { (viewController, error) in
if let viewController = viewController {
print(viewController)
}
if let error = error {
print(error.localizedDescription)
}
}
}
But now I have got the error in the presentSigning method: Envelope is ready for sync and can not be resumed for signing.
And any screen with my pdf document was not shown. How to resolve it? How I can preview this document and after that add the ability for a user to sign it?
Solution
The working code of the EnvelopManager class:
final class EnvelopeManager {
private let envelopesManager = DSMEnvelopesManager()
private let templateManager = DSMTemplatesManager()
// MARK: - Lifecycle
func sync() {
envelopesManager.syncEnvelopes()
}
func perform(with config: DSMAccountInfo, presentingController: UIViewController) {
guard let path = Bundle.main.path(forResource: R.file.tgkCapitalPortfolioBAgreementPdf.name, ofType: "pdf") else { return }
let envelopDefinition = DSMEnvelopeDefinition()
envelopDefinition.envelopeName = "Some name"
envelopDefinition.emailSubject = "Please Sign Envelope on Document"
envelopDefinition.emailBlurb = "Hello, Please sign my Envelope"
let builder = DSMDocumentBuilder()
builder.addDocumentId("1")
builder.addName(R.file.tgkCapitalPortfolioBAgreementPdf.name)
builder.addFilePath(Bundle.main.path(forResource: R.file.tgkCapitalPortfolioBAgreementPdf.name,
ofType: "pdf")!)
let document = builder.build()
envelopDefinition.documents = [document]
let signHere = DSMSignHere()
signHere.documentId = document.documentId
signHere.pageNumber = 1
signHere.recipientId = "1"
signHere.frame = .init(originX: 100,
originY: 100,
width: 100,
height: 100,
originYOffsetApplied: 50)
signHere.tabId = "1"
let tabs = DSMTabs()
tabs.signHereTabs = [signHere]
let signer = DSMSigner()
signer.email = config.email
signer.name = config.userName
signer.userId = config.userId
signer.clientUserId = config.userId
signer.routingOrder = 1
signer.recipientId = "1"
signer.tabs = tabs
let signers: [DSMSigner] = [signer]
let recipients = DSMRecipients()
recipients.signers = signers
envelopDefinition.recipients = recipients
envelopDefinition.status = "created"
envelopesManager.composeEnvelope(with: envelopDefinition, signingMode: .offline) { [weak self] envelopID, error in
if let envelopID = envelopID {
print(envelopID)
self?.presentSigning(presenter: presentingController,
envelopeID: envelopID)
} else {
print(error.localizedDescription)
}
}
}
private func presentSigning(presenter: UIViewController, envelopeID: String) {
envelopesManager.resumeSigningEnvelope(withPresenting: presenter,
envelopeId: envelopeID) { (viewController, error) in
if let viewController = viewController {
print(viewController)
}
if let error = error {
print(error.localizedDescription)
}
}
}
}
After you will have signed the document do not forget to call the sync method on the top view controller
But now I have got the error in the presentSigning method: Envelope is ready for sync and can not be resumed for signing.
Envelope is ready for sync and can not be resumed for signing. denotes the state that envelope has no local signers awaiting signing. This could happen because of a few reasons.
Taking a look at the adding recipient section of guide, this adds a Remote Signer to local envelope.
// Create an envelope recipient with name and email and assign an id and type with routing order
DSMEnvelopeRecipient *recipient = [[[[[[DSMRecipientBuilder builderForType: DSMRecipientTypeSigner]
addRecipientId: #"1"]
addSignerName: #"Jane Wood"]
addSignerEmail: #"JaneWood#docusign.com"]
addRoutingOrder: 1]
build];
No local signers have been added to capture signatures. If you need to add a local signer they could represent following cases:
SDK authenticated account could be used as a signer (DSMRecipientTypeSigner). In the example: #"JaneWood#docusign.com" is logged in with SDK with routingOrder (or SigningOrder) of 1.
SDK authenticated account could be used as a host to an in-person-signer (DSMRecipientTypeInPersonSigner). The following example would add an in-person-signer for #"JaneWood#docusign.com" as host:
// Create an envelope recipient with name and email and assign an id and type with routing order
DSMEnvelopeRecipient *recipient = [[[[[[[DSMRecipientBuilder builderForType: DSMRecipientTypeInPersonSigner]
addRecipientId: #"1"]
addHostName: #"Jane Wood"]
addHostEmail: #"JaneWood#docusign.com"]
addSignerName: #"IPS - John Local Doe"]
addRoutingOrder: 1]
build];
Only remote signers (non-local SDK account) have been added to envelope: Invoking syncEnvelopes on such envelopes will send documents and email the remote-signers to complete signing.
Note: All of the local signers have already completed signing: You'd reach this case once all local signatures have been captured, and using syncEnvelopes would send envelope to DocuSign servers.
#igdev: Thanks for adding code snippets and details explaining your approach.
How I can create an envelope in online mode and sign it?
As of current SDK version, creating online envelopes with EnvelopeDefinition is not yet supported.
Following method is only supported for remote-envelopes that have been already created (via DocuSign API or Web) and accessible on the DocuSign account under use by Native-SDK.
envelopesManager.presentSigning(withPresenting:, enveloped: , animated:, completion)
I just want to select my pdf which contains in the project bundle and in some position add the sign.
Compose flow based envelope creation would enable you to use the PDF from the bundle, position tabs and present signing in the offline mode. Here is a guide (Compose Envelope) that lists steps with sample code to get the desired result. Following snippet from compose envelope guide shows how such envelope can be presented for signing:
if (envelope) {
// Compose the envelope to automatically cache it
[self.envelopesManager composeEnvelopeWithEnvelopeDefinition: envelope
signingMode: DSMSigningModeOffline
completion: ^(NSString * _Nullable envelopeId, NSError * _Nonnull error) {
// error checks in case envelope compose failed. Also use notifications for caching related events.
if (error) { ... }
// Resume the envelope to start the signing process
[self.envelopesManager resumeSigningEnvelopeWithPresentingController: self
envelopeId: envelopeId
completion: ^(UIViewController * _Nullable presentedController, NSError * _Nullable error) {
// error checks in case UI presentation failed. Use notifications for other events.
if (error) { ... }
}];
}
];
}
The sample code prior to update (thanks for adding it) to use the tab-frame instead of anchors is correct step as anchors aren't supported in offline mode by the SDK. In order to avoid such issues, it's best to use the EnvelopeBuilder (which in turn uses Recipient and TabBuilder) to create tabs for individual recipients and assign correct frames (e.g. signHere.frame = CGRect(x: 100, y: 100, width: 50, height: 40)). Using EnvelopeBuilder also ensures that custom envelope data goes through validation process.
/*!
* #discussion add frame of the tab (e.g. CGFrameMake(100, 100, 50, 40) for Sign Tab) and return builder.
* #param frame a frame to draw tab within
*/
- (DSMTabBuilder *)addFrame:(CGRect)frame;
Check answer below for envelope ready for sync state:
I have submitted my app for review, but apple team regularly rejecting my app and telling they are getting exception. but i have tried it in multiple device and i am not getting any exception. they are telling they are getting exception while entering otp. i have given crash report provided by apple team below.
func validateOTP() -> Void {
var strOTP = String(format: "%#%#%#%#", txtFieldFirst.text ?? "1",txtFieldSecond.text ?? "0",txtFieldThird.text ?? "1",txtFieldFourth.text ?? "0")
strOTP = strOTP.trimmingCharacters(in: .whitespaces)
print("OTP:",strOTP)
if strOTP.count == 4 {
if isServerReachable() {
var dict: [String : Any] = [:]
dict["mobile"] = Defaults().strUserMobileNumber
dict["otp"] = strOTP
dict["device_id"] = StringConstant.Device.Id
dict["registration_id"] = Defaults().strDeviceToken
dict["device_type"] = StringConstant.Device.DeviceType
SVProgressHUD.show()
if strLoginType == StringConstant.kUserTypeStudent {
CCAParserLayer.callStudentLogin(dict, handler: { strStatus, strMessage, dictResponse in
SVProgressHUD.dismiss()
if (strStatus == "YES") {
let json = JSON(dictResponse!)
Defaults().strUserType = StringConstant.kUserTypeStudent
Defaults().strStudentId = json["student_data"][0]["student_id"].stringValue
Defaults().strUserId = json["student_data"][0]["user_id"].stringValue
Defaults().strStudentName = json["student_data"][0]["student_name"].stringValue
Defaults().strStudentMobile = json["student_data"][0]["student_mobile"].stringValue
Defaults().isLoggedIn = true
self.performSegue(withIdentifier: "otpToStudentDashboard", sender: self)
}else{
self.showToastAlert(strMessage ?? StringConstant.kErrorMsg)
}
})
}else{
CCAParserLayer.callCoachLogin(dict, handler: { strStatus, strMessage, dictResponse in
SVProgressHUD.dismiss()
if (strStatus == "YES") {
let json = JSON(dictResponse!)
Defaults().strUserType = StringConstant.kUserTypeCoach
Defaults().strUserId = json["user_id"].stringValue
Defaults().isLoggedIn = true
self.performSegue(withIdentifier: "otpToCoachDashboard", sender: self)
}else{
self.showToastAlert(strMessage ?? StringConstant.kErrorMsg)
}
})
}
}else{
showToastAlert(StringConstant.kNoInternet)
}
}
}
You can find crash report here : https://iosapps-ssl.itunes.apple.com/itunes-assets/Purple123/v4/c8/b4/f4/c8b4f459-268d-9e4f-41dc-e1a1debd59eb/attachment-14470021504513854757crashlog-F30F5C6F-07C9-4CC4-8B64-F59D82FB8BE3.txt?accessKey=1589094390_3573176945688459523_oOvkQoPlbl9fPoJG1v1vK5NdJTucUtWucC9bizZwY9B4EZuopjhvtKxf1naNx4BtYBPHmq2Ea0IfZs6uAOK65demdPuIBJvsxNIPFfrYYRhSRFT8ltCRFGBdHI5M8WWPyiCP4eRCwPkGaVVgFFVibbGEpYk4eDyInD0EiVNTfc8rDwSroUxEhY%2BdhrzluHhCVRkaPC5aMqSVFmY%2BMvW9SyvdvqS3dYOCEUb509YanCazpFPm%2FXSgxoTQFlNvzOnY
SCENARIO
The app downloads user subscriptions one by one. This call will be made in multiple places (in completion block after another network call and from a button press from a UIAlertController). The logic is to download all the subscriptions and once one subscription download is down it goes to the next until all have been downloaded and then our SVProgressHUD dismisses. The code works great when we build and run from Xcode. But when we build the IPA and send to our customer, this logic creates some sort of a stall, and the SVProgressHUD alert keeps spinning:
This is a big problem because our application is focused around downloading content from subscriptions.
Why is this logic stalling after I Archive and build an IPA from it, but not when I build and run from Xcode?
Code Below:
// Making the call
DispatchQueue.main.async {
DGWebService().syncUserSubscribedContent {
DispatchQueue.main.async {
self.finishLogin()
}
}
}
// Sequentially going through each subscription and downloading them
func syncUserSubscribedContent(completion: #escaping Constants.WebService.ContentCompletion) {
let subscriptions = MPTUser.sharedUser.getSubscriptionsForDownload()
DispatchQueue.global().async {
if subscriptions.count > 0 {
var index:Int = 0
var t = subscriptions.count
var downloading: Bool = false
while t != 0 {
if downloading == false {
downloading = true
if index < 0 {
index = 0
}
if index > subscriptions.count - 1 {
index = subscriptions.count - 1
}
if index <= subscriptions.count {
let subscription = subscriptions[index]
if subscription.didDownloadContent == false {
if let subscriptionID = subscription.subscriptionID {
DispatchQueue.main.async {
SVProgressHUD.show(withStatus: "Downloading Documents\nfor\n\(subscription.functionalGroupName!)\n\(index+1) of \(subscriptions.count)")
}
self.getUserSubscribedContent(subscriptionID: subscriptionID, completion: { (success) in
subscription.didDownloadContent = true
index += 1
t -= 1
downloading = false
})
}
else {
index += 1
t -= 1
downloading = false
}
}
}
else {
index += 1
t -= 1
downloading = false
}
}
}
}
completion()
}
}
self.getUserSubscribedContent is a function that downloads the content and sends a completion back in the block.
If someone could help me out here it would be much appreciated.
You can try using a DispatchGroup. Here's a rough (and untested) example:
DispatchQueue.global().async {
let subscriptions = MPTUser.sharedUser.getSubscriptionsForDownload()
let group = DispatchGroup()
var completed = 0
let completion: (Bool) -> Void = {
if $0 {
completed += 1
}
group.leave()
DispatchQueue.main.async {
SVProgressHUD.show(withStatus: "Downloading Documents\nfor\n\(subscription.functionalGroupName!)\n\(completed) of \(subscriptions.count)")
}
}
for subscription in subscriptions {
self.getUserSubscribedContent(subscriptionID: subscription.subscriptionID, completion: completion)
group.enter()
}
// However long you want to wait (in seconds) before timing out
_ = group.wait(timeout: .now() + 30)
}
I have Firebase generating random keys for my records in a Swift app. How do I go about retrieving that key if I want to change a specific record? In my example below, I'm marking a task complete but when I hit the button it doesn't work as intended because I am not referencing the particular task but to reference the task I need the randomly generated key.
func doneHit(cell:TaskCell) {
if let ip = tableView.indexPathForCell(cell) {
var task = tasksInSectionArray[ip.section][ip.row]
let tasksRef = ref.childByAppendingPath("tasks")
ref.observeSingleEventOfType(.Value, withBlock: { snapshot in
let doneRef = tasksRef.childByAppendingPath("\(snapshot.key)/done")
if task.done == false {
task.done = true
cell.checkBox.image = UIImage(named: "checkedbox")
doneRef.setValue(task.done)
}
else {
task.done = false
cell.checkBox.image = UIImage(named: "uncheckedbox")
doneRef.setValue(task.done)
}
let completedByRef = tasksRef.childByAppendingPath("\(snapshot.key)/completedBy")
if task.done == true {
completedByRef.setValue(self.user)
cell.detailLabel.text = "Completed By: \(self.user)"
}
else {
completedByRef.setValue("")
cell.detailLabel.text = ""
}
})
}
}
My Firebase structure:
tasks
randomly generated ID
title:
description:
randomly generated ID
title:
description:
Update 1:
I have updated my code to get the IDs for all of the tasks but the functionality of updating the backend isn't working properly. It is only letting update the tasks created in the app. There are 150 tasks that I imported using the web Dashboard. Those are the ones I can't get to update.
func doneHit(cell:TaskCell) {
if let ip = tableView.indexPathForCell(cell) {
var task = tasksInSectionArray[ip.section][ip.row]
let tasksRef = ref.childByAppendingPath("tasks")
var taskID = ""
tasksRef.observeSingleEventOfType(.Value, withBlock: { snapshot in
for task in snapshot.children {
let _tasks = task as! FDataSnapshot
let id = _tasks.key
print(id)
taskID = id
}
let doneRef = tasksRef.childByAppendingPath("\(taskID)/done")
if task.done == false {
task.done = true
cell.checkBox.image = UIImage(named: "checkedbox")
doneRef.setValue(task.done)
}
else {
task.done = false
cell.checkBox.image = UIImage(named: "uncheckedbox")
doneRef.setValue(task.done)
}
let completedByRef = tasksRef.childByAppendingPath("\(taskID)/completedBy")
if task.done == true {
completedByRef.setValue(self.user)
cell.detailLabel.text = "Completed By: \(self.user)"
}
else {
completedByRef.setValue("")
cell.detailLabel.text = ""
}
})
}
}
To create a randomly generated key you need to use childByAutoId() (which I can't see in your example code).
This will return a Firebase reference you could use and which will return the key with it's .key property
var post1Ref = ref.childByAutoId()
post1Ref.setValue(post1)
var postId = post1Ref.key
See documentation here
tasksRef.observeSingleEventOfType(.Value, withBlock: { snapshot in
for task in snapshot.children {
guard let taskSnapshot = task as? FDataSnapshot else {
continue
}
let id = task.key
// do other things
}
}
Following Tim's answer for Swift 3 and Firebase 4 I had to use the below
for task in snapshot.children {
guard let taskSnapshot = task as? DataSnapshot else {
continue
}
let id = taskSnapshot.key
An alternative to Ron's answer involves generating the key first and then using it to set value:
let autoId = databaseReference.childByAutoId().key
databaseReference.child(autoId).setValue(YOUR_VALUE)
When I rotate the app twice after selecting a few items, it crashes. I have overridden the sendEvent method and that's where the debugger stops. When I try to print the event type, it shows me something weird (I think it's a memory location that doesn't exist):
(lldb) print event.type
(UIEventType) $R10 = <invalid> (0xff)
Somehow I think this is related to how I handle the rotation. I have a master-detail style application, that uses a different type of navigation for pad-landscape, pad-portrait and phone. I have created a class named NavigationFlowController which handles all navigational events and sets up the views accordingly. On rotation, it breaks up the view trees and recomposes them with the correct navigation
func changeViewHierarchyForDevideAndOrientation(newOrientation:UIInterfaceOrientation? = nil){
print("MA - Calling master layout method")
UIApplication.myDelegate().window?.frame = UIScreen.mainScreen().bounds
let idiom = UIDevice.currentDevice().userInterfaceIdiom
var orientation:UIInterfaceOrientation!
if let no = newOrientation{
orientation = no
}else{
orientation = UIApplication.sharedApplication().statusBarOrientation
}
print("MA - Breaking up view tree...")
breakupFormerViewTree([sidebarViewController, listViewController, detailViewController, loginViewController])
print("MA - Start init navbackbone")
initNavBackboneControllers()
guard let _ = UIApplication.myDelegate().currentUser else {
if idiom == UIUserInterfaceIdiom.Phone{
currentState = AppState.PHONE
}else if idiom == UIUserInterfaceIdiom.Pad && UIInterfaceOrientationIsLandscape(orientation){
currentState = AppState.PAD_LANDSCAPE
}else if idiom == UIUserInterfaceIdiom.Pad && UIInterfaceOrientationIsPortrait(orientation){
currentState = AppState.PAD_PORTRAIT
}
print("MA - Current user is nil - resetting")
mainViewController.addChildViewController(loginViewController)
return
}
if idiom == UIUserInterfaceIdiom.Phone{
currentState = AppState.PHONE
leftNavigationController?.viewControllers = [listViewController]
slideViewController?.rearViewController = sidebarViewController
slideViewController?.frontViewController = leftNavigationController
slideViewController?.rearViewRevealWidth = 267;
mainViewController.addChildViewController(slideViewController!)
}else if idiom == UIUserInterfaceIdiom.Pad && UIInterfaceOrientationIsLandscape(orientation){
currentState = AppState.PAD_LANDSCAPE
leftNavigationController!.viewControllers = [sidebarViewController, listViewController]
rightNavigationController!.viewControllers = [detailViewController]
detailViewController.navigationItem.leftBarButtonItems = []
detailViewController.initLayout()
print("MA - Init split view controller with VCs")
splitViewController!.viewControllers = [leftNavigationController!, rightNavigationController!]
mainViewController.addChildViewController(splitViewController!)
}else if idiom == UIUserInterfaceIdiom.Pad && UIInterfaceOrientationIsPortrait(orientation){
currentState = AppState.PAD_PORTRAIT
leftNavigationController!.pushViewController(sidebarViewController, animated: false)
leftNavigationController!.pushViewController(listViewController, animated: false)
rightNavigationController!.pushViewController(detailViewController, animated: false)
rightNavigationController?.setNavigationBarHidden(false, animated: false)
slideViewController!.rearViewController = leftNavigationController
slideViewController!.frontViewController = rightNavigationController
detailViewController.navigationItem.leftBarButtonItem = UIBarButtonItem(title: "< Documenten", style: UIBarButtonItemStyle.Bordered, target: slideViewController, action: "revealToggle:")
detailViewController.initLayout()
slideViewController!.rearViewRevealWidth = 350;
mainViewController.addChildViewController(slideViewController!)
}
}
func breakupFormerViewTree(vcs:[UIViewController?]){
for vc in vcs{
if let vcUnwrapped = vc, _ = vcUnwrapped.parentViewController {
vcUnwrapped.removeFromParentViewController()
vcUnwrapped.view.removeFromSuperview()
}
}
}
func initNavBackboneControllers(){
leftNavigationController = UINavigationController()
leftNavigationController?.navigationBar.barTintColor = UIColor(red: 0.25, green: 0.25, blue: 0.25, alpha: 1.0)
leftNavigationController?.navigationBar.tintColor = UIColor.whiteColor()
leftNavigationController?.navigationBar.titleTextAttributes = [NSForegroundColorAttributeName: UIColor.whiteColor()]
leftNavigationController?.navigationBar.translucent = false
rightNavigationController = UINavigationController()
rightNavigationController?.navigationBar.barTintColor = UIColor(red: 0.25, green: 0.25, blue: 0.25, alpha: 1.0)
rightNavigationController?.navigationBar.tintColor = UIColor.whiteColor()
rightNavigationController?.navigationBar.titleTextAttributes = [NSForegroundColorAttributeName: UIColor.whiteColor()]
rightNavigationController?.navigationBar.translucent = false
slideViewController = SWRevealViewController()
slideViewController?.rearViewRevealOverdraw = 0;
slideViewController?.bounceBackOnOverdraw = false;
slideViewController?.stableDragOnOverdraw = true;
slideViewController?.delegate = self
if UIDevice.currentDevice().userInterfaceIdiom == UIUserInterfaceIdiom.Pad{
splitViewController = UISplitViewController()
}
}
EDIT (in response to Justin's questions):
1) I've experienced the crash on all iOS8 iPad simulators.
2) From a fresh start, if I select like 6-7 items and then I rotate twice, it crashes. But I can also select an item, rotate a few times, select some more and keep rotating and at some point it will crash.
3) When an item is selected, the following code is executed:
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let document = getInfoForSection(indexPath.section).documents[indexPath.item]
if document.canOpen{
openDocument(document)
DataManager.sharedInstance.getDocument(document.uri, after: {
(document:Document?) -> () in
if let documentUnwrapped = document{
let detailVC = NavigationFlowController.sharedInstance.detailViewController;
if detailVC.document?.uri == documentUnwrapped.uri{
NavigationFlowController.sharedInstance.detailViewController.documentUpdated(documentUnwrapped)
}
}
})
}
}
And then in the detail view controller:
func initLayout(){
if viewForCard == nil{
// views not yet initialized, happens when initLayout if called from the document setter before this view has been loaded
// just return, the layouting will be done on viewDidLoad with the correct document instead
return
}
self.navigationItem.rightBarButtonItems = []
if document == nil{
// Removed code that handles no document selected
...
return
}
heightForCard.constant = NavigationFlowController.sharedInstance.currentState == AppState.PHONE ? CARD_HEIGHT_PHONE : CARD_HEIGHT_TABLET
viewForCard.hidden = false
removeAllSubviews(viewForCard)
removeAllSubviews(viewForDetails)
viewForDetails.translatesAutoresizingMaskIntoConstraints = false
self.metaVC?.document = document
//self.documentVC?.document = document
self.navigationItem.rightBarButtonItems = []
downloadDocumentIfNeeded()
if NavigationFlowController.sharedInstance.currentState == AppState.PAD_LANDSCAPE || NavigationFlowController.sharedInstance.currentState == AppState.PAD_PORTRAIT{
self.viewForDetails.backgroundColor = document?.senderStyling?.color
addChildViewController(self.metaVC!)
addChildViewController(self.documentVC!)
let metaView = self.metaVC!.view
let documentView:UIView = self.documentVC!.view
viewForDetails.addSubview(metaView)
viewForDetails.addSubview(documentView)
// whole lot of layouting code removed
...
let doubleTap = UITapGestureRecognizer(target: self, action: "toggleZoom")
documentVC!.view.addGestureRecognizer(doubleTap)
}else{
// Phone version code removed
...
}
}
EDIT2:
func downloadDocumentIfNeeded(){
var tmpPath:NSURL?
if let url = document?.contentUrl{
let directoryURL = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)[0]
if let docName = self.document?.name,
safeName = disallowedCharacters?.stringByReplacingMatchesInString(docName, options: [], range: NSMakeRange(0, docName.characters.count), withTemplate: "-"){
tmpPath = directoryURL.URLByAppendingPathComponent("\(safeName)_\(DetailViewController.dateFormatter.stringFromDate(self.document!.creationDate!)).pdf")
}
if let urlString = tmpPath?.path{
if NSFileManager.defaultManager().fileExistsAtPath(urlString) {
// File is there, load it
loadDocumentInWebview(tmpPath!)
}else{
// Download file
let destination: (NSURL, NSHTTPURLResponse) -> (NSURL) = {
(temporaryURL, response) in
if let path = tmpPath{
return path
}
return temporaryURL
}
download(.GET, URLString: url, destination: destination).response {
(request, response, data, error) in
if error != nil && error?.code != 516{
ToastView.showToastInParentView(self.view, withText: "An error has occurred while loading the document", withDuaration: 10)
}else if let pathUnwrapped = tmpPath {
self.loadDocumentInWebview(pathUnwrapped)
}
}
}
}
}
}
func loadDocumentInWebview(path:NSURL){
if self.navigationItem.rightBarButtonItems == nil{
self.navigationItem.rightBarButtonItems = []
}
self.documentVC?.finalPath = path
let shareItem = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.Action, target: self, action: "share")
shareItem.tag = SHARE_ITEM_TAG
addNavItem(shareItem)
}
func addNavItem(navItem:UIBarButtonItem){
var addIt = true
for item in self.navigationItem.rightBarButtonItems!{
if item.tag == navItem.tag{
addIt = false
}
}
if addIt{
self.navigationItem.rightBarButtonItems?.append(navItem)
self.navigationItem.rightBarButtonItems!.sortInPlace({ $0.tag > $1.tag })
}
}
EDIT3: I've overridden the sendEvent method to track whether or not a user is touching the app or not, but even if I take out this code, it still crashes, and the debugger then breaks on UIApplicationMain.
override func sendEvent(event: UIEvent)
{
super.sendEvent(event)
if event.type == UIEventType.Touches{
if let touches = event.allTouches(){
for item in touches{
if let touch = item as? UITouch{
if touch.phase == UITouchPhase.Began{
touchCounter++
}else if touch.phase == UITouchPhase.Ended || touch.phase == UITouchPhase.Cancelled{
touchCounter--
}
if touchCounter == 0{
receiver?.notTouching()
}
}
}
}
}
}
Tough one, a bit more insight in the events upto this bug might be helpful.
Does it happen on every device (if not, which devices gives you troubles)
It happens after "vigorously selecting" items. Did your device change orientation before that. Does it also happen before you once rotate?
What do you do in code when you "select an item".
Other then that, I'd start to get the flow of removing your child ViewControllers in breakupFormerViewTree() right. Based on the Apple Docs you want to tell the child it's being removed, before removing the view and then finally removing the child from the Parent ViewController
https://developer.apple.com/library/ios/featuredarticles/ViewControllerPGforiPhoneOS/CreatingCustomContainerViewControllers/CreatingCustomContainerViewControllers.html
Here it actually says you want to call willMoveToParentViewController(nil) before doing the removing. It doesn't say what happens if you don't, but I can imagine the OS doing some lifecycle management there, preventing it from sending corrupt events at a later point.
https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIViewController_Class/index.html#//apple_ref/occ/instm/UIViewController/willMoveToParentViewController:
EDIT (After extra was code posted)
I don't see anything else in your code that might cause it to crash. It does look like a memory-error as you stated, but no idea where it's coming from. Try turning on Zombie objects and Guard Malloc (Scheme > Run > Diagnostics) and maybe you can get a bit more info on what's causing it.
Other then that, I'd just comment out loads of your implementation, swap Subclasses with empty ViewControllers until it doesn't happen again. You should be able to pinpoint what part of your implementation is involved in creating this event. Once you do that, well, pinpoint more and evaluate every single line of code in that implementation.